Skip to content

Commit 9937f29

Browse files
committed
ADD - Characteristics as types & modifier applying order
1 parent 2b48078 commit 9937f29

67 files changed

Lines changed: 2013 additions & 395 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"private": true,
33
"name": "anima-beyond-foundry",
4-
"version": "2.1.0",
4+
"version": "2.2.0",
55
"description": "Unofficial Anima Beyond Fantasy system for Foundry VTT",
66
"type": "module",
77
"scripts": {

src/animabf.mjs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { registerSettings, ABFSettingsKeys } from './utils/registerSettings';
2-
import { Logger, preloadTemplates } from './utils';
2+
import { Logger, TEMPLATE_PATHS } from './utils';
33
import ABFActorSheet from './module/actor/ABFActorSheet';
44
import ABFFoundryRoll from './module/rolls/ABFFoundryRoll';
55
import ABFCombat from './module/combat/ABFCombat';
@@ -21,7 +21,7 @@ import { preloadClickHandlers } from './module/actor/utils/createClickHandlers.j
2121

2222
import { ABFAttackData } from './module/combat/ABFAttackData.js';
2323
import { getChatContextMenuFactories } from './utils/buildChatContextMenu.js';
24-
import { Templates } from './module/utils/constants';
24+
import { Templates, HandlebarsPartials } from './module/utils/constants';
2525

2626
import './scss/animabf.scss';
2727

@@ -30,6 +30,8 @@ import { System, registerSystemOnGame } from './utils/systemMeta';
3030
import { resolveTokenName } from './utils/tokenName.js';
3131
import { FormulaEvaluator } from './utils/formulaEvaluator.js';
3232

33+
import { registerHandlebarsPartials } from './utils/handlebarsPartials.js';
34+
3335
/* ------------------------------------ */
3436
/* Initialize system */
3537
/* ------------------------------------ */
@@ -38,17 +40,18 @@ Hooks.once('init', async () => {
3840
registerSystemOnGame();
3941
Logger.log('Game Id:' + System.id);
4042

41-
// Preload Handlebars templates
42-
await preloadTemplates();
43+
window.ABFFoundryRoll = ABFFoundryRoll;
44+
CONFIG.Dice.rolls = [ABFFoundryRoll, ...CONFIG.Dice.rolls];
45+
46+
// Load Handlebars templates
47+
await loadTemplates(TEMPLATE_PATHS);
48+
await registerHandlebarsPartials(HandlebarsPartials);
4349

4450
// Assign custom classes and constants here
4551
CONFIG.Actor.documentClass = ABFActor;
4652

4753
CONFIG.config = ABFConfig;
4854

49-
window.ABFFoundryRoll = ABFFoundryRoll;
50-
CONFIG.Dice.rolls = [ABFFoundryRoll, ...CONFIG.Dice.rolls];
51-
5255
CONFIG.Combat.documentClass = ABFCombat;
5356
CONFIG.Combatant.documentClass = ABFCombatant;
5457

src/lang/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@
461461
"anima.ui.resistances.poison": "Poison",
462462
"anima.ui.resistances.psychic": "Psychic",
463463
"anima.ui.resistances.title": "Resistances",
464+
"anima.ui.characteristics.title": "Characteristics",
464465
"anima.ui.secondaries.acrobatics.title": "Acrobatics",
465466
"anima.ui.secondaries.alchemy.title": "Alchemy",
466467
"anima.ui.secondaries.animals.title": "Animals",
@@ -819,6 +820,7 @@
819820
"anima.ui.tooltips.base": "Final",
820821
"anima.ui.tooltips.special": "Special",
821822
"anima.ui.tooltips.final": "Final",
823+
"anima.ui.tooltips.mod": "Modifier",
822824
"keyBindings.damageCalculator.name": "Damage calculator",
823825
"keyBindings.damageCalculator.hint": "Open damage calculator",
824826
"keyBindings.sendAttack.name": "Send Attack",

src/lang/es.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@
463463
"anima.ui.resistances.poison": "Venenos",
464464
"anima.ui.resistances.psychic": "Psíquica",
465465
"anima.ui.resistances.title": "Resistencias",
466+
"anima.ui.characteristics.title": "Características",
466467
"anima.ui.secondaries.acrobatics.title": "Acrobacias",
467468
"anima.ui.secondaries.alchemy.title": "Alquimia",
468469
"anima.ui.secondaries.animals.title": "Animales",
@@ -821,6 +822,7 @@
821822
"anima.ui.tooltips.base": "Final",
822823
"anima.ui.tooltips.special": "Especial",
823824
"anima.ui.tooltips.final": "Final",
825+
"anima.ui.tooltips.mod": "Modificador",
824826
"keyBindings.damageCalculator.name": "Calculadora de daño",
825827
"keyBindings.damageCalculator.hint": "Abre la calculadora de daño.",
826828
"keyBindings.sendAttack.name": "Enviar ataque",

src/lang/fr.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@
434434
"anima.ui.resistances.poison": "Poisons",
435435
"anima.ui.resistances.psychic": "Psychique",
436436
"anima.ui.resistances.title": "Résistances",
437+
"anima.ui.characteristics.title": "Characteristics",
437438
"anima.ui.secondaries.acrobatics.title": "Acrobaties",
438439
"anima.ui.secondaries.alchemy.title": "Alchimie",
439440
"anima.ui.secondaries.animals.title": "Animaux",
@@ -539,6 +540,7 @@
539540
"anima.ui.tooltips.base": "Final",
540541
"anima.ui.tooltips.final": "Final",
541542
"anima.ui.tooltips.special": "Spécial",
543+
"anima.ui.tooltips.mod": "Modifier",
542544
"anima.ui.weapons.different.title": "Différent / Mains nues",
543545
"anima.ui.weapons.known.title": "Connu",
544546
"anima.ui.weapons.mixed.title": "1 ou 2 mains",

src/module/actor/ABFActor.js

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import ABFFoundryRoll from '../rolls/ABFFoundryRoll';
2222
import { openModDialog } from '../utils/dialogs/openSimpleInputDialog';
2323
import ABFItem from '../items/ABFItem';
2424
import { FormulaEvaluator } from '../../utils/formulaEvaluator.js';
25+
import { inflateSystemFromTypeMarkers } from './types/inflateSystemFromTypeMarkers.js';
26+
import { TYPED_PATHS } from './types/typedTemplateIndex.js';
27+
import { buildTypedNodes } from './types/runtimeTypedNodes.js';
2528

2629
export class ABFActor extends Actor {
2730
i18n = game.i18n;
@@ -42,14 +45,31 @@ export class ABFActor extends Actor {
4245
}
4346
}
4447

48+
async _preCreate(data, options, user) {
49+
// Ensure system exists + template defaults
50+
data.system = foundry.utils.mergeObject(data.system ?? {}, INITIAL_ACTOR_DATA, {
51+
overwrite: false
52+
});
53+
54+
// Inflate typed nodes from __type and remove markers
55+
data.system = inflateSystemFromTypeMarkers(data.system);
56+
57+
return super._preCreate(data, options, user);
58+
}
59+
4560
async prepareDerivedData() {
4661
super.prepareDerivedData();
4762

48-
this.system = foundry.utils.mergeObject(this.system, INITIAL_ACTOR_DATA, {
49-
overwrite: false
50-
});
63+
// this.system = foundry.utils.mergeObject(this.system, INITIAL_ACTOR_DATA, {
64+
// overwrite: false
65+
// });
66+
67+
// Safety: if some actor came without inflation (old data / imports)
68+
this.system = inflateSystemFromTypeMarkers(this.system);
5169

70+
buildTypedNodes(this, TYPED_PATHS);
5271
await prepareActor(this);
72+
// applyTypedDerived(this);
5373
}
5474

5575
getRollData() {
@@ -871,40 +891,10 @@ export class ABFActor extends Actor {
871891
getItem(itemId) {
872892
return this.getEmbeddedDocument('Item', itemId);
873893
}
874-
875894
applyActiveEffects() {
876-
const originals = new Map();
877-
878-
try {
879-
for (const effect of this.effects.contents) {
880-
if (!effect.active) continue;
881-
882-
const changes = effect.changes;
883-
if (!Array.isArray(changes) || changes.length === 0) continue;
884-
885-
// Save originals
886-
originals.set(
887-
effect,
888-
changes.map(c => c.value)
889-
);
890-
891-
// Patch values (in-memory only)
892-
for (const change of changes) {
893-
change.value = this._applyDynamicEffectValue(change.value);
894-
}
895-
}
896-
897-
// Let Foundry core do the real AE logic (modes, priority, etc.)
898-
return super.applyActiveEffects();
899-
} finally {
900-
// Restore original values so nothing "sticks" on the document
901-
for (const [effect, values] of originals.entries()) {
902-
const changes = effect.changes;
903-
for (let i = 0; i < changes.length; i++) {
904-
changes[i].value = values[i];
905-
}
906-
}
907-
}
895+
// Active Effects are applied in prepareActor using the flow ordering (per-change).
896+
// We keep _applyDynamicEffectValue for evaluating dynamic change values.
897+
return this;
908898
}
909899

910900
_applyDynamicEffectValue(value) {

src/module/actor/ABFActorSheet.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default class ABFActorSheet extends ActorSheet {
3131
...{
3232
classes: [game.animabf.id, 'sheet', 'actor'],
3333
template: 'systems/animabf/templates/actor/actor-sheet.hbs',
34-
width: 1000,
34+
width: 1100,
3535
height: 850,
3636
submitOnChange: true,
3737
viewPermission: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER,
@@ -76,7 +76,7 @@ export default class ABFActorSheet extends ActorSheet {
7676
return 1300;
7777
}
7878

79-
return 1000;
79+
return 1100;
8080
}
8181

8282
async _render(force, options = {}) {

src/module/actor/constants.js

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -183,36 +183,28 @@ export const INITIAL_ACTOR_DATA = {
183183
characteristics: {
184184
primaries: {
185185
agility: {
186-
value: 0,
187-
mod: 0
186+
__type: '{"type":"Characteristic"}'
188187
},
189188
constitution: {
190-
value: 0,
191-
mod: 0
189+
__type: '{"type":"Characteristic"}'
192190
},
193191
dexterity: {
194-
value: 0,
195-
mod: 0
192+
__type: '{"type":"Characteristic"}'
196193
},
197194
strength: {
198-
value: 0,
199-
mod: 0
195+
__type: '{"type":"Characteristic"}'
200196
},
201197
intelligence: {
202-
value: 0,
203-
mod: 0
198+
__type: '{"type":"Characteristic"}'
204199
},
205200
perception: {
206-
value: 0,
207-
mod: 0
201+
__type: '{"type":"Characteristic"}'
208202
},
209203
power: {
210-
value: 0,
211-
mod: 0
204+
__type: '{"type":"Characteristic"}'
212205
},
213206
willPower: {
214-
value: 0,
215-
mod: 0
207+
__type: '{"type":"Characteristic"}'
216208
}
217209
},
218210
secondaries: {

src/module/actor/types/BaseType.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
export class BaseType {
2+
static type = 'BaseType';
3+
4+
constructor(actor, systemPath) {
5+
if (new.target === BaseType) {
6+
throw new Error('BaseType is abstract and cannot be instantiated directly.');
7+
}
8+
9+
this.actor = actor;
10+
this.systemPath = systemPath;
11+
}
12+
13+
_get(relPath, fallback = undefined) {
14+
return (
15+
foundry.utils.getProperty(this.actor, `${this.systemPath}.${relPath}`) ?? fallback
16+
);
17+
}
18+
19+
_set(relPath, value) {
20+
foundry.utils.setProperty(this.actor, `${this.systemPath}.${relPath}`, value);
21+
}
22+
23+
/**
24+
* Returns derived calculation specs for effectFlow.
25+
* Subclasses should override and may return multiple specs (e.g. "final", "mod").
26+
*
27+
* - deps/mods are RELATIVE to this.systemPath (e.g. "base.value")
28+
* - compute must be a pure function (no side-effects) that returns an object
29+
* with keys matching the logical fields written by mods (e.g. { final: 10 }).
30+
*
31+
* @returns {Array<{
32+
* id: string,
33+
* deps: string[],
34+
* mods: string[],
35+
* compute: (inputs: Record<string, any>) => Record<string, any>
36+
* }>}
37+
*/
38+
getDerivedFlowSpecs() {
39+
return [];
40+
}
41+
42+
// Optionally keep applyDerived temporarily (legacy)
43+
applyDerived() {
44+
throw new Error(`${this.constructor.name}.applyDerived() not implemented.`);
45+
}
46+
47+
/** Structure defaults used for migrations */
48+
static defaults() {
49+
return {};
50+
}
51+
52+
static normalizeInflateInput(node) {
53+
// Default: no-op
54+
return node;
55+
}
56+
57+
static inflate(overrides, nodeWithoutMarker) {
58+
const combined = foundry.utils.mergeObject(overrides ?? {}, nodeWithoutMarker ?? {}, {
59+
inplace: false,
60+
insertKeys: true,
61+
insertValues: true
62+
});
63+
64+
const normalized = this.normalizeInflateInput(combined);
65+
66+
const merged = foundry.utils.mergeObject(this.defaults(), normalized, {
67+
inplace: false,
68+
insertKeys: true,
69+
insertValues: true
70+
});
71+
72+
// IMPORTANT: remove legacy keys like "value" or "__type"
73+
return this.pruneToDefaults(merged);
74+
}
75+
76+
/**
77+
* Remove keys that are not part of this type's default shape.
78+
* Mutates and returns the same object.
79+
* @param {object} obj
80+
* @returns {object}
81+
*/
82+
static pruneToDefaults(obj) {
83+
if (!obj || typeof obj !== 'object') return obj;
84+
85+
const allowed = new Set(Object.keys(this.defaults()));
86+
for (const k of Object.keys(obj)) {
87+
if (!allowed.has(k)) delete obj[k];
88+
}
89+
return obj;
90+
}
91+
92+
/**
93+
* Convenience: merge defaults + data, then prune to defaults shape.
94+
* Returns a new object.
95+
* @param {object} data
96+
* @returns {object}
97+
*/
98+
static sanitize(data) {
99+
const merged = foundry.utils.mergeObject(this.defaults(), data ?? {}, {
100+
inplace: false,
101+
insertKeys: true,
102+
insertValues: true
103+
});
104+
return this.pruneToDefaults(merged);
105+
}
106+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export class TypeRegistry {
2+
static #byType = new Map(); // type -> ctor
3+
4+
static get size() {
5+
return this.#byType.size;
6+
}
7+
8+
static register(ctor) {
9+
this.#byType.set(ctor.type, ctor);
10+
}
11+
12+
static get(type) {
13+
return this.#byType.get(type) ?? null;
14+
}
15+
16+
static create(type, actor, systemPath) {
17+
const ctor = this.get(type);
18+
if (!ctor) throw new Error(`Unknown type: ${type}`);
19+
return new ctor(actor, systemPath);
20+
}
21+
22+
static defaultsFor(type) {
23+
const ctor = this.get(type);
24+
if (!ctor) throw new Error(`Unknown type: ${type}`);
25+
return ctor.defaults();
26+
}
27+
}

0 commit comments

Comments
 (0)