Skip to content

Commit 7dfec22

Browse files
committed
UPDATED - Effects
1 parent 3ed9238 commit 7dfec22

21 files changed

Lines changed: 593 additions & 69 deletions

File tree

src/animabf.mjs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { registerSettings, ABFSettingsKeys } from './utils/registerSettings';
22
import { Logger, preloadTemplates } from './utils';
33
import ABFActorSheet from './module/actor/ABFActorSheet';
4+
import ABFTokenHUD from './module/actor/ABFTokenHUD';
45
import ABFFoundryRoll from './module/rolls/ABFFoundryRoll';
56
import ABFCombat from './module/combat/ABFCombat';
67
import { ABFActor } from './module/actor/ABFActor';
@@ -54,6 +55,7 @@ Hooks.once('init', async () => {
5455

5556
CONFIG.Item.documentClass = ABFItem;
5657
CONFIG.ui.actors = ABFActorDirectory;
58+
CONFIG.Token.hudClass = ABFTokenHUD;
5759

5860
// Register custom sheets (if any)
5961
Actors.unregisterSheet('core', ActorSheet);
@@ -319,6 +321,16 @@ Hooks.on('chatMessage', (chatLog, message, chatData) => {
319321
return false;
320322
});
321323

324+
Hooks.on('renderActiveEffectConfig', (app, html) => {
325+
const transfer = html.find('input[name="transfer"]');
326+
if (!transfer.length) return;
327+
328+
transfer.prop('checked', false);
329+
transfer.prop('disabled', true);
330+
331+
transfer.closest('.form-group').hide();
332+
});
333+
322334
// // Auto-number unlinked tokens as "{name} (n)" when dropped
323335
// Hooks.on('createToken', async doc => {
324336
// // Ignore linked tokens

src/lang/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,7 @@
633633
"dialogs.items.technique.content": "Technique name",
634634
"dialogs.items.title.content": "Title name",
635635
"dialogs.items.weapons.content": "Weapon name",
636+
"anima.ui.effects.effectsList.newEffectDialog.content": "Effect name",
636637
"dialogs.mod.content": "Modifier",
637638
"dialogs.newSupernaturalShield.title": "New Supernatural Shield",
638639
"dialogs.psychicShield.difficulty.special": "Dice Roll",

src/lang/es.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,7 @@
635635
"dialogs.items.technique.content": "Nombre de la técnica",
636636
"dialogs.items.title.content": "Nombre del título",
637637
"dialogs.items.weapons.content": "Nombre del arma",
638+
"anima.ui.effects.effectsList.newEffectDialog.content": "Nombre del efecto",
638639
"dialogs.mod.content": "Modificador",
639640
"dialogs.newSupernaturalShield.title": "Nuevo Escudo Sobrenatural",
640641
"dialogs.psychicShield.difficulty.special": "Realizar Tirada",

src/lang/fr.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@
597597
"dialogs.items.technique.content": "Nom de la Technique",
598598
"dialogs.items.title.content": "Nom du Titre",
599599
"dialogs.items.weapons.content": "Nom de l'Arme",
600+
"anima.ui.effects.effectsList.newEffectDialog.content": "Effect name",
600601
"dialogs.migrations.content": "Une migration va commencer. Pour éviter toute perte de données, sauvegardez votre dossier de données avant d'appliquer la migration. Êtes-vous prêt à continuer ?",
601602
"dialogs.migrations.error": "Échec de l'application de la migration n°{version} :\n{error}",
602603
"dialogs.migrations.success": "La migration n°{version} « {title} » a été appliquée avec succès.",

src/module/actor/ABFActor.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,10 +344,10 @@ export class ABFActor extends Actor {
344344
);
345345

346346
for (const psychicShield of psychicShields) {
347-
const psychic = psychicShield.getFlag(game.abf.id, 'psychic');
347+
const psychic = psychicShield.getFlag(game.animabf.id, 'psychic');
348348
if (psychic?.overmantained) {
349349
if (psychic.maintainMax >= psychicShield.system.shieldPoints) {
350-
psychicShield.unsetFlag(game.abf.id, 'psychic');
350+
psychicShield.unsetFlag(game.animabf.id, 'psychic');
351351
} else {
352352
const supShield = {
353353
system: psychicShield.system,

src/module/actor/ABFActorSheet.js

Lines changed: 144 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ABFFoundryRoll from '../rolls/ABFFoundryRoll';
33
import { splitAsActorAndItemChanges } from './utils/splitAsActorAndItemChanges';
44
import { unflat } from './utils/unflat';
55
import { ALL_ITEM_CONFIGURATIONS } from './utils/prepareItems/constants';
6+
import { INITIAL_EFFECT_DATA } from '../types/effects/EffectItemConfig';
67
import { getFieldValueFromPath } from './utils/prepareItems/util/getFieldValueFromPath';
78
import { getUpdateObjectFromPath } from './utils/prepareItems/util/getUpdateObjectFromPath';
89
import { ABFItems } from '../items/ABFItems';
@@ -98,18 +99,27 @@ export default class ABFActorSheet extends ActorSheet {
9899
async getData(options) {
99100
const sheet = await super.getData(options);
100101

101-
if (this.actor.type === 'character') {
102-
await sheet.actor.prepareDerivedData();
103-
sheet.system = sheet.actor.system;
102+
const actor = this.actor; // use the real Document, not sheet.actor
103+
104+
if (actor?.type === 'character') {
105+
await actor.prepareDerivedData();
106+
sheet.system = actor.system;
104107
}
105108

106109
sheet.config = CONFIG.config;
110+
107111
const permissions = game.settings.get(
108112
game.animabf.id,
109113
ABFSettingsKeys.MODIFY_DICE_FORMULAS_PERMISSION
110114
);
111115
sheet.canModifyDice = permissions?.[game.user.role] === true;
112-
sheet.effects = this.actor.effects?.contents ?? [];
116+
117+
// Use embedded item collection directly
118+
const effectItems = actor.items.filter(i => i && i.type === ABFItems.EFFECT);
119+
sheet.effects = effectItems;
120+
121+
console.log('EFFECT ITEMS EN SHEET', sheet.effects);
122+
113123
return sheet;
114124
}
115125

@@ -175,31 +185,74 @@ export default class ABFActorSheet extends ActorSheet {
175185
const a = event.currentTarget;
176186
const action = a.dataset.action;
177187
const li = a.closest('.effect');
178-
const effectId = li?.dataset.effectId;
188+
const itemId = li?.dataset.itemId;
189+
const item = itemId ? this.actor.items.get(itemId) : null;
179190

180191
switch (action) {
181-
case 'create':
192+
case 'create': {
182193
const name = game.i18n.localize('anima.effects.newEffect') ?? 'New Effect';
183-
return this.actor.createEmbeddedDocuments('ActiveEffect', [
194+
const [created] = await this.actor.createEmbeddedDocuments('Item', [
184195
{
196+
type: ABFItems.EFFECT,
185197
name,
186-
icon: 'icons/svg/aura.svg'
198+
system: INITIAL_EFFECT_DATA
187199
}
188200
]);
201+
if (created?.sheet) created.sheet.render(true);
202+
return;
203+
}
189204

190205
case 'edit': {
191-
const effect = this.actor.effects.get(effectId);
192-
return effect?.sheet?.render(true);
206+
if (!item) return;
207+
208+
// Asegura que exista un AE vinculado a este item
209+
const effect = await this._ensureEffectForItem(item);
210+
if (!effect) return;
211+
212+
// Configura la sincronización item <-> AE
213+
this._setupEffectSync(item, effect);
214+
215+
return effect.sheet?.render(true);
193216
}
194217

195-
case 'delete':
196-
return this.actor.deleteEmbeddedDocuments('ActiveEffect', [effectId]);
218+
case 'delete': {
219+
if (!itemId) return;
220+
221+
const item = this.actor.items.get(itemId);
222+
if (!item) return;
223+
224+
// get linked AE
225+
const effect = this._getLinkedEffect(item);
226+
227+
const deletions = [];
228+
229+
// delete item
230+
deletions.push(this.actor.deleteEmbeddedDocuments('Item', [itemId]));
231+
232+
// delete linked AE
233+
if (effect) {
234+
deletions.push(this.actor.deleteEmbeddedDocuments('ActiveEffect', [effect.id]));
235+
}
236+
237+
return Promise.all(deletions);
238+
}
197239

198240
case 'toggle': {
199-
const effect = this.actor.effects.get(effectId);
200-
if (!effect) return;
201-
return effect.update({ disabled: !effect.disabled });
241+
if (!item) return;
242+
243+
const newActive = !item.system.active;
244+
await item.update({ 'system.active': newActive });
245+
246+
const effect = this._getLinkedEffect(item);
247+
if (effect) {
248+
await effect.update({ disabled: !newActive });
249+
}
250+
251+
return;
202252
}
253+
254+
default:
255+
return;
203256
}
204257
}
205258

@@ -356,4 +409,80 @@ export default class ABFActorSheet extends ActorSheet {
356409
...otherItems
357410
]);
358411
};
412+
413+
_getLinkedEffect(item) {
414+
if (!item) return null;
415+
return this.actor.effects.find(e => e.origin === item.uuid) ?? null;
416+
}
417+
418+
async _linkItemToEffect(item, effect) {
419+
if (!item || !effect) return;
420+
await item.setFlag('animabf', 'linkedEffectId', effect.id);
421+
}
422+
423+
async _ensureEffectForItem(item) {
424+
if (!item) return null;
425+
426+
let effect = this._getLinkedEffect(item);
427+
if (effect) return effect;
428+
429+
const rawBaseData = item.system?.effectData ?? {};
430+
// Ignore old origin if it exists in stored data
431+
const { origin, ...baseData } = rawBaseData;
432+
433+
const data = foundry.utils.mergeObject(
434+
{
435+
name: item.name,
436+
icon: item.img || 'icons/svg/aura.svg',
437+
disabled: !item.system?.active,
438+
origin: item.uuid // always the current item
439+
},
440+
baseData,
441+
{ inplace: false }
442+
);
443+
444+
const [created] = await this.actor.createEmbeddedDocuments('ActiveEffect', [data]);
445+
return created ?? null;
446+
}
447+
448+
async _onDropItem(event, data) {
449+
const created = await super._onDropItem(event, data);
450+
451+
const items = Array.isArray(created) ? created : created ? [created] : [];
452+
453+
for (const item of items) {
454+
if (item.type !== ABFItems.EFFECT) continue;
455+
456+
await item.update({
457+
'system.active': false,
458+
'system.effectData.disabled': true
459+
});
460+
461+
await this._ensureEffectForItem(item);
462+
}
463+
464+
return created;
465+
}
466+
467+
_setupEffectSync(item, effect) {
468+
const handler = async (doc, diff, options, userId) => {
469+
if (doc.id !== effect.id) return;
470+
if (userId !== game.user.id) return;
471+
472+
if (doc.transfer === true) {
473+
await doc.update({ transfer: false });
474+
return;
475+
}
476+
477+
const obj = doc.toObject();
478+
const { _id, _key, parent, ...clean } = obj;
479+
480+
await item.update({ 'system.effectData': clean });
481+
await item.update({ 'system.active': !doc.disabled });
482+
483+
Hooks.off('updateActiveEffect', handler);
484+
};
485+
486+
Hooks.on('updateActiveEffect', handler);
487+
}
359488
}

src/module/actor/ABFTokenHUD.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
export default class ABFTokenHUD extends TokenHUD {
2+
/** @override */
3+
async _onRender(context, options) {
4+
await super._onRender(context, options);
5+
6+
const root = this.element;
7+
if (!root) return;
8+
9+
const token = this.object;
10+
const actor = token?.actor;
11+
if (!actor) return;
12+
13+
const flagSystem = game.animabf.id;
14+
const flagKey = 'defensesCounter';
15+
16+
const defensesCounter = (await actor.getFlag(flagSystem, flagKey)) ?? {
17+
accumulated: 0,
18+
keepAccumulating: true
19+
};
20+
21+
const currentValue = Number(defensesCounter.accumulated) || 0;
22+
23+
const middleCol = root.querySelector('.col.middle');
24+
if (!middleCol) return;
25+
26+
// Reuse control if exists
27+
let control = middleCol.querySelector('.attribute.abf-flag-value');
28+
if (!control) {
29+
control = document.createElement('div');
30+
control.classList.add('attribute', 'abf-flag-value');
31+
control.dataset.tooltip = 'Defensas adicionales';
32+
33+
control.innerHTML = `
34+
<label style="margin-right: 4px;">DEF</label>
35+
<input type="number" name="abfFlagValue" min="0" step="1" value="0">
36+
`;
37+
38+
middleCol.prepend(control);
39+
}
40+
41+
const input = control.querySelector("input[name='abfFlagValue']");
42+
if (!input) return;
43+
44+
input.value = currentValue;
45+
46+
input.onchange = async ev => {
47+
ev.stopPropagation();
48+
ev.preventDefault();
49+
50+
const newValue = Number(ev.target.value) || 0;
51+
52+
await actor.setFlag(flagSystem, flagKey, {
53+
...defensesCounter,
54+
accumulated: newValue
55+
});
56+
};
57+
}
58+
}

src/module/actor/utils/prepareItems/constants.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { ElanPowerItemConfig } from '../../../types/general/ElanPowerItemConfig'
3434
import { ArmorItemConfig } from '../../../types/combat/ArmorItemConfig';
3535
import { SupernaturalShieldItemConfig } from '../../../types/combat/SupernaturalShieldItemConfig';
3636
import { InventoryItemItemConfig } from '../../../types/general/InventoryItemItemConfig';
37+
import { EffectItemConfig } from '../../../types/effects/EffectItemConfig';
3738

3839
export const INTERNAL_ITEM_CONFIGURATIONS = {
3940
[ArsMagnusItemConfig.type]: ArsMagnusItemConfig,
@@ -74,7 +75,8 @@ export const ITEM_CONFIGURATIONS = {
7475
[PsychicDisciplineItemConfig.type]: PsychicDisciplineItemConfig,
7576
[PsychicPowerItemConfig.type]: PsychicPowerItemConfig,
7677
[TechniqueItemConfig.type]: TechniqueItemConfig,
77-
[WeaponItemConfig.type]: WeaponItemConfig
78+
[WeaponItemConfig.type]: WeaponItemConfig,
79+
[EffectItemConfig.type]: EffectItemConfig
7880
};
7981

8082
export const ALL_ITEM_CONFIGURATIONS = {

src/module/items/ABFItem.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,45 @@
11
import { prepareItem } from './utils/prepareItem/prepareItem';
2+
import { ABFItems } from './ABFItems';
23

34
export default class ABFItem extends Item {
45
async prepareDerivedData() {
56
await super.prepareDerivedData();
67

78
await prepareItem(this);
89
}
10+
11+
toActiveEffectData() {
12+
if (this.type !== ABFItems.EFFECT) return null;
13+
14+
const effectData = this.system.effectData ?? {};
15+
return {
16+
name: this.name,
17+
icon: effectData.icon ?? this.img ?? 'icons/svg/aura.svg',
18+
disabled: effectData.disabled ?? true,
19+
changes: effectData.changes ?? [],
20+
duration: effectData.duration ?? {},
21+
transfer: false,
22+
flags: effectData.flags ?? {}
23+
};
24+
}
25+
26+
async fromActiveEffect(activeEffect) {
27+
if (this.type !== ABFItems.EFFECT || !activeEffect) return;
28+
29+
const data = activeEffect.toObject();
30+
const { name, icon, disabled, changes, duration, flags } = data;
31+
32+
await this.update({
33+
name,
34+
'system.active': !disabled,
35+
'system.effectData': {
36+
icon,
37+
disabled,
38+
changes,
39+
duration,
40+
transfer: false,
41+
flags
42+
}
43+
});
44+
}
945
}

0 commit comments

Comments
 (0)