Skip to content

Commit 2110832

Browse files
committed
Standarize the combat turn taking across combat hud and tracker
1 parent 73db471 commit 2110832

15 files changed

+216
-104
lines changed

module/checks/common-events.mjs

+55
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,59 @@ function skill(actor, item) {
261261
Hooks.call(FUHooks.SKILL_EVENT, event);
262262
}
263263

264+
/**
265+
* @description Dispatched when an actor performs a skill without am accuracy check.
266+
* @typedef ItemEvent
267+
* @property {ItemReference} item
268+
* @property {FUActor} actor
269+
* @property {String} type The item type
270+
* @property {Token} token
271+
* @property {EventTarget[]} targets
272+
*/
273+
274+
/**
275+
* @description Dispatches an event to signal the usage of a consumable
276+
* @param {FUActor} actor
277+
* @param {FUItem} item
278+
*/
279+
function item(actor, item) {
280+
/** @type ConsumableDataModel **/
281+
const consumable = item.system;
282+
const targets = Targeting.getSerializedTargetData();
283+
const eventTargets = getEventTargets(targets);
284+
285+
/** @type ItemEvent **/
286+
const event = {
287+
item: toItemReference(item),
288+
actor: actor,
289+
type: consumable.subtype.value,
290+
token: actor.resolveToken(),
291+
targets: eventTargets,
292+
};
293+
Hooks.call(FUHooks.ITEM_EVENT, event);
294+
}
295+
296+
/**
297+
* @description Dispatched when an actor performs a study check
298+
* @typedef StudyEvent
299+
* @property {FUActor} actor
300+
* @property {Token} token
301+
* @property {EventTarget[]} targets
302+
*/
303+
304+
function study(actor, targets) {
305+
const targetData = Targeting.serializeTargetData(targets);
306+
const eventTargets = getEventTargets(targetData);
307+
308+
/** @type ItemEvent **/
309+
const event = {
310+
actor: actor,
311+
token: actor.resolveToken(),
312+
targets: eventTargets,
313+
};
314+
Hooks.call(FUHooks.STUDY_EVENT, event);
315+
}
316+
264317
/**
265318
* @param {TargetData[]} targets
266319
* @returns {EventTarget[]}
@@ -301,4 +354,6 @@ export const CommonEvents = Object.freeze({
301354
loss,
302355
spell,
303356
skill,
357+
item,
358+
study,
304359
});

module/checks/common-sections.mjs

+2-2
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ const opportunity = (sections, opportunity, order) => {
151151
* @param accuracyData
152152
* @param {TemplateDamageData} damageData
153153
*/
154-
const targeted = (sections, actor, item, targets, flags, accuracyData, damageData) => {
154+
const targeted = (sections, actor, item, targets, flags, accuracyData = undefined, damageData = undefined) => {
155155
const isTargeted = targets?.length > 0 || !Targeting.STRICT_TARGETING;
156156
if (isTargeted) {
157157
sections.push(async function () {
@@ -234,7 +234,7 @@ async function showFloatyText(targetData, localizedText) {
234234
* @param {Object} flags
235235
* @param {ResourceExpense} expense
236236
*/
237-
const spendResource = (sections, actor, item, targets, flags, expense) => {
237+
const spendResource = (sections, actor, item, targets, flags, expense = undefined) => {
238238
// Resolve the expense if not explicit
239239
if (expense === undefined) {
240240
// If using the newer cost data model

module/documents/actors/actor.mjs

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { FUItem } from '../items/item.mjs';
22
import { FUHooks } from '../../hooks.mjs';
3-
import { Effects, toggleStatusEffect } from '../../pipelines/effects.mjs';
3+
import { Effects, prepareActiveEffectCategories, toggleStatusEffect } from '../../pipelines/effects.mjs';
44
import { SYSTEM } from '../../helpers/config.mjs';
55
import { Flags } from '../../helpers/flags.mjs';
66

@@ -33,6 +33,7 @@ import { Flags } from '../../helpers/flags.mjs';
3333
* @description Extend the base Actor document by defining a custom roll data structure
3434
* @extends {Actor}
3535
* @property {CharacterDataModel | NpcDataModel} system
36+
* @property {EffectCategories} effectCategories
3637
* @remarks {@link https://foundryvtt.com/api/classes/client.Actor.html}
3738
* @inheritDoc
3839
*/
@@ -310,6 +311,18 @@ export class FUActor extends Actor {
310311
return effects;
311312
}
312313

314+
/**
315+
* @returns {EffectCategories}
316+
* @remarks Used by modules mostly
317+
*/
318+
get effectCategories() {
319+
const effects = Array.from(this.allApplicableEffects());
320+
this.temporaryEffects.forEach((effect) => {
321+
if (effects.indexOf(effect) < 0) effects.push(effect);
322+
});
323+
return prepareActiveEffectCategories(effects);
324+
}
325+
313326
applyActiveEffects() {
314327
if (this.system.prepareEmbeddedData instanceof Function) {
315328
this.system.prepareEmbeddedData();

module/documents/items/consumable/consumable-data-model.mjs

+6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import { CheckHooks } from '../../../checks/check-hooks.mjs';
22
import { FU } from '../../../helpers/config.mjs';
33
import { CommonSections } from '../../../checks/common-sections.mjs';
4+
import { CheckConfiguration } from '../../../checks/check-configuration.mjs';
5+
import { CommonEvents } from '../../../checks/common-events.mjs';
46

57
Hooks.on(CheckHooks.renderCheck, (sections, check, actor, item, flags) => {
68
if (item?.system instanceof ConsumableDataModel) {
79
CommonSections.tags(sections, [{ tag: FU.consumableType[item.system.subtype.value] }, { tag: 'FU.InventoryAbbr', value: item.system.ipCost.value, flip: true }]);
810
CommonSections.description(sections, item.system.description, item.system.summary.value);
11+
const targets = CheckConfiguration.inspect(check).getTargetsOrDefault();
12+
CommonSections.targeted(sections, actor, item, targets, flags);
913
CommonSections.spendResource(sections, actor, item, [], flags);
14+
15+
CommonEvents.item(actor, item);
1016
}
1117
});
1218

module/helpers/checks.mjs

+3
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ import { Flags } from './flags.mjs';
152152
import { ChecksV2 } from '../checks/checks-v2.mjs';
153153
import { CheckHooks } from '../checks/check-hooks.mjs';
154154
import { CheckConfiguration } from '../checks/check-configuration.mjs';
155+
import { CommonEvents } from '../checks/common-events.mjs';
155156

156157
/**
157158
*
@@ -946,6 +947,8 @@ export async function promptOpenCheck(actor, title, action) {
946947
try {
947948
const studyRollHandler = new StudyRollHandler(actor, checkResult.result);
948949
await studyRollHandler.handleStudyRoll();
950+
const targets = CheckConfiguration.inspect(checkResult).getTargetsOrDefault();
951+
CommonEvents.study(actor, targets);
949952
return { rollResult: checkResult.result, message: null };
950953
} catch (error) {
951954
console.error('Error processing study roll:', error);

module/helpers/study-roll.mjs

+1-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export class StudyRollHandler {
2626
}
2727
}
2828

29-
// Iterate over each targeted actor
3029
for (const actor of targets) {
3130
await this.handleStudyRollCallback(actor);
3231
}
@@ -87,7 +86,7 @@ export class StudyRollHandler {
8786
);
8887

8988
// Render the dialog
90-
dialog.render(true);
89+
await dialog.render(true);
9190
}
9291

9392
/**

module/helpers/targeting.mjs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { ChooseWeaponDialog } from '../documents/items/skill/choose-weapon-dialog.mjs';
22
import { Flags } from './flags.mjs';
3-
import { SYSTEM } from './config.mjs';
43
import { getTargeted } from './target-handler.mjs';
54

65
/**
@@ -125,7 +124,7 @@ function deserializeTargetData(targetData) {
125124
* @param {jQuery} jQuery
126125
*/
127126
function onRenderChatMessage(document, jQuery) {
128-
if (!document.getFlag(SYSTEM, Flags.ChatMessage.Targets)) {
127+
if (!document.getFlag(Flags.Scope, Flags.ChatMessage.Targets)) {
129128
return;
130129
}
131130

module/hooks.mjs

+12
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,16 @@ export const FUHooks = {
100100
* @remarks Uses {@link StatusEvent}. It happens AFTER the status effect has been applied.
101101
*/
102102
STATUS_EVENT: 'projectfu.events.status',
103+
/**
104+
* @description Dispatched after an actor uses a consumable
105+
* @example callback(event)
106+
* @remarks Uses {@link ItemEvent}
107+
*/
108+
ITEM_EVENT: 'projectfu.events.item',
109+
/**
110+
* @description Dispatched after an actor performs a study check
111+
* @example callback(event)
112+
* @remarks Uses {@link StudyEvent}
113+
*/
114+
STUDY_EVENT: 'projectfu.events.study',
103115
};

module/pipelines/effects.mjs

+7
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ export async function onManageActiveEffect(event, owner) {
112112
}
113113
}
114114

115+
/**
116+
* @typedef EffectCategories
117+
* @property temporary
118+
* @property passive
119+
* @property inactive
120+
*/
121+
115122
/**
116123
* Prepare the data structure for Active Effects which are currently applied to an Actor or Item.
117124
* @param {ActiveEffect[]} effects The array of Active Effect instances to prepare sheet data for

module/sheets/actor-standard-sheet.mjs

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isActiveEffectForStatusEffectId, onManageActiveEffect, prepareActiveEffectCategories, toggleStatusEffect } from '../pipelines/effects.mjs';
1+
import { isActiveEffectForStatusEffectId, onManageActiveEffect, toggleStatusEffect } from '../pipelines/effects.mjs';
22
import { createChatMessage, promptCheck, promptOpenCheck } from '../helpers/checks.mjs';
33
import { ItemCustomizer } from '../helpers/item-customizer.mjs';
44
import { ActionHandler } from '../helpers/action-handler.mjs';
@@ -127,11 +127,7 @@ export class FUStandardActorSheet extends ActorSheet {
127127
context.rollData = context.actor.getRollData();
128128

129129
// Prepare active effects
130-
const effects = Array.from(this.actor.allApplicableEffects());
131-
this.actor.temporaryEffects.forEach((effect) => {
132-
if (effects.indexOf(effect) < 0) effects.push(effect);
133-
});
134-
context.effects = prepareActiveEffectCategories(effects);
130+
context.effects = this.actor.effectCategories;
135131

136132
// Combine all effects into a single array
137133
context.allEffects = [...context.effects.temporary.effects, ...context.effects.passive.effects, ...context.effects.inactive.effects];

module/ui/combat-hud.mjs

+7-19
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,11 @@ export class CombatHUD extends Application {
153153
data.cssClasses = this.options.classes.join(' ');
154154
data.cssId = this.options.id;
155155
data.isCompact = game.settings.get(SYSTEM, SETTINGS.optionCombatHudCompact);
156-
data.isGM = game.user.isGM;
157156

158157
const opacity = game.settings.get(SYSTEM, SETTINGS.optionCombatHudOpacity) / 100;
159158
data.additionalStyle = this._getAdditionalStyle(opacity);
160159

161160
const ordering = game.settings.get(SYSTEM, SETTINGS.optionCombatHudActorOrdering);
162-
163161
data.npcs = [];
164162
data.characters = [];
165163

@@ -185,14 +183,9 @@ export class CombatHUD extends Application {
185183

186184
const NPCTurnsLeftMode = game.settings.get(SYSTEM, SETTINGS.optionCombatHudShowNPCTurnsLeftMode);
187185

188-
const currentTurn = game.combat.getCurrentTurn();
189-
const turnsLeft = ui.combat.countTurnsLeft(game.combat);
190-
// const round = game.combat.round;
191-
192186
/** @type FUCombat **/
193187
const combat = game.combat;
194-
data.turnStarted = combat.isTurnStarted;
195-
data.hasCombatStarted = game.combat.started;
188+
combat.populateData(data);
196189

197190
for (const combatant of game.combat.combatants) {
198191
if (!combatant.actor || !combatant.token) continue;
@@ -202,7 +195,10 @@ export class CombatHUD extends Application {
202195
id: combatant.id,
203196
name: combatant.name,
204197
actor: combatant.actor,
198+
isOwner: combatant.isOwner,
199+
totalTurns: combatant.totalTurns,
205200
token: combatant.token,
201+
faction: combatant.faction,
206202
effects: activeEffects,
207203
// token._source should contain the most current version of the token's texture.
208204
img: game.settings.get(SYSTEM, SETTINGS.optionCombatHudPortrait) === 'token' ? combatant.token._source.texture.src : combatant.actor.img,
@@ -247,7 +243,6 @@ export class CombatHUD extends Application {
247243

248244
// Ensure shouldEffectsMarquee is false if effectsMarqueeDuration is over 9000
249245
actorData.shouldEffectsMarquee = actorData.effects.length > maxEffectsBeforeMarquee && effectsMarqueeDuration < 9000;
250-
251246
actorData.effectsMarqueeDuration = effectsMarqueeDuration;
252247

253248
const marqueeDirection = game.settings.get(SYSTEM, SETTINGS.optionCombatHudEffectsMarqueeMode);
@@ -271,29 +266,19 @@ export class CombatHUD extends Application {
271266
});
272267
}
273268

274-
actorData.isOwner = combatant.isOwner;
275269
actorData.order = order;
276270

277-
actorData.totalTurns = combatant.totalTurns;
278271
if (NPCTurnsLeftMode === 'never') {
279272
actorData.totalTurns = 1;
280273
} else if (NPCTurnsLeftMode === 'only-studied' && !this._isNPCStudied(combatant.token)) {
281274
actorData.totalTurns = 1;
282275
}
283276

284-
actorData.turnsLeft = turnsLeft[combatant.id] ?? 0;
285-
286277
if (combatant.token.disposition === foundry.CONST.TOKEN_DISPOSITIONS.FRIENDLY) {
287-
actorData.isCurrentTurn = currentTurn === 'friendly';
288278
data.characters.push(actorData);
289279
} else {
290-
actorData.isCurrentTurn = currentTurn === 'hostile';
291280
data.npcs.push(actorData);
292281
}
293-
294-
// Decides whether combatant can (start turn | take turn)
295-
actorData.isCurrentCombatant = combat.isCurrentCombatant(combatant);
296-
actorData.hasTurns = turnsLeft[combatant.id] && actorData.isCurrentTurn;
297282
}
298283

299284
data.characters.sort((a, b) => a.order - b.order);
@@ -361,6 +346,7 @@ export class CombatHUD extends Application {
361346

362347
html.find('a[data-action=start-turn]').click((event) => ui.combat.handleStartTurn(event));
363348
html.find('a[data-action=end-turn]').click((event) => ui.combat.handleEndTurn(event));
349+
html.find('a[data-action=take-turn-out-of-turn]').click((event) => ui.combat.handleTakeTurnOutOfTurn(event));
364350
}
365351

366352
_doHudDragStart(event) {
@@ -861,6 +847,8 @@ export class CombatHUD extends Application {
861847
activeEffect.parent.parent?.documentName === 'Actor' &&
862848
foundry.utils.hasProperty(changes, 'transfer') &&
863849
game.combat?.combatants.some((c) => c.actor.uuid === activeEffect.parent.parent.uuid)
850+
!(activeEffect.target && game.combat?.combatants.some((c) => c.actor.uuid === activeEffect.target.uuid)) &&
851+
game.combat?.combatants.some((c) => c.actor.uuid === activeEffect.parent.parent.uuid)
864852
)
865853
) {
866854
return;

0 commit comments

Comments
 (0)