Skip to content

Commit 75642a9

Browse files
committed
FIX - secondaries
1 parent 106b522 commit 75642a9

11 files changed

Lines changed: 390 additions & 15 deletions

src/animabf.mjs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import { FormulaEvaluator } from './utils/formulaEvaluator.js';
3232

3333
import { registerHandlebarsPartials } from './utils/handlebarsPartials.js';
3434

35+
import { macroCreators, macroExecutors } from './utils/macroCreatorRegistry.js';
36+
3537
/* ------------------------------------ */
3638
/* Initialize system */
3739
/* ------------------------------------ */
@@ -115,6 +117,19 @@ Hooks.once('ready', async () => {
115117
game.animabf.api ??= {};
116118
Object.assign(game.animabf.api, { ABFAttackData });
117119

120+
game.animabf.macros ??= {};
121+
122+
game.animabf.macros.execute = async ({ id, actorUuid, itemUuid }) => {
123+
const exec = macroExecutors[id];
124+
if (typeof exec !== 'function') return;
125+
126+
const actor = await fromUuid(actorUuid);
127+
const item = await fromUuid(itemUuid);
128+
if (!actor || !item) return;
129+
130+
return exec({ actor, item });
131+
};
132+
118133
// GM-side socket to update attack targets flag
119134
game.socket.on('system.animabf', async p => {
120135
if (!game.user.isGM) return;
@@ -380,6 +395,26 @@ Hooks.on('renderTokenHUD', async (hud, html) => {
380395
};
381396
});
382397

398+
Hooks.on('hotbarDrop', async (_bar, data, slot) => {
399+
if (data?.type !== 'Item' || !data.uuid) return;
400+
401+
const item = await fromUuid(data.uuid);
402+
if (!item) return;
403+
404+
const actor = item.parent;
405+
if (!actor) return; // No actor -> no macro
406+
407+
const creatorId = item.system?.hotbarMacroCreatorId;
408+
if (!creatorId) return; // No reference -> do nothing (let Foundry default)
409+
410+
const creator = macroCreators[creatorId];
411+
if (typeof creator !== 'function') return; // Unknown id -> do nothing
412+
413+
// Let the creator build+assign the macro. If it returns true => we handled it.
414+
const handled = await creator({ actor, item, slot });
415+
if (handled) return false; // Prevent default behavior
416+
});
417+
383418
// // Auto-number unlinked tokens as "{name} (n)" when dropped
384419
// Hooks.on('createToken', async doc => {
385420
// // Ignore linked tokens

src/module/actor/types/BaseType.js

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
import { INITIAL_ACTOR_DATA } from '../constants.js'; // ajusta el path si BaseType.js no está un nivel abajo
2+
3+
function stripSystemPrefix(systemPath) {
4+
return typeof systemPath === 'string' && systemPath.startsWith('system.')
5+
? systemPath.slice('system.'.length)
6+
: systemPath;
7+
}
8+
9+
function parseTypeMarker(str) {
10+
try {
11+
const obj = JSON.parse(str);
12+
if (!obj || typeof obj !== 'object') return null;
13+
return obj; // { type, ...overrides }
14+
} catch {
15+
return null;
16+
}
17+
}
18+
119
export class BaseType {
220
static type = 'BaseType';
321

@@ -8,6 +26,54 @@ export class BaseType {
826

927
this.actor = actor;
1028
this.systemPath = systemPath;
29+
30+
// Cache to avoid parsing JSON every _get call
31+
this._initialOverrideCache = null;
32+
this._initialOverrideCacheReady = false;
33+
}
34+
35+
_resolveInitialOverrides() {
36+
if (this._initialOverrideCacheReady) return this._initialOverrideCache;
37+
38+
this._initialOverrideCacheReady = true;
39+
this._initialOverrideCache = null;
40+
41+
const relPath = stripSystemPrefix(this.systemPath);
42+
if (!relPath) return this._initialOverrideCache;
43+
44+
const node = foundry.utils.getProperty(INITIAL_ACTOR_DATA, relPath);
45+
const markerStr = node?.__type;
46+
if (typeof markerStr !== 'string') return this._initialOverrideCache;
47+
48+
const marker = parseTypeMarker(markerStr);
49+
if (!marker) return this._initialOverrideCache;
50+
51+
// Remove "type", keep only overrides
52+
const { type, ...overrides } = marker;
53+
this._initialOverrideCache = overrides;
54+
55+
return this._initialOverrideCache;
56+
}
57+
58+
_get(relPath, fallback = undefined) {
59+
const actorValue = foundry.utils.getProperty(
60+
this.actor,
61+
`${this.systemPath}.${relPath}`
62+
);
63+
if (actorValue !== undefined) return actorValue;
64+
65+
// Fallback to INITIAL_ACTOR_DATA overrides (from __type marker), if any
66+
const overrides = this._resolveInitialOverrides();
67+
if (overrides) {
68+
const overrideValue = foundry.utils.getProperty(overrides, relPath);
69+
if (overrideValue !== undefined) return overrideValue;
70+
}
71+
72+
return fallback;
73+
}
74+
75+
_set(relPath, value) {
76+
foundry.utils.setProperty(this.actor, `${this.systemPath}.${relPath}`, value);
1177
}
1278

1379
// ---- Instance extra deps (per derived spec id) ----
@@ -43,16 +109,6 @@ export class BaseType {
43109
});
44110
}
45111

46-
_get(relPath, fallback = undefined) {
47-
return (
48-
foundry.utils.getProperty(this.actor, `${this.systemPath}.${relPath}`) ?? fallback
49-
);
50-
}
51-
52-
_set(relPath, value) {
53-
foundry.utils.setProperty(this.actor, `${this.systemPath}.${relPath}`, value);
54-
}
55-
56112
/**
57113
* Returns derived calculation specs for effectFlow.
58114
* Subclasses should override and may return multiple specs (e.g. "final", "mod").

src/module/actor/types/concreteTypes/Ability.js

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,17 @@ export class Ability extends AffectedByCharacteristicValue {
88
}
99

1010
get applyPhysicalActionMod() {
11-
return this._get('applyPhysicalActionMod', true);
11+
const v = this._get('applyPhysicalActionMod', undefined);
12+
if (v !== undefined) return v;
13+
14+
const ATTRIBUTES_AFFECTED_BY_PHYSICAL_PENALTIES = [
15+
'agility',
16+
'dexterity',
17+
'strength',
18+
'constitution'
19+
];
20+
21+
return ATTRIBUTES_AFFECTED_BY_PHYSICAL_PENALTIES.includes(this.attribute);
1222
}
1323

1424
static defaults() {
@@ -24,8 +34,19 @@ export class Ability extends AffectedByCharacteristicValue {
2434
if (!out || typeof out !== 'object') return out;
2535

2636
if (typeof out.applyAllActionMod !== 'boolean') out.applyAllActionMod = true;
27-
if (typeof out.applyPhysicalActionMod !== 'boolean')
28-
out.applyPhysicalActionMod = true;
37+
38+
const ATTRIBUTES_AFFECTED_BY_PHYSICAL_PENALTIES = [
39+
'agility',
40+
'dexterity',
41+
'strength',
42+
'constitution'
43+
];
44+
45+
if (typeof out.applyPhysicalActionMod !== 'boolean') {
46+
out.applyPhysicalActionMod = ATTRIBUTES_AFFECTED_BY_PHYSICAL_PENALTIES.includes(
47+
out.attribute
48+
);
49+
}
2950

3051
return out;
3152
}

src/module/actor/types/concreteTypes/AffectedByCharacteristicValue.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ export class AffectedByCharacteristicValue extends NumericalValue {
2424
const out = super.normalizeInflateInput(node);
2525
if (!out || typeof out !== 'object') return out;
2626

27+
// Migration: attribute.value -> attribute (string)
28+
if (
29+
out.attribute &&
30+
typeof out.attribute === 'object' &&
31+
typeof out.attribute.value === 'string'
32+
) {
33+
out.attribute = out.attribute.value;
34+
}
35+
2736
if (out.attribute === undefined) out.attribute = null;
2837

2938
if (typeof out.computeCharacteristicMod !== 'boolean') {

src/module/types/combat/WeaponItemConfig.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,8 @@ export const INITIAL_WEAPON_DATA = {
176176
primary: { value: WeaponCritic.CUT },
177177
secondary: { value: NoneWeaponCritic.NONE }
178178
},
179-
useCustomFormula: { value: false }
179+
useCustomFormula: { value: false },
180+
hotbarMacroCreatorId: 'weapon.attack'
180181
};
181182

182183
/** @type {import("../Items").WeaponItemConfig} */
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "Reset Apply physical mod",
3+
"type": "script",
4+
"command": "(async () => {\n const PHYSICAL_ATTRIBUTES = new Set([\n 'agility',\n 'dexterity',\n 'strength',\n 'constitution'\n ]);\n\n const getAttributeString = (node) => {\n const a = node?.attribute;\n if (typeof a === 'string') return a;\n if (a && typeof a === 'object' && typeof a.value === 'string') return a.value; // legacy\n return null;\n };\n\n const walk = (obj, pathParts, out) => {\n if (!obj || typeof obj !== 'object') return;\n\n // If this looks like an Ability-ish node (has applyPhysicalActionMod), patch it\n if (Object.prototype.hasOwnProperty.call(obj, 'applyPhysicalActionMod')) {\n const attr = getAttributeString(obj);\n const shouldBeTrue = attr != null && PHYSICAL_ATTRIBUTES.has(attr);\n\n const current = obj.applyPhysicalActionMod;\n if (current !== shouldBeTrue) {\n const fullPath = pathParts.join('.');\n out.push({ path: fullPath, value: shouldBeTrue });\n }\n }\n\n // Recurse\n for (const [k, v] of Object.entries(obj)) {\n if (v && typeof v === 'object') walk(v, [...pathParts, k], out);\n }\n };\n\n let totalChanges = 0;\n let touchedActors = 0;\n\n for (const actor of game.actors.contents) {\n const changes = [];\n walk(actor.system, ['system'], changes);\n\n if (!changes.length) continue;\n\n const updates = {};\n for (const c of changes) updates[`${c.path}.applyPhysicalActionMod`] = c.value;\n\n await actor.update(updates);\n\n totalChanges += changes.length;\n touchedActors += 1;\n\n console.log(`[ABF] Updated ${actor.name}:`, updates);\n }\n\n ui.notifications.info(\n `[ABF] applyPhysicalActionMod actualizado en ${touchedActors} actores (${totalChanges} cambios).`\n );\n})();",
5+
"img": "icons/svg/dice-target.svg",
6+
"author": "i22cqqk4LNN9HuHn",
7+
"scope": "global",
8+
"folder": null,
9+
"flags": {},
10+
"_stats": {
11+
"compendiumSource": null,
12+
"duplicateSource": null,
13+
"exportSource": null,
14+
"coreVersion": "13.345",
15+
"systemId": "animabf",
16+
"systemVersion": "2.2.1",
17+
"createdTime": 1772540419979,
18+
"modifiedTime": 1772540419979,
19+
"lastModifiedBy": "i22cqqk4LNN9HuHn"
20+
},
21+
"_id": "CFOQb99s9tGcrqAm",
22+
"sort": 0,
23+
"ownership": {
24+
"default": 0,
25+
"i22cqqk4LNN9HuHn": 3
26+
},
27+
"_key": "!macros!CFOQb99s9tGcrqAm"
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "Reset Apply physical mod [Selected]",
3+
"type": "script",
4+
"command": "(async () => {\n const PHYSICAL_ATTRIBUTES = new Set([\n 'agility',\n 'dexterity',\n 'strength',\n 'constitution'\n ]);\n\n const getAttributeString = (node) => {\n const a = node?.attribute;\n if (typeof a === 'string') return a;\n if (a && typeof a === 'object' && typeof a.value === 'string') return a.value; // legacy\n return null;\n };\n\n const walk = (obj, pathParts, out) => {\n if (!obj || typeof obj !== 'object') return;\n\n if (Object.prototype.hasOwnProperty.call(obj, 'applyPhysicalActionMod')) {\n const attr = getAttributeString(obj);\n const shouldBeTrue = attr != null && PHYSICAL_ATTRIBUTES.has(attr);\n\n if (obj.applyPhysicalActionMod !== shouldBeTrue) {\n out.push({ path: pathParts.join('.'), value: shouldBeTrue });\n }\n }\n\n for (const [k, v] of Object.entries(obj)) {\n if (v && typeof v === 'object') walk(v, [...pathParts, k], out);\n }\n };\n\n const actors = canvas?.tokens?.controlled\n ?.map(t => t.actor)\n .filter(a => a);\n\n if (!actors?.length) {\n ui.notifications.warn('[ABF] No hay tokens seleccionados.');\n return;\n }\n\n let totalChanges = 0;\n let touchedActors = 0;\n\n for (const actor of actors) {\n const changes = [];\n walk(actor.system, ['system'], changes);\n\n if (!changes.length) continue;\n\n const updates = {};\n for (const c of changes) updates[`${c.path}.applyPhysicalActionMod`] = c.value;\n\n await actor.update(updates);\n\n totalChanges += changes.length;\n touchedActors += 1;\n\n console.log(`[ABF] Updated ${actor.name}:`, updates);\n }\n\n ui.notifications.info(\n `[ABF] applyPhysicalActionMod actualizado en ${touchedActors} actores (${totalChanges} cambios).`\n );\n})();",
5+
"img": "icons/svg/dice-target.svg",
6+
"author": "i22cqqk4LNN9HuHn",
7+
"scope": "global",
8+
"folder": null,
9+
"flags": {},
10+
"_stats": {
11+
"compendiumSource": null,
12+
"duplicateSource": null,
13+
"exportSource": null,
14+
"coreVersion": "13.345",
15+
"systemId": "animabf",
16+
"systemVersion": "2.2.1",
17+
"createdTime": 1772540421579,
18+
"modifiedTime": 1772540421579,
19+
"lastModifiedBy": "i22cqqk4LNN9HuHn"
20+
},
21+
"_id": "Rbr4OknbfgprDoZl",
22+
"sort": 0,
23+
"ownership": {
24+
"default": 0,
25+
"i22cqqk4LNN9HuHn": 3
26+
},
27+
"_key": "!macros!Rbr4OknbfgprDoZl"
28+
}

src/system.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@
7474
"type": "Item",
7575
"private": false,
7676
"flags": {}
77+
},
78+
{
79+
"name": "macros",
80+
"label": "(ES) Macros",
81+
"type": "Macro",
82+
"path": "packs/macros.db",
83+
"system": "animabf",
84+
"private": false,
85+
"flags": {}
7786
}
7887
],
7988
"languages": [

src/template.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@
440440
"critic": { "value": "-" },
441441
"visible": false,
442442
"macro": "",
443+
"hotbarMacroCreatorId": "",
443444
"grades": {
444445
"base": {
445446
"name": { "value": "" },
@@ -580,6 +581,7 @@
580581
"discipline": { "value": "matrixPowers" },
581582
"critic": { "value": "-" },
582583
"macro": "",
584+
"hotbarMacroCreatorId": "",
583585
"hasMaintenance": { "value": false },
584586
"visible": false,
585587
"bonus": { "value": 0 }
@@ -659,7 +661,8 @@
659661
"hasOwnStr": { "value": false },
660662
"weaponStrength": { "base": { "value": 0 }, "final": { "value": 0 } },
661663
"critic": { "primary": { "value": "-" }, "secondary": { "value": "-" } },
662-
"useCustomFormula": {"value": false }
664+
"useCustomFormula": {"value": false },
665+
"hotbarMacroCreatorId": ""
663666
},
664667
"supernaturalShield": {
665668
"shieldPoints": { "value": 0 },

0 commit comments

Comments
 (0)