Skip to content

Commit 43e4636

Browse files
authored
Merge pull request #47 from pafvel/main
fix: 🐛 Single action mode for actors with speed > 1
2 parents 78ab60d + 1568202 commit 43e4636

File tree

7 files changed

+251
-24
lines changed

7 files changed

+251
-24
lines changed

src/combat/combat.js

+23-5
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ export default class YearZeroCombat extends Combat {
308308
const slowAndFastActions = game.settings.get(MODULE_ID, SETTINGS_KEYS.SLOW_AND_FAST_ACTIONS);
309309
const singleAction = game.settings.get(MODULE_ID, SETTINGS_KEYS.SINGLE_ACTION);
310310
if (toEnd && (slowAndFastActions || singleAction)) {
311-
await this.#removeAllFastAndSlowActions();
311+
await this.#removeAllActions();
312312
}
313313
}
314314

@@ -334,7 +334,7 @@ export default class YearZeroCombat extends Combat {
334334
const slowAndFastActions = game.settings.get(MODULE_ID, SETTINGS_KEYS.SLOW_AND_FAST_ACTIONS);
335335
const singleAction = game.settings.get(MODULE_ID, SETTINGS_KEYS.SINGLE_ACTION);
336336
if (slowAndFastActions || singleAction) {
337-
await this.#removeAllFastAndSlowActions();
337+
await this.#removeAllActions();
338338
}
339339

340340
return this;
@@ -421,20 +421,38 @@ export default class YearZeroCombat extends Combat {
421421
* @returns {Promise.<void>}
422422
* @async
423423
*/
424-
async #removeAllFastAndSlowActions() {
424+
async #removeAllActions() {
425425
const tokens = Array.from(new Set(this.combatants.map(c => c.token)));
426426
Promise.all(tokens.map(async c => await YearZeroCombat.#removeSlowAndFastActions(c)));
427+
Promise.all(tokens.map(async c => await YearZeroCombat.#removeSingleActions(c)));
427428
}
428429

429430
static async #removeSlowAndFastActions(token) {
430431
const effects = [
431432
STATUS_EFFECTS.FAST_ACTION,
432433
STATUS_EFFECTS.SLOW_ACTION,
433-
STATUS_EFFECTS.SINGLE_ACTION,
434434
].filter(action => CONFIG.statusEffects.find(e => e.id === action));
435435

436436
return Promise.all(effects.map(async effect =>
437-
await token.toggleActiveEffect({ id: effect }, { active: false }),
437+
await token.actor.toggleStatusEffect(effect, { active: false }),
438+
));
439+
}
440+
441+
static async #removeSingleActions(token) {
442+
const effects = [
443+
STATUS_EFFECTS.SINGLE_ACTION_1,
444+
STATUS_EFFECTS.SINGLE_ACTION_2,
445+
STATUS_EFFECTS.SINGLE_ACTION_3,
446+
STATUS_EFFECTS.SINGLE_ACTION_4,
447+
STATUS_EFFECTS.SINGLE_ACTION_5,
448+
STATUS_EFFECTS.SINGLE_ACTION_6,
449+
STATUS_EFFECTS.SINGLE_ACTION_7,
450+
STATUS_EFFECTS.SINGLE_ACTION_8,
451+
STATUS_EFFECTS.SINGLE_ACTION_9,
452+
].filter(action => CONFIG.statusEffects.find(e => e.id === action));
453+
454+
return Promise.all(effects.map(async effect =>
455+
await token.actor.toggleStatusEffect(effect, { active: false }),
438456
));
439457
}
440458

src/combat/combatant.js

+41
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,32 @@ export default class YearZeroCombatant extends Combatant {
229229
}
230230

231231
/* ------------------------------------------ */
232+
#getSingleActionStatus(combatant) {
233+
const as = combatant.combat.turns
234+
.filter(t => t.tokenId === combatant.tokenId)
235+
.slice(0, YZEC.StatusEffects.singleAction.length);
236+
for (let i = 0; i < as.length; i++) {
237+
as[i] = {
238+
id: as[i].id,
239+
action: as[i].token.hasStatusEffect(YZEC.StatusEffects.singleAction[i].id),
240+
};
241+
}
242+
return as;
243+
}
244+
245+
#updateSingleActionStatus(combatant, preCombatantActions) {
246+
const post = this.#getSingleActionStatus(combatant);
247+
for (let i = 0; i < post.length; i++) {
248+
const preIndex = preCombatantActions.findIndex(e => e.id === post[i].id);
249+
if (preIndex < 0) continue;
250+
const preAction = preCombatantActions[preIndex].action;
251+
const postAction = post[i].action;
252+
if (preAction != postAction) {
253+
const postEffect = YZEC.StatusEffects.singleAction[i];
254+
combatant.token.actor.toggleStatusEffect(postEffect.id, { active: preAction });
255+
}
256+
}
257+
}
232258

233259
/**
234260
* Swaps initiative cards between two combatants.
@@ -266,9 +292,24 @@ export default class YearZeroCombatant extends Combatant {
266292
});
267293
}
268294
}
295+
if (game.settings.get(MODULE_ID, SETTINGS_KEYS.SINGLE_ACTION)) {
296+
// Action status effects rely on the order of combatants with the same token.
297+
// If that order changes, the actions must be updated accordingly.
298+
const thisActions = this.#getSingleActionStatus(this);
299+
const combatantActions = this.#getSingleActionStatus(tCombatant);
300+
301+
const result = await this.combat.updateEmbeddedDocuments('Combatant', updates, { turnEvents: false });
302+
303+
this.#updateSingleActionStatus(this, thisActions);
304+
if (this.tokenId != tCombatant.tokenId) {
305+
this.#updateSingleActionStatus(tCombatant, combatantActions);
306+
}
307+
return result;
308+
}
269309
return this.combat.updateEmbeddedDocuments('Combatant', updates, { turnEvents: false });
270310
}
271311

312+
272313
/* ------------------------------------------ */
273314
/* Combatant Creation */
274315
/* ------------------------------------------ */

src/combat/slow-and-fast-actions.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
import { YZEC } from '@module/config';
2+
import { MODULE_ID, SETTINGS_KEYS } from '@module/constants';
23

34
export function addSlowAndFastStatusEffects() {
45
CONFIG.statusEffects.push(...YZEC.StatusEffects.slowAndFastActions);
56
}
67

78
export function addSingleActionStatusEffect() {
8-
CONFIG.statusEffects.push(YZEC.StatusEffects.singleAction);
9+
CONFIG.statusEffects.push(...YZEC.StatusEffects.singleAction);
10+
}
11+
12+
export function onRenderTokenHUD(_app, html, options) {
13+
const key = game.settings.get(MODULE_ID, SETTINGS_KEYS.ACTOR_SPEED_ATTRIBUTE);
14+
const speed = foundry.utils.getProperty(options.delta, key)
15+
|| foundry.utils.getProperty(game.actors.get(options.actorId), key)
16+
|| 1;
17+
18+
// Remove unused single action status effects from HUD
19+
for (let i = 1 + speed; i <= 9; i++) {
20+
const effects = html.find(`.effect-control[data-status-id="action${i}"]`);
21+
effects.remove();
22+
}
923
}

src/module/config.js

+142-9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ import { CARDS_DRAW_KEEP_STATES, MODULE_ID, STATUS_EFFECTS } from './constants.j
77
*/
88
export const YZEC = {};
99

10+
function singleActionCondition(combat, combatant, index) {
11+
const tokenId = combat.combatants.get(combatant.id).tokenId;
12+
let action = 0;
13+
for (const turn of combat.turns) {
14+
if (turn.tokenId === tokenId) action++;
15+
if (turn.id === combatant.id) break;
16+
}
17+
return action === index;
18+
}
19+
1020
YZEC.CombatTracker = {
1121
src: `modules/${MODULE_ID}/sidebar/combat-tracker.config.json`,
1222
// config: undefined,
@@ -31,12 +41,85 @@ YZEC.CombatTracker = {
3141
],
3242
singleAction: [
3343
{
34-
eventName: 'single-action-button-clicked',
44+
eventName: 'single-action-button-1-clicked',
45+
icon: 'fa-play',
46+
id: 'single-action-button-1',
47+
property: 'action1',
48+
label: 'YZEC.CombatTracker.SingleAction',
49+
visibility: 'owner',
50+
condition: (combat, combatant) => singleActionCondition(combat, combatant, 1),
51+
},
52+
{
53+
eventName: 'single-action-button-2-clicked',
54+
icon: 'fa-play',
55+
id: 'single-action-button-2',
56+
property: 'action2',
57+
label: 'YZEC.CombatTracker.SingleAction',
58+
visibility: 'owner',
59+
condition: (combat, combatant) => singleActionCondition(combat, combatant, 2),
60+
},
61+
{
62+
eventName: 'single-action-button-3-clicked',
63+
icon: 'fa-play',
64+
id: 'single-action-button-3',
65+
property: 'action3',
66+
label: 'YZEC.CombatTracker.SingleAction',
67+
visibility: 'owner',
68+
condition: (combat, combatant) => singleActionCondition(combat, combatant, 3),
69+
},
70+
{
71+
eventName: 'single-action-button-4-clicked',
72+
icon: 'fa-play',
73+
id: 'single-action-button-4',
74+
property: 'action4',
75+
label: 'YZEC.CombatTracker.SingleAction',
76+
visibility: 'owner',
77+
condition: (combat, combatant) => singleActionCondition(combat, combatant, 4),
78+
},
79+
{
80+
eventName: 'single-action-button-5-clicked',
81+
icon: 'fa-play',
82+
id: 'single-action-button-5',
83+
property: 'action5',
84+
label: 'YZEC.CombatTracker.SingleAction',
85+
visibility: 'owner',
86+
condition: (combat, combatant) => singleActionCondition(combat, combatant, 5),
87+
},
88+
{
89+
eventName: 'single-action-button-6-clicked',
90+
icon: 'fa-play',
91+
id: 'single-action-button-6',
92+
property: 'action6',
93+
label: 'YZEC.CombatTracker.SingleAction',
94+
visibility: 'owner',
95+
condition: (combat, combatant) => singleActionCondition(combat, combatant, 6),
96+
},
97+
{
98+
eventName: 'single-action-button-7-clicked',
99+
icon: 'fa-play',
100+
id: 'single-action-button-7',
101+
property: 'action7',
102+
label: 'YZEC.CombatTracker.SingleAction',
103+
visibility: 'owner',
104+
condition: (combat, combatant) => singleActionCondition(combat, combatant, 7),
105+
},
106+
{
107+
eventName: 'single-action-button-8-clicked',
35108
icon: 'fa-play',
36-
id: 'single-action-button',
37-
property: 'action',
109+
id: 'single-action-button-8',
110+
property: 'action8',
38111
label: 'YZEC.CombatTracker.SingleAction',
39112
visibility: 'owner',
113+
condition: (combat, combatant) => singleActionCondition(combat, combatant, 8),
114+
},
115+
{
116+
eventName: 'single-action-button-9-clicked',
117+
icon: 'fa-play',
118+
id: 'single-action-button-9',
119+
property: 'action9',
120+
label: 'YZEC.CombatTracker.SingleAction',
121+
visibility: 'owner',
122+
condition: (combat, combatant) => singleActionCondition(combat, combatant, 9),
40123
},
41124
],
42125
lockInitiative: [
@@ -85,12 +168,62 @@ YZEC.StatusEffects = {
85168
statuses: ['slowAction'],
86169
},
87170
],
88-
singleAction: {
89-
id: STATUS_EFFECTS.SINGLE_ACTION,
90-
label: 'YZEC.CombatTracker.SingleAction',
91-
icon: `modules/${MODULE_ID}/assets/icons/slow-action.svg`,
92-
statuses: ['action'],
93-
},
171+
singleAction: [
172+
{
173+
id: STATUS_EFFECTS.SINGLE_ACTION_1,
174+
label: 'YZEC.CombatTracker.SingleAction',
175+
icon: `modules/${MODULE_ID}/assets/icons/slow-action.svg`,
176+
statuses: ['action1'],
177+
},
178+
{
179+
id: STATUS_EFFECTS.SINGLE_ACTION_2,
180+
label: 'YZEC.CombatTracker.SingleAction',
181+
icon: `modules/${MODULE_ID}/assets/icons/slow-action.svg`,
182+
statuses: ['action2'],
183+
},
184+
{
185+
id: STATUS_EFFECTS.SINGLE_ACTION_3,
186+
label: 'YZEC.CombatTracker.SingleAction',
187+
icon: `modules/${MODULE_ID}/assets/icons/slow-action.svg`,
188+
statuses: ['action3'],
189+
},
190+
{
191+
id: STATUS_EFFECTS.SINGLE_ACTION_4,
192+
label: 'YZEC.CombatTracker.SingleAction',
193+
icon: `modules/${MODULE_ID}/assets/icons/slow-action.svg`,
194+
statuses: ['action4'],
195+
},
196+
{
197+
id: STATUS_EFFECTS.SINGLE_ACTION_5,
198+
label: 'YZEC.CombatTracker.SingleAction',
199+
icon: `modules/${MODULE_ID}/assets/icons/slow-action.svg`,
200+
statuses: ['action5'],
201+
},
202+
{
203+
id: STATUS_EFFECTS.SINGLE_ACTION_6,
204+
label: 'YZEC.CombatTracker.SingleAction',
205+
icon: `modules/${MODULE_ID}/assets/icons/slow-action.svg`,
206+
statuses: ['action6'],
207+
},
208+
{
209+
id: STATUS_EFFECTS.SINGLE_ACTION_7,
210+
label: 'YZEC.CombatTracker.SingleAction',
211+
icon: `modules/${MODULE_ID}/assets/icons/slow-action.svg`,
212+
statuses: ['action7'],
213+
},
214+
{
215+
id: STATUS_EFFECTS.SINGLE_ACTION_8,
216+
label: 'YZEC.CombatTracker.SingleAction',
217+
icon: `modules/${MODULE_ID}/assets/icons/slow-action.svg`,
218+
statuses: ['action8'],
219+
},
220+
{
221+
id: STATUS_EFFECTS.SINGLE_ACTION_9,
222+
label: 'YZEC.CombatTracker.SingleAction',
223+
icon: `modules/${MODULE_ID}/assets/icons/slow-action.svg`,
224+
statuses: ['action9'],
225+
},
226+
],
94227
};
95228

96229
/* ------------------------------------------ */

src/module/constants.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,15 @@ export const CARD_STACK = {
1818
export const STATUS_EFFECTS = {
1919
FAST_ACTION: 'fastAction',
2020
SLOW_ACTION: 'slowAction',
21-
SINGLE_ACTION: 'action',
21+
SINGLE_ACTION_1: 'action1',
22+
SINGLE_ACTION_2: 'action2',
23+
SINGLE_ACTION_3: 'action3',
24+
SINGLE_ACTION_4: 'action4',
25+
SINGLE_ACTION_5: 'action5',
26+
SINGLE_ACTION_6: 'action6',
27+
SINGLE_ACTION_7: 'action7',
28+
SINGLE_ACTION_8: 'action8',
29+
SINGLE_ACTION_9: 'action9',
2230
};
2331

2432
/** @enum {string} */

src/sidebar/combat-tracker.js

+13-7
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
duplicateCombatant, getCombatantsSharingToken,
1313
} from '@combat/duplicate-combatant';
1414
import { YZEC } from '@module/config';
15-
import { MODULE_ID, SETTINGS_KEYS } from '@module/constants';
15+
import { MODULE_ID, SETTINGS_KEYS, STATUS_EFFECTS } from '@module/constants';
1616
import { getCombatantSortOrderModifier, resetInitiativeDeck } from '@utils/utils';
1717
import YearZeroCombatGroupColor from '../apps/combat-group-color';
1818

@@ -280,14 +280,12 @@ export default class YearZeroCombatTracker extends CombatTracker {
280280
event: eventName,
281281
origin: btn,
282282
};
283-
if (property === 'slowAction' || property === 'fastAction' || property === 'action') {
283+
284+
if (Object.values(STATUS_EFFECTS).includes(property)) {
284285
const effect = CONFIG.statusEffects.find(e => e.id === property);
285286
const active = !combatant[property];
286287
if (!active) await combatant.unsetFlag(MODULE_ID, property);
287-
await combatant.token.toggleActiveEffect(
288-
{ ...effect },
289-
{ active },
290-
);
288+
await combatant.token.actor.toggleStatusEffect(effect.id, { active });
291289
}
292290
else if (property) {
293291
await combatant.setFlag(MODULE_ID, property, !combatant.getFlag(MODULE_ID, property));
@@ -493,7 +491,15 @@ export default class YearZeroCombatTracker extends CombatTracker {
493491
// FIXME: Figure out why these turn up as flags.
494492
delete flags.fastAction;
495493
delete flags.slowAction;
496-
delete flags.action;
494+
delete flags.action1;
495+
delete flags.action2;
496+
delete flags.action3;
497+
delete flags.action4;
498+
delete flags.action5;
499+
delete flags.action6;
500+
delete flags.action7;
501+
delete flags.action8;
502+
delete flags.action9;
497503
const statuses = combatant.actor?.statuses.reduce((acc, s) => {
498504
acc[s] = true;
499505
return acc;

src/yze-combat.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818
import YearZeroCards from '@combat/cards';
1919
import YearZeroCombat from '@combat/combat';
2020
import YearZeroCombatant from '@combat/combatant';
21-
import { addSlowAndFastStatusEffects, addSingleActionStatusEffect } from '@combat/slow-and-fast-actions';
21+
import {
22+
addSlowAndFastStatusEffects,
23+
addSingleActionStatusEffect,
24+
onRenderTokenHUD,
25+
} from '@combat/slow-and-fast-actions';
2226
import { YZEC } from '@module/config';
2327
import { HOOKS_KEYS, MODULE_ID, SETTINGS_KEYS } from '@module/constants';
2428
import { initializeHandlebars } from '@module/handlebars';
@@ -99,3 +103,6 @@ Hooks.on('getCombatTrackerEntryContext', YearZeroCombatTracker.appendControlsToC
99103
Hooks.on('renderCombatantConfig', onRenderCombatantConfig);
100104

101105
Hooks.on('createCombatant', YearZeroCombat.createCombatant);
106+
107+
// Removes unused single action status effects from the HUD
108+
Hooks.on('renderTokenHUD', onRenderTokenHUD);

0 commit comments

Comments
 (0)