intelligent-system-design-language 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/langium-language-designer.md +38 -0
- package/.claude/agents/typescript-vscode-expert.md +29 -0
- package/.claude/agents/ui-ux-designer.md +36 -0
- package/.claude/settings.local.json +33 -0
- package/.idea/inspectionProfiles/Project_Default.xml +7 -0
- package/.idea/isdl.iml +14 -0
- package/.idea/modules.xml +9 -0
- package/.idea/vcs.xml +7 -0
- package/.idea/watcherTasks.xml +4 -0
- package/.vscodeignore +18 -0
- package/LICENSE +674 -0
- package/README.md +86 -0
- package/bin/cli.js +4 -0
- package/bin/lsp.js +8 -0
- package/isdl.png +0 -0
- package/out/_backgrounds.scss +91 -0
- package/out/_handlebars.scss +505 -0
- package/out/_isdlStyles.scss +1357 -0
- package/out/_vuetifyOverrides.scss +425 -0
- package/out/_vuetifyStyles.scss +31957 -0
- package/out/cli/cli-util.js +39 -0
- package/out/cli/cli-util.js.map +1 -0
- package/out/cli/components/_backgrounds.scss +91 -0
- package/out/cli/components/_handlebars.scss +505 -0
- package/out/cli/components/_isdlStyles.scss +1357 -0
- package/out/cli/components/_vuetifyOverrides.scss +425 -0
- package/out/cli/components/_vuetifyStyles.scss +31957 -0
- package/out/cli/components/active-effect-sheet-generator.js +643 -0
- package/out/cli/components/active-effect-sheet-generator.js.map +1 -0
- package/out/cli/components/base-actor-sheet-generator.js +125 -0
- package/out/cli/components/base-actor-sheet-generator.js.map +1 -0
- package/out/cli/components/base-sheet-generator.js +525 -0
- package/out/cli/components/base-sheet-generator.js.map +1 -0
- package/out/cli/components/chat-card-generator.js +683 -0
- package/out/cli/components/chat-card-generator.js.map +1 -0
- package/out/cli/components/css-generator.js +58 -0
- package/out/cli/components/css-generator.js.map +1 -0
- package/out/cli/components/damage-roll-generator.js +173 -0
- package/out/cli/components/damage-roll-generator.js.map +1 -0
- package/out/cli/components/datamodel-generator.js +672 -0
- package/out/cli/components/datamodel-generator.js.map +1 -0
- package/out/cli/components/derived-data-generator.js +1340 -0
- package/out/cli/components/derived-data-generator.js.map +1 -0
- package/out/cli/components/hotbar-drop-hook-generator.js +95 -0
- package/out/cli/components/hotbar-drop-hook-generator.js.map +1 -0
- package/out/cli/components/init-hook-generator.js +597 -0
- package/out/cli/components/init-hook-generator.js.map +1 -0
- package/out/cli/components/keywords-generator.js +220 -0
- package/out/cli/components/keywords-generator.js.map +1 -0
- package/out/cli/components/language-generator.js +110 -0
- package/out/cli/components/language-generator.js.map +1 -0
- package/out/cli/components/measured-template-preview.js +234 -0
- package/out/cli/components/measured-template-preview.js.map +1 -0
- package/out/cli/components/method-generator.js +1812 -0
- package/out/cli/components/method-generator.js.map +1 -0
- package/out/cli/components/ready-hook-generator.js +448 -0
- package/out/cli/components/ready-hook-generator.js.map +1 -0
- package/out/cli/components/token-generator.js +138 -0
- package/out/cli/components/token-generator.js.map +1 -0
- package/out/cli/components/utils.js +176 -0
- package/out/cli/components/utils.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-attribute.js +148 -0
- package/out/cli/components/vue/base-components/vue-attribute.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-boolean.js +77 -0
- package/out/cli/components/vue/base-components/vue-boolean.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-calculator.js +106 -0
- package/out/cli/components/vue/base-components/vue-calculator.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-damage-application.js +369 -0
- package/out/cli/components/vue/base-components/vue-damage-application.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-damage-bonuses.js +225 -0
- package/out/cli/components/vue/base-components/vue-damage-bonuses.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-damage-resistances.js +256 -0
- package/out/cli/components/vue/base-components/vue-damage-resistances.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-damage-track.js +134 -0
- package/out/cli/components/vue/base-components/vue-damage-track.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-date-time.js +55 -0
- package/out/cli/components/vue/base-components/vue-date-time.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-dice.js +111 -0
- package/out/cli/components/vue/base-components/vue-dice.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-die.js +86 -0
- package/out/cli/components/vue/base-components/vue-die.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-document-choice.js +172 -0
- package/out/cli/components/vue/base-components/vue-document-choice.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-document-choices.js +203 -0
- package/out/cli/components/vue/base-components/vue-document-choices.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-document-link.js +73 -0
- package/out/cli/components/vue/base-components/vue-document-link.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-extended-choice.js +101 -0
- package/out/cli/components/vue/base-components/vue-extended-choice.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-inventory.js +532 -0
- package/out/cli/components/vue/base-components/vue-inventory.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-macro-choice.js +150 -0
- package/out/cli/components/vue/base-components/vue-macro-choice.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-measured-template.js +543 -0
- package/out/cli/components/vue/base-components/vue-measured-template.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-money.js +496 -0
- package/out/cli/components/vue/base-components/vue-money.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-number.js +184 -0
- package/out/cli/components/vue/base-components/vue-number.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-paperdoll.js +56 -0
- package/out/cli/components/vue/base-components/vue-paperdoll.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-parent-property-reference.js +89 -0
- package/out/cli/components/vue/base-components/vue-parent-property-reference.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-prosemirror.js +31 -0
- package/out/cli/components/vue/base-components/vue-prosemirror.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-resource.js +149 -0
- package/out/cli/components/vue/base-components/vue-resource.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-roll-visualizer.js +121 -0
- package/out/cli/components/vue/base-components/vue-roll-visualizer.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-self-property-reference.js +75 -0
- package/out/cli/components/vue/base-components/vue-self-property-reference.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-string-choice.js +111 -0
- package/out/cli/components/vue/base-components/vue-string-choice.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-string-choices.js +216 -0
- package/out/cli/components/vue/base-components/vue-string-choices.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-string.js +73 -0
- package/out/cli/components/vue/base-components/vue-string.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-text-field.js +66 -0
- package/out/cli/components/vue/base-components/vue-text-field.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-tracker.js +444 -0
- package/out/cli/components/vue/base-components/vue-tracker.js.map +1 -0
- package/out/cli/components/vue/vue-action-component-generator.js +88 -0
- package/out/cli/components/vue/vue-action-component-generator.js.map +1 -0
- package/out/cli/components/vue/vue-active-effect-sheet-generator.js +1016 -0
- package/out/cli/components/vue/vue-active-effect-sheet-generator.js.map +1 -0
- package/out/cli/components/vue/vue-base-components-generator.js +59 -0
- package/out/cli/components/vue/vue-base-components-generator.js.map +1 -0
- package/out/cli/components/vue/vue-datatable-component-generator.js +307 -0
- package/out/cli/components/vue/vue-datatable-component-generator.js.map +1 -0
- package/out/cli/components/vue/vue-datatable-sheet-class-generator.js +342 -0
- package/out/cli/components/vue/vue-datatable-sheet-class-generator.js.map +1 -0
- package/out/cli/components/vue/vue-datatable2-component-generator.js +939 -0
- package/out/cli/components/vue/vue-datatable2-component-generator.js.map +1 -0
- package/out/cli/components/vue/vue-document-creation-app.js +140 -0
- package/out/cli/components/vue/vue-document-creation-app.js.map +1 -0
- package/out/cli/components/vue/vue-document-creation-sheet.js +105 -0
- package/out/cli/components/vue/vue-document-creation-sheet.js.map +1 -0
- package/out/cli/components/vue/vue-generator.js +240 -0
- package/out/cli/components/vue/vue-generator.js.map +1 -0
- package/out/cli/components/vue/vue-mixin.js +338 -0
- package/out/cli/components/vue/vue-mixin.js.map +1 -0
- package/out/cli/components/vue/vue-pinned-datatable-component-generator.js +306 -0
- package/out/cli/components/vue/vue-pinned-datatable-component-generator.js.map +1 -0
- package/out/cli/components/vue/vue-prompt-generator.js +201 -0
- package/out/cli/components/vue/vue-prompt-generator.js.map +1 -0
- package/out/cli/components/vue/vue-prompt-sheet-class-generator.js +252 -0
- package/out/cli/components/vue/vue-prompt-sheet-class-generator.js.map +1 -0
- package/out/cli/components/vue/vue-sheet-application-generator.js +2008 -0
- package/out/cli/components/vue/vue-sheet-application-generator.js.map +1 -0
- package/out/cli/components/vue/vue-sheet-class-generator.js +484 -0
- package/out/cli/components/vue/vue-sheet-class-generator.js.map +1 -0
- package/out/cli/generator.js +659 -0
- package/out/cli/generator.js.map +1 -0
- package/out/cli/main.js +43 -0
- package/out/cli/main.js.map +1 -0
- package/out/datatables.min.css +54 -0
- package/out/datatables.min.js +178 -0
- package/out/extension/github/githubAuthProvider.js +345 -0
- package/out/extension/github/githubAuthProvider.js.map +1 -0
- package/out/extension/github/githubConfig.js +132 -0
- package/out/extension/github/githubConfig.js.map +1 -0
- package/out/extension/github/githubGistActions.js +251 -0
- package/out/extension/github/githubGistActions.js.map +1 -0
- package/out/extension/github/githubGistManager.js +255 -0
- package/out/extension/github/githubGistManager.js.map +1 -0
- package/out/extension/github/githubManager.js +1735 -0
- package/out/extension/github/githubManager.js.map +1 -0
- package/out/extension/github/githubQuickActions.js +659 -0
- package/out/extension/github/githubQuickActions.js.map +1 -0
- package/out/extension/github/githubTreeProvider.js +181 -0
- package/out/extension/github/githubTreeProvider.js.map +1 -0
- package/out/extension/github/system-workflow.yml +48 -0
- package/out/extension/main.cjs +70315 -0
- package/out/extension/main.cjs.map +7 -0
- package/out/extension/main.js +237 -0
- package/out/extension/main.js.map +1 -0
- package/out/extension/package.json +426 -0
- package/out/isdl.png +0 -0
- package/out/language/generated/ast.js +2992 -0
- package/out/language/generated/ast.js.map +1 -0
- package/out/language/generated/grammar.js +13970 -0
- package/out/language/generated/grammar.js.map +1 -0
- package/out/language/generated/module.js +20 -0
- package/out/language/generated/module.js.map +1 -0
- package/out/language/intelligent-system-design-language-formatter.js +85 -0
- package/out/language/intelligent-system-design-language-formatter.js.map +1 -0
- package/out/language/intelligent-system-design-language-module.js +69 -0
- package/out/language/intelligent-system-design-language-module.js.map +1 -0
- package/out/language/intelligent-system-design-language-quickfixes.js +37 -0
- package/out/language/intelligent-system-design-language-quickfixes.js.map +1 -0
- package/out/language/intelligent-system-design-language-validator.js +515 -0
- package/out/language/intelligent-system-design-language-validator.js.map +1 -0
- package/out/language/isdl-hover-provider.js +77 -0
- package/out/language/isdl-hover-provider.js.map +1 -0
- package/out/language/isdl-scope-provider.js +149 -0
- package/out/language/isdl-scope-provider.js.map +1 -0
- package/out/language/main.cjs +47655 -0
- package/out/language/main.cjs.map +7 -0
- package/out/language/main.js +11 -0
- package/out/language/main.js.map +1 -0
- package/out/missing-character.png +0 -0
- package/out/package.json +426 -0
- package/out/paperdoll_default.png +0 -0
- package/out/progressbar.min.js +7 -0
- package/out/styles.scss +722 -0
- package/out/test/formatting/formatter.test.js +46 -0
- package/out/test/formatting/formatter.test.js.map +1 -0
- package/out/vuetify.esm.js +30279 -0
- package/package.json +426 -0
|
@@ -0,0 +1,1340 @@
|
|
|
1
|
+
import { isStringExp, isStringParamValue, isInitiativeProperty, isAttributeParamMod, isHookHandler, isTrackerExp, isLayout, isMeasuredTemplateField, isTableField, isAccess, isDiceField, isMoneyField } from '../../language/generated/ast.js';
|
|
2
|
+
import { isActor, isItem, isResourceExp, isAttributeExp, isMethodBlock, isNumberExp, isNumberParamMax, isNumberParamValue, isNumberParamMin, isWhereParam, isDocument, } from "../../language/generated/ast.js";
|
|
3
|
+
import { expandToNode, joinToNode, toString } from 'langium/generate';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { translateBodyExpressionToJavascript, translateExpression } from './method-generator.js';
|
|
7
|
+
import { getAllOfType } from './utils.js';
|
|
8
|
+
export function generateExtendedDocumentClasses(entry, id, destination) {
|
|
9
|
+
const generatedFileDir = path.join(destination, "system", "documents");
|
|
10
|
+
if (!fs.existsSync(generatedFileDir)) {
|
|
11
|
+
fs.mkdirSync(generatedFileDir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
function generateExtendedDocumentClass(type, entry) {
|
|
14
|
+
function translateMethodOrValueOrStored(property, param) {
|
|
15
|
+
if (param == undefined) {
|
|
16
|
+
return expandToNode `
|
|
17
|
+
return system.${property.name.toLowerCase()} ?? 0
|
|
18
|
+
`;
|
|
19
|
+
}
|
|
20
|
+
if (isMethodBlock(param.value)) {
|
|
21
|
+
if (isNumberParamValue(param)) {
|
|
22
|
+
toBeReapplied.add("system." + property.name.toLowerCase());
|
|
23
|
+
}
|
|
24
|
+
return expandToNode `
|
|
25
|
+
${translateExpression(entry, id, param.value, true, property)}
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
return expandToNode `
|
|
29
|
+
return ${param.value}
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
const generatedFilePath = path.join(generatedFileDir, `${type.toLowerCase()}.mjs`);
|
|
33
|
+
const toBeReapplied = new Set();
|
|
34
|
+
function generateDerivedAttribute(property) {
|
|
35
|
+
var _a, _b;
|
|
36
|
+
if (isLayout(property)) {
|
|
37
|
+
return joinToNode(property.body, property => generateDerivedAttribute(property), { appendNewLineIfNotEmpty: true });
|
|
38
|
+
}
|
|
39
|
+
if (isStringExp(property)) {
|
|
40
|
+
let stringValue = property.params.find(p => isStringParamValue(p));
|
|
41
|
+
if (stringValue != undefined) {
|
|
42
|
+
if (stringValue.value == "")
|
|
43
|
+
return;
|
|
44
|
+
if (typeof stringValue.value == "string") {
|
|
45
|
+
return expandToNode `
|
|
46
|
+
// ${property.name} String Derived Data
|
|
47
|
+
this.system.${property.name.toLowerCase()} = "${stringValue.value}";
|
|
48
|
+
`.appendNewLineIfNotEmpty();
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
return expandToNode `
|
|
52
|
+
// ${property.name} String Derived Data
|
|
53
|
+
const ${property.name.toLowerCase()}CurrentValueFunc = (system) => {
|
|
54
|
+
const context = {
|
|
55
|
+
object: this
|
|
56
|
+
};
|
|
57
|
+
${translateExpression(entry, id, stringValue.value, true, property)}
|
|
58
|
+
};
|
|
59
|
+
this.system.${property.name.toLowerCase()} = ${property.name.toLowerCase()}CurrentValueFunc(this.system);
|
|
60
|
+
`.appendNewLineIfNotEmpty();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (isNumberExp(property)) {
|
|
65
|
+
const valueParam = property.params.find(p => isNumberParamValue(p));
|
|
66
|
+
const minParam = property.params.find(p => isNumberParamMin(p));
|
|
67
|
+
const maxParam = property.params.find(p => isNumberParamMax(p));
|
|
68
|
+
if (valueParam) {
|
|
69
|
+
return expandToNode `
|
|
70
|
+
// ${property.name} Number Calculated Data
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
${minParam != undefined ? expandToNode `
|
|
74
|
+
const ${property.name.toLowerCase()}MinFunc = (system) => {
|
|
75
|
+
const context = {
|
|
76
|
+
object: this
|
|
77
|
+
};
|
|
78
|
+
${translateMethodOrValueOrStored(property, minParam)}
|
|
79
|
+
};
|
|
80
|
+
let ${property.name.toLowerCase()}Min = ${property.name.toLowerCase()}MinFunc(this.system);
|
|
81
|
+
if (isNaN(${property.name.toLowerCase()}Min) || ${property.name.toLowerCase()}Min === undefined || ${property.name.toLowerCase()}Min === null) {
|
|
82
|
+
${property.name.toLowerCase()}Min = 0;
|
|
83
|
+
}
|
|
84
|
+
`.appendNewLine() : ""}
|
|
85
|
+
|
|
86
|
+
${maxParam != undefined ? expandToNode `
|
|
87
|
+
const ${property.name.toLowerCase()}MaxFunc = (system) => {
|
|
88
|
+
const context = {
|
|
89
|
+
object: this
|
|
90
|
+
};
|
|
91
|
+
${translateMethodOrValueOrStored(property, maxParam)}
|
|
92
|
+
};
|
|
93
|
+
let ${property.name.toLowerCase()}Max = ${property.name.toLowerCase()}MaxFunc(this.system);
|
|
94
|
+
if (isNaN(${property.name.toLowerCase()}Max) || ${property.name.toLowerCase()}Max === undefined || ${property.name.toLowerCase()}Max === null) {
|
|
95
|
+
${property.name.toLowerCase()}Max = 0;
|
|
96
|
+
}
|
|
97
|
+
`.appendNewLine() : ""}
|
|
98
|
+
|
|
99
|
+
// ${property.name} Number Derived Data
|
|
100
|
+
const ${property.name.toLowerCase()}CurrentValueFunc = (system) => {
|
|
101
|
+
const context = {
|
|
102
|
+
object: this
|
|
103
|
+
};
|
|
104
|
+
${translateMethodOrValueOrStored(property, valueParam)}
|
|
105
|
+
};
|
|
106
|
+
Object.defineProperty(this.system, "${property.name.toLowerCase()}", {
|
|
107
|
+
get: () => {
|
|
108
|
+
let current = ${property.name.toLowerCase()}CurrentValueFunc(this.system);
|
|
109
|
+
// Protect against NaN from invalid operations (e.g., "A" + 2)
|
|
110
|
+
if (isNaN(current) || current === undefined || current === null) {
|
|
111
|
+
current = 0;
|
|
112
|
+
}
|
|
113
|
+
${minParam != undefined ? expandToNode `
|
|
114
|
+
if ( current < ${property.name.toLowerCase()}Min ) {
|
|
115
|
+
current = ${property.name.toLowerCase()}Min;
|
|
116
|
+
}
|
|
117
|
+
`.appendNewLine() : ""}
|
|
118
|
+
${maxParam != undefined ? expandToNode `
|
|
119
|
+
if ( current > ${property.name.toLowerCase()}Max ) {
|
|
120
|
+
current = ${property.name.toLowerCase()}Max;
|
|
121
|
+
}
|
|
122
|
+
`.appendNewLine() : ""}
|
|
123
|
+
return current;
|
|
124
|
+
},
|
|
125
|
+
configurable: true
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
`.appendNewLineIfNotEmpty();
|
|
129
|
+
}
|
|
130
|
+
return expandToNode `
|
|
131
|
+
// ${property.name} Number Derived Data
|
|
132
|
+
const ${property.name.toLowerCase()}CurrentValueFunc = (system) => {
|
|
133
|
+
const context = {
|
|
134
|
+
object: this
|
|
135
|
+
};
|
|
136
|
+
${translateMethodOrValueOrStored(property, valueParam)}
|
|
137
|
+
};
|
|
138
|
+
this.system.${property.name.toLowerCase()} = ${property.name.toLowerCase()}CurrentValueFunc(this.system);
|
|
139
|
+
|
|
140
|
+
${minParam != undefined ? expandToNode `
|
|
141
|
+
const ${property.name.toLowerCase()}MinFunc = (system) => {
|
|
142
|
+
const context = {
|
|
143
|
+
object: this
|
|
144
|
+
};
|
|
145
|
+
${translateMethodOrValueOrStored(property, minParam)}
|
|
146
|
+
};
|
|
147
|
+
const ${property.name.toLowerCase()}Min = ${property.name.toLowerCase()}MinFunc(this.system);
|
|
148
|
+
if ( this.system.${property.name.toLowerCase()} < ${property.name.toLowerCase()}Min ) {
|
|
149
|
+
this.system.${property.name.toLowerCase()} = ${property.name.toLowerCase()}Min;
|
|
150
|
+
}
|
|
151
|
+
`.appendNewLine() : ""}
|
|
152
|
+
|
|
153
|
+
${maxParam != undefined ? expandToNode `
|
|
154
|
+
const ${property.name.toLowerCase()}MaxFunc = (system) => {
|
|
155
|
+
const context = {
|
|
156
|
+
object: this
|
|
157
|
+
};
|
|
158
|
+
${translateMethodOrValueOrStored(property, maxParam)}
|
|
159
|
+
};
|
|
160
|
+
const ${property.name.toLowerCase()}Max = ${property.name.toLowerCase()}MaxFunc(this.system);
|
|
161
|
+
if ( this.system.${property.name.toLowerCase()} > ${property.name.toLowerCase()}Max ) {
|
|
162
|
+
this.system.${property.name.toLowerCase()} = ${property.name.toLowerCase()}Max;
|
|
163
|
+
}
|
|
164
|
+
`.appendNewLine() : ""}
|
|
165
|
+
`.appendNewLineIfNotEmpty();
|
|
166
|
+
}
|
|
167
|
+
if (isMoneyField(property)) {
|
|
168
|
+
const valueParam = property.params.find(p => isNumberParamValue(p));
|
|
169
|
+
if (valueParam) {
|
|
170
|
+
// Check if this money field has denominations
|
|
171
|
+
if (property.denominations && property.denominations.length > 0) {
|
|
172
|
+
// Cannot use value param with multi-denomination money
|
|
173
|
+
console.warn(`Money field ${property.name} has both denominations and value parameter. Value parameter will be ignored for multi-denomination money.`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
return expandToNode `
|
|
177
|
+
// ${property.name} Money Calculated Data
|
|
178
|
+
const ${property.name.toLowerCase()}CurrentValueFunc = (system) => {
|
|
179
|
+
const context = {
|
|
180
|
+
object: this
|
|
181
|
+
};
|
|
182
|
+
${translateMethodOrValueOrStored(property, valueParam)}
|
|
183
|
+
};
|
|
184
|
+
Object.defineProperty(this.system, "${property.name.toLowerCase()}", {
|
|
185
|
+
get: () => {
|
|
186
|
+
return ${property.name.toLowerCase()}CurrentValueFunc(this.system);
|
|
187
|
+
},
|
|
188
|
+
configurable: true
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
`.appendNewLineIfNotEmpty();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (isAttributeExp(property)) {
|
|
195
|
+
console.log("Processing Derived Attribute: " + property.name);
|
|
196
|
+
const modParam = property.params.find(p => isAttributeParamMod(p));
|
|
197
|
+
return expandToNode `
|
|
198
|
+
|
|
199
|
+
// ${property.name} Attribute Derived Data
|
|
200
|
+
const ${property.name.toLowerCase()}CurrentValue = this.system.${property.name.toLowerCase()}?.value ?? 0;
|
|
201
|
+
const ${property.name.toLowerCase()}CurrentMax = this.system.${property.name.toLowerCase()}?.max ?? 0;
|
|
202
|
+
const ${property.name.toLowerCase()}ModFunc = (system) => {
|
|
203
|
+
const context = {
|
|
204
|
+
object: this
|
|
205
|
+
};
|
|
206
|
+
${modParam ? translateExpression(entry, id, modParam.method, true, property) : `return ${property.name.toLowerCase()}CurrentValue`}
|
|
207
|
+
};
|
|
208
|
+
let ${property.name.toLowerCase()}Mod = ${property.name.toLowerCase()}ModFunc(this.system);
|
|
209
|
+
// Protect against NaN from invalid operations
|
|
210
|
+
if (isNaN(${property.name.toLowerCase()}Mod) || ${property.name.toLowerCase()}Mod === undefined || ${property.name.toLowerCase()}Mod === null) {
|
|
211
|
+
${property.name.toLowerCase()}Mod = 0;
|
|
212
|
+
}
|
|
213
|
+
this.system.${property.name.toLowerCase()} = {
|
|
214
|
+
value: ${property.name.toLowerCase()}CurrentValue,
|
|
215
|
+
max: ${property.name.toLowerCase()}CurrentMax,
|
|
216
|
+
mod: ${property.name.toLowerCase()}Mod
|
|
217
|
+
};
|
|
218
|
+
if ( this.system.${property.name.toLowerCase()}.value > this.system.${property.name.toLowerCase()}.max ) {
|
|
219
|
+
this.system.${property.name.toLowerCase()}.value = this.system.${property.name.toLowerCase()}.max;
|
|
220
|
+
}
|
|
221
|
+
`.appendNewLineIfNotEmpty();
|
|
222
|
+
}
|
|
223
|
+
;
|
|
224
|
+
function generateValueOrMethod(value) {
|
|
225
|
+
if (isMethodBlock(value)) {
|
|
226
|
+
return expandToNode `
|
|
227
|
+
const context = {
|
|
228
|
+
object: this
|
|
229
|
+
};
|
|
230
|
+
${translateExpression(entry, id, value, true, undefined)}
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
return expandToNode `
|
|
234
|
+
return ${value};
|
|
235
|
+
`;
|
|
236
|
+
}
|
|
237
|
+
if (isTrackerExp(property)) {
|
|
238
|
+
console.log("Processing Derived Tracker: " + property.name);
|
|
239
|
+
const maxParam = property.params.find(x => isNumberParamMax(x));
|
|
240
|
+
const minParam = property.params.find(x => isNumberParamMin(x));
|
|
241
|
+
const valueParam = property.params.find(x => isNumberParamValue(x));
|
|
242
|
+
if (maxParam == undefined && minParam == undefined && valueParam == undefined)
|
|
243
|
+
return;
|
|
244
|
+
return expandToNode `
|
|
245
|
+
// ${property.name} Tracker Derived Data
|
|
246
|
+
const ${property.name.toLowerCase()}TempValue = this.system.${property.name.toLowerCase()}.temp ?? 0;
|
|
247
|
+
const ${property.name.toLowerCase()}CurrentMin = (system) => {
|
|
248
|
+
${minParam == undefined ? expandToNode `return this.system.${property.name.toLowerCase()}?.min ?? 0;` : generateValueOrMethod(minParam.value)};
|
|
249
|
+
}
|
|
250
|
+
const ${property.name.toLowerCase()}CurrentValue = (system) => {
|
|
251
|
+
${valueParam == undefined ? expandToNode `return this.system.${property.name.toLowerCase()}?.value ?? 0;` : generateValueOrMethod(valueParam.value)};
|
|
252
|
+
}
|
|
253
|
+
const ${property.name.toLowerCase()}CurrentMax = (system) => {
|
|
254
|
+
${maxParam == undefined ? expandToNode `return this.system.${property.name.toLowerCase()}?.max ?? 0;` : generateValueOrMethod(maxParam.value)};
|
|
255
|
+
}
|
|
256
|
+
let ${property.name.toLowerCase()}Min = ${property.name.toLowerCase()}CurrentMin(this.system);
|
|
257
|
+
let ${property.name.toLowerCase()}Value = ${property.name.toLowerCase()}CurrentValue(this.system);
|
|
258
|
+
let ${property.name.toLowerCase()}Max = ${property.name.toLowerCase()}CurrentMax(this.system);
|
|
259
|
+
// Protect against NaN from invalid operations
|
|
260
|
+
if (isNaN(${property.name.toLowerCase()}Min) || ${property.name.toLowerCase()}Min === undefined || ${property.name.toLowerCase()}Min === null) {
|
|
261
|
+
${property.name.toLowerCase()}Min = 0;
|
|
262
|
+
}
|
|
263
|
+
if (isNaN(${property.name.toLowerCase()}Value) || ${property.name.toLowerCase()}Value === undefined || ${property.name.toLowerCase()}Value === null) {
|
|
264
|
+
${property.name.toLowerCase()}Value = 0;
|
|
265
|
+
}
|
|
266
|
+
if (isNaN(${property.name.toLowerCase()}Max) || ${property.name.toLowerCase()}Max === undefined || ${property.name.toLowerCase()}Max === null) {
|
|
267
|
+
${property.name.toLowerCase()}Max = 0;
|
|
268
|
+
}
|
|
269
|
+
this.system.${property.name.toLowerCase()} = {
|
|
270
|
+
min: ${property.name.toLowerCase()}Min,
|
|
271
|
+
value: ${property.name.toLowerCase()}Value,
|
|
272
|
+
temp: ${property.name.toLowerCase()}TempValue,
|
|
273
|
+
max: ${property.name.toLowerCase()}Max,
|
|
274
|
+
};
|
|
275
|
+
if ( !editMode && this.system.${property.name.toLowerCase()}.value < this.system.${property.name.toLowerCase()}.min ) {
|
|
276
|
+
this.system.${property.name.toLowerCase()}.value = this.system.${property.name.toLowerCase()}.min;
|
|
277
|
+
}
|
|
278
|
+
else if ( !editMode && this.system.${property.name.toLowerCase()}.value > this.system.${property.name.toLowerCase()}.max ) {
|
|
279
|
+
this.system.${property.name.toLowerCase()}.value = this.system.${property.name.toLowerCase()}.max;
|
|
280
|
+
}
|
|
281
|
+
`.appendNewLineIfNotEmpty();
|
|
282
|
+
}
|
|
283
|
+
;
|
|
284
|
+
if (isResourceExp(property)) {
|
|
285
|
+
console.log("Processing Derived Resource: " + property.name);
|
|
286
|
+
const maxParam = property.params.find(x => isNumberParamMax(x));
|
|
287
|
+
const minParam = property.params.find(x => isNumberParamMin(x));
|
|
288
|
+
const valueParam = property.params.find(x => isNumberParamValue(x));
|
|
289
|
+
if (maxParam == undefined && minParam == undefined && valueParam == undefined)
|
|
290
|
+
return;
|
|
291
|
+
//toBeReapplied.add("system." + property.name.toLowerCase() + ".max");
|
|
292
|
+
return expandToNode `
|
|
293
|
+
// ${property.name} Resource Derived Data
|
|
294
|
+
const ${property.name.toLowerCase()}TempValue = this.system.${property.name.toLowerCase()}.temp ?? 0;
|
|
295
|
+
const ${property.name.toLowerCase()}CurrentMin = (system) => {
|
|
296
|
+
${minParam == undefined ? expandToNode `return this.system.${property.name.toLowerCase()}?.min ?? 0;` : generateValueOrMethod(minParam.value)};
|
|
297
|
+
}
|
|
298
|
+
const ${property.name.toLowerCase()}CurrentValue = (system) => {
|
|
299
|
+
${valueParam == undefined ? expandToNode `return this.system.${property.name.toLowerCase()}?.value ?? 0;` : generateValueOrMethod(valueParam.value)};
|
|
300
|
+
}
|
|
301
|
+
const ${property.name.toLowerCase()}CurrentMax = (system) => {
|
|
302
|
+
${maxParam == undefined ? expandToNode `return this.system.${property.name.toLowerCase()}?.max ?? 0;` : generateValueOrMethod(maxParam.value)};
|
|
303
|
+
}
|
|
304
|
+
let ${property.name.toLowerCase()}Value = ${property.name.toLowerCase()}CurrentValue(this.system);
|
|
305
|
+
let ${property.name.toLowerCase()}Max = ${property.name.toLowerCase()}CurrentMax(this.system);
|
|
306
|
+
// Protect against NaN from invalid operations
|
|
307
|
+
if (isNaN(${property.name.toLowerCase()}Value) || ${property.name.toLowerCase()}Value === undefined || ${property.name.toLowerCase()}Value === null) {
|
|
308
|
+
${property.name.toLowerCase()}Value = 0;
|
|
309
|
+
}
|
|
310
|
+
if (isNaN(${property.name.toLowerCase()}Max) || ${property.name.toLowerCase()}Max === undefined || ${property.name.toLowerCase()}Max === null) {
|
|
311
|
+
${property.name.toLowerCase()}Max = 0;
|
|
312
|
+
}
|
|
313
|
+
this.system.${property.name.toLowerCase()} = {
|
|
314
|
+
value: ${property.name.toLowerCase()}Value,
|
|
315
|
+
temp: ${property.name.toLowerCase()}TempValue,
|
|
316
|
+
max: ${property.name.toLowerCase()}Max
|
|
317
|
+
};
|
|
318
|
+
this.reapplyActiveEffectsForName("system.${property.name.toLowerCase()}.max");
|
|
319
|
+
if ( !editMode && this.system.${property.name.toLowerCase()}.value < ${property.name.toLowerCase()}CurrentMin(this.system) ) {
|
|
320
|
+
this.system.${property.name.toLowerCase()}.value = ${property.name.toLowerCase()}CurrentMin(this.system);
|
|
321
|
+
}
|
|
322
|
+
else if ( !editMode && this.system.${property.name.toLowerCase()}.value > this.system.${property.name.toLowerCase()}.max ) {
|
|
323
|
+
this.system.${property.name.toLowerCase()}.value = this.system.${property.name.toLowerCase()}.max;
|
|
324
|
+
}
|
|
325
|
+
`.appendNewLineIfNotEmpty();
|
|
326
|
+
}
|
|
327
|
+
if (isTableField(property)) {
|
|
328
|
+
console.log("Processing Derived Document Array: " + property.name);
|
|
329
|
+
const whereParam = property.params.find(p => isWhereParam(p));
|
|
330
|
+
if (whereParam) {
|
|
331
|
+
return expandToNode `
|
|
332
|
+
// ${property.name} Document Array Derived Data
|
|
333
|
+
this.system.${property.name.toLowerCase()} = this.items.filter((item) => {
|
|
334
|
+
if ( item.type !== "${(_a = property.document.ref) === null || _a === void 0 ? void 0 : _a.name.toLowerCase()}") return false;
|
|
335
|
+
return ${translateExpression(entry, id, whereParam.value, true, property)};
|
|
336
|
+
});
|
|
337
|
+
`.appendNewLineIfNotEmpty();
|
|
338
|
+
}
|
|
339
|
+
return expandToNode `
|
|
340
|
+
// ${property.name} Document Array Derived Data
|
|
341
|
+
this.system.${property.name.toLowerCase()} = this.items.filter((item) => item.type == "${(_b = property.document.ref) === null || _b === void 0 ? void 0 : _b.name.toLowerCase()}");
|
|
342
|
+
`.appendNewLineIfNotEmpty();
|
|
343
|
+
}
|
|
344
|
+
if (isMeasuredTemplateField(property)) {
|
|
345
|
+
return expandToNode `
|
|
346
|
+
// ${property.name} Measured Template Field Derived Data
|
|
347
|
+
|
|
348
|
+
const ${property.name.toLowerCase()}Summary = () => {
|
|
349
|
+
let sum = \`\${this.system.${property.name.toLowerCase()}.direction}° \${this.system.${property.name.toLowerCase()}.type} (\${this.system.${property.name.toLowerCase()}.distance} squares)\`;
|
|
350
|
+
if (this.system.${property.name.toLowerCase()}.type === 'cone') sum += \` \${this.system.${property.name.toLowerCase()}.angle}° angle\`;
|
|
351
|
+
if (this.system.${property.name.toLowerCase()}.type === 'ray') sum += \` \${this.system.${property.name.toLowerCase()}.width} squares wide\`;
|
|
352
|
+
return sum;
|
|
353
|
+
}
|
|
354
|
+
this.system.${property.name.toLowerCase()}.summary = ${property.name.toLowerCase()}Summary();
|
|
355
|
+
`.appendNewLineIfNotEmpty();
|
|
356
|
+
}
|
|
357
|
+
if (isDiceField(property)) {
|
|
358
|
+
return expandToNode `
|
|
359
|
+
// ${property.name} Dice Field Derived Data
|
|
360
|
+
|
|
361
|
+
const ${property.name.toLowerCase()}Value = () => {
|
|
362
|
+
// Output a string of num + die
|
|
363
|
+
return this.system.${property.name.toLowerCase()}.number + this.system.${property.name.toLowerCase()}.die;
|
|
364
|
+
}
|
|
365
|
+
this.system.${property.name.toLowerCase()}.value = ${property.name.toLowerCase()}Value();
|
|
366
|
+
`.appendNewLineIfNotEmpty();
|
|
367
|
+
}
|
|
368
|
+
// if (isParentPropertyRefExp(property)) {
|
|
369
|
+
// console.log("Processing Derived Parent Property: " + property.name);
|
|
370
|
+
// return expandToNode`
|
|
371
|
+
// // Parent ${property.name} Property Derived Data
|
|
372
|
+
// if ( !this.parent || this.system.${property.name.toLowerCase()}Ref == "" ) {
|
|
373
|
+
// this.system.${property.name.toLowerCase()} = 0;
|
|
374
|
+
// }
|
|
375
|
+
// else {
|
|
376
|
+
// this.system.${property.name.toLowerCase()} = foundry.utils.getProperty(this.parent, this.system.${property.name.toLowerCase()}Ref) ?? 0;
|
|
377
|
+
// }
|
|
378
|
+
// `.appendNewLineIfNotEmpty().appendNewLine();
|
|
379
|
+
// }
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
function extractPropertyDependencies(methodBlock) {
|
|
383
|
+
const dependencies = new Set();
|
|
384
|
+
function traverseExpression(node) {
|
|
385
|
+
var _a, _b;
|
|
386
|
+
if (!node)
|
|
387
|
+
return;
|
|
388
|
+
if (isAccess(node)) {
|
|
389
|
+
// For access expressions like self.PropertyName, check if it's referencing another property
|
|
390
|
+
if ((_b = (_a = node.property) === null || _a === void 0 ? void 0 : _a.ref) === null || _b === void 0 ? void 0 : _b.name) {
|
|
391
|
+
dependencies.add(node.property.ref.name.toLowerCase());
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (isAccess(node)) {
|
|
395
|
+
// Self-reference expressions don't create dependencies on specific properties
|
|
396
|
+
// since they're resolved at runtime based on user selection
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
// Recursively traverse child nodes
|
|
400
|
+
if (node.$children) {
|
|
401
|
+
for (const child of node.$children) {
|
|
402
|
+
traverseExpression(child);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
// Handle common expression properties
|
|
406
|
+
if (node.left)
|
|
407
|
+
traverseExpression(node.left);
|
|
408
|
+
if (node.right)
|
|
409
|
+
traverseExpression(node.right);
|
|
410
|
+
if (node.value)
|
|
411
|
+
traverseExpression(node.value);
|
|
412
|
+
if (node.expression)
|
|
413
|
+
traverseExpression(node.expression);
|
|
414
|
+
if (node.body && Array.isArray(node.body)) {
|
|
415
|
+
for (const expr of node.body) {
|
|
416
|
+
traverseExpression(expr);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// Handle binary expression operands (e1, e2)
|
|
420
|
+
if (node.e1)
|
|
421
|
+
traverseExpression(node.e1);
|
|
422
|
+
if (node.e2)
|
|
423
|
+
traverseExpression(node.e2);
|
|
424
|
+
}
|
|
425
|
+
traverseExpression(methodBlock);
|
|
426
|
+
return dependencies;
|
|
427
|
+
}
|
|
428
|
+
function isPropertyComputed(property) {
|
|
429
|
+
if (isLayout(property)) {
|
|
430
|
+
return false; // Layouts don't have computed values themselves
|
|
431
|
+
}
|
|
432
|
+
// Check if any parameter contains a method block
|
|
433
|
+
if (isNumberExp(property)) {
|
|
434
|
+
const valueParam = property.params.find((p) => isNumberParamValue(p));
|
|
435
|
+
const minParam = property.params.find((p) => isNumberParamMin(p));
|
|
436
|
+
const maxParam = property.params.find((p) => isNumberParamMax(p));
|
|
437
|
+
return !!(valueParam && isMethodBlock(valueParam.value)) ||
|
|
438
|
+
!!(minParam && isMethodBlock(minParam.value)) ||
|
|
439
|
+
!!(maxParam && isMethodBlock(maxParam.value));
|
|
440
|
+
}
|
|
441
|
+
if (isStringExp(property)) {
|
|
442
|
+
const stringValue = property.params.find((p) => isStringParamValue(p));
|
|
443
|
+
return !!(stringValue && isMethodBlock(stringValue.value));
|
|
444
|
+
}
|
|
445
|
+
if (isAttributeExp(property)) {
|
|
446
|
+
const modParam = property.params.find((p) => isAttributeParamMod(p));
|
|
447
|
+
return !!(modParam && isMethodBlock(modParam.method));
|
|
448
|
+
}
|
|
449
|
+
if (isTrackerExp(property) || isResourceExp(property)) {
|
|
450
|
+
const params = property.params;
|
|
451
|
+
const valueParam = params.find((p) => isNumberParamValue(p));
|
|
452
|
+
const minParam = params.find((p) => isNumberParamMin(p));
|
|
453
|
+
const maxParam = params.find((p) => isNumberParamMax(p));
|
|
454
|
+
return !!(valueParam && isMethodBlock(valueParam.value)) ||
|
|
455
|
+
!!(minParam && isMethodBlock(minParam.value)) ||
|
|
456
|
+
!!(maxParam && isMethodBlock(maxParam.value));
|
|
457
|
+
}
|
|
458
|
+
if (isMoneyField(property)) {
|
|
459
|
+
const valueParam = property.params.find((p) => isNumberParamValue(p));
|
|
460
|
+
return !!(valueParam && isMethodBlock(valueParam.value));
|
|
461
|
+
}
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
function buildDependencyGraph(properties) {
|
|
465
|
+
const dependencies = [];
|
|
466
|
+
function processProperty(property) {
|
|
467
|
+
if (isLayout(property)) {
|
|
468
|
+
// Recursively process layout children
|
|
469
|
+
const childDeps = new Set();
|
|
470
|
+
for (const child of property.body) {
|
|
471
|
+
const childDep = processProperty(child);
|
|
472
|
+
childDep.dependencies.forEach(dep => childDeps.add(dep));
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
property,
|
|
476
|
+
dependencies: childDeps,
|
|
477
|
+
isComputed: false
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
const deps = new Set();
|
|
481
|
+
const computed = isPropertyComputed(property);
|
|
482
|
+
if (computed) {
|
|
483
|
+
// Extract dependencies from method blocks
|
|
484
|
+
if (isNumberExp(property)) {
|
|
485
|
+
const valueParam = property.params.find((p) => isNumberParamValue(p));
|
|
486
|
+
const minParam = property.params.find((p) => isNumberParamMin(p));
|
|
487
|
+
const maxParam = property.params.find((p) => isNumberParamMax(p));
|
|
488
|
+
if (valueParam && isMethodBlock(valueParam.value)) {
|
|
489
|
+
extractPropertyDependencies(valueParam.value).forEach(dep => deps.add(dep));
|
|
490
|
+
}
|
|
491
|
+
if (minParam && isMethodBlock(minParam.value)) {
|
|
492
|
+
extractPropertyDependencies(minParam.value).forEach(dep => deps.add(dep));
|
|
493
|
+
}
|
|
494
|
+
if (maxParam && isMethodBlock(maxParam.value)) {
|
|
495
|
+
extractPropertyDependencies(maxParam.value).forEach(dep => deps.add(dep));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
else if (isStringExp(property)) {
|
|
499
|
+
const stringValue = property.params.find((p) => isStringParamValue(p));
|
|
500
|
+
if (stringValue && isMethodBlock(stringValue.value)) {
|
|
501
|
+
extractPropertyDependencies(stringValue.value).forEach(dep => deps.add(dep));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
else if (isAttributeExp(property)) {
|
|
505
|
+
const modParam = property.params.find((p) => isAttributeParamMod(p));
|
|
506
|
+
if (modParam && isMethodBlock(modParam.method)) {
|
|
507
|
+
extractPropertyDependencies(modParam.method).forEach(dep => deps.add(dep));
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
else if (isTrackerExp(property) || isResourceExp(property)) {
|
|
511
|
+
const params = property.params;
|
|
512
|
+
const valueParam = params.find((p) => isNumberParamValue(p));
|
|
513
|
+
const minParam = params.find((p) => isNumberParamMin(p));
|
|
514
|
+
const maxParam = params.find((p) => isNumberParamMax(p));
|
|
515
|
+
if (valueParam && isMethodBlock(valueParam.value)) {
|
|
516
|
+
extractPropertyDependencies(valueParam.value).forEach(dep => deps.add(dep));
|
|
517
|
+
}
|
|
518
|
+
if (minParam && isMethodBlock(minParam.value)) {
|
|
519
|
+
extractPropertyDependencies(minParam.value).forEach(dep => deps.add(dep));
|
|
520
|
+
}
|
|
521
|
+
if (maxParam && isMethodBlock(maxParam.value)) {
|
|
522
|
+
extractPropertyDependencies(maxParam.value).forEach(dep => deps.add(dep));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
else if (isMoneyField(property)) {
|
|
526
|
+
const valueParam = property.params.find((p) => isNumberParamValue(p));
|
|
527
|
+
if (valueParam && isMethodBlock(valueParam.value)) {
|
|
528
|
+
extractPropertyDependencies(valueParam.value).forEach(dep => deps.add(dep));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
// Remove self-references (only for properties that have names)
|
|
532
|
+
if ('name' in property && property.name) {
|
|
533
|
+
deps.delete(property.name.toLowerCase());
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
property,
|
|
538
|
+
dependencies: deps,
|
|
539
|
+
isComputed: computed
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
for (const property of properties) {
|
|
543
|
+
dependencies.push(processProperty(property));
|
|
544
|
+
}
|
|
545
|
+
return dependencies;
|
|
546
|
+
}
|
|
547
|
+
function topologicalSort(dependencies) {
|
|
548
|
+
const sorted = [];
|
|
549
|
+
const visiting = new Set();
|
|
550
|
+
const visited = new Set();
|
|
551
|
+
const cycles = [];
|
|
552
|
+
function getPropertyName(property) {
|
|
553
|
+
return ('name' in property && property.name) ? property.name.toLowerCase() : 'unknown';
|
|
554
|
+
}
|
|
555
|
+
function visit(depItem, path = []) {
|
|
556
|
+
const name = getPropertyName(depItem.property);
|
|
557
|
+
if (visiting.has(name)) {
|
|
558
|
+
// Found a cycle
|
|
559
|
+
const cycleStart = path.indexOf(name);
|
|
560
|
+
if (cycleStart >= 0) {
|
|
561
|
+
cycles.push([...path.slice(cycleStart), name]);
|
|
562
|
+
}
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (visited.has(name)) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
visiting.add(name);
|
|
569
|
+
path.push(name);
|
|
570
|
+
// Visit dependencies first
|
|
571
|
+
for (const depName of depItem.dependencies) {
|
|
572
|
+
const depItem = dependencies.find(d => getPropertyName(d.property) === depName);
|
|
573
|
+
if (depItem) {
|
|
574
|
+
visit(depItem, [...path]);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
visiting.delete(name);
|
|
578
|
+
visited.add(name);
|
|
579
|
+
path.pop();
|
|
580
|
+
sorted.push(depItem);
|
|
581
|
+
}
|
|
582
|
+
// Visit all non-computed properties first
|
|
583
|
+
for (const depItem of dependencies) {
|
|
584
|
+
if (!depItem.isComputed && !visited.has(getPropertyName(depItem.property))) {
|
|
585
|
+
visit(depItem);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
// Then visit computed properties
|
|
589
|
+
for (const depItem of dependencies) {
|
|
590
|
+
if (depItem.isComputed && !visited.has(getPropertyName(depItem.property))) {
|
|
591
|
+
visit(depItem);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return { sorted, cycles };
|
|
595
|
+
}
|
|
596
|
+
function getTopLevelProperties(body) {
|
|
597
|
+
const result = [];
|
|
598
|
+
for (const item of body) {
|
|
599
|
+
if (isDocument(item)) {
|
|
600
|
+
// Recursively process document bodies
|
|
601
|
+
result.push(...getTopLevelProperties(item.body));
|
|
602
|
+
}
|
|
603
|
+
else if (isLayout(item)) {
|
|
604
|
+
// Flatten ALL layouts (pages, sections, rows, columns, tabs) down to
|
|
605
|
+
// their leaf properties. Layout nodes must NOT be added to the
|
|
606
|
+
// dependency graph: anonymous layouts (row/column have no name) all
|
|
607
|
+
// resolve to the same 'unknown' key in topologicalSort's visited set,
|
|
608
|
+
// so every anonymous layout after the first is skipped and the derived
|
|
609
|
+
// data for its children (e.g. a resource's calculated max) is never
|
|
610
|
+
// emitted — leaving it at the datamodel default. Flattening to named
|
|
611
|
+
// leaf properties makes section/row/column/page/root all behave the same.
|
|
612
|
+
result.push(...getTopLevelProperties(item.body));
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
// For regular properties (ClassExpression), add them directly
|
|
616
|
+
result.push(item);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return result;
|
|
620
|
+
}
|
|
621
|
+
function generateDerivedData(document) {
|
|
622
|
+
// Build dependency graph for top-level properties only (no duplicates from nested layouts)
|
|
623
|
+
const allProperties = getTopLevelProperties(document.body);
|
|
624
|
+
const dependencies = buildDependencyGraph(allProperties);
|
|
625
|
+
const { sorted, cycles } = topologicalSort(dependencies);
|
|
626
|
+
// Debug: Log dependency information (can be removed in production)
|
|
627
|
+
// console.log(`Dependencies for ${document.name}:`, dependencies.map(d => ({
|
|
628
|
+
// name: ('name' in d.property && d.property.name) ? d.property.name : 'unknown',
|
|
629
|
+
// deps: Array.from(d.dependencies),
|
|
630
|
+
// computed: d.isComputed
|
|
631
|
+
// })));
|
|
632
|
+
// Log dependency cycles for debugging
|
|
633
|
+
if (cycles.length > 0) {
|
|
634
|
+
console.warn(`Dependency cycles detected in ${document.name}:`, cycles);
|
|
635
|
+
}
|
|
636
|
+
return expandToNode `
|
|
637
|
+
async _prepare${document.name}DerivedData() {
|
|
638
|
+
const editMode = this.flags["${id}"]?.["edit-mode"] ?? true;
|
|
639
|
+
|
|
640
|
+
${cycles.length > 0 ? expandToNode `
|
|
641
|
+
// WARNING: Dependency cycles detected: ${cycles.map(cycle => cycle.join(' -> ')).join(', ')}
|
|
642
|
+
// Properties will be processed in original order to prevent infinite loops.
|
|
643
|
+
`.appendNewLine() : ""}
|
|
644
|
+
|
|
645
|
+
${joinToNode(sorted.map(dep => dep.property), property => generateDerivedAttribute(property), { appendNewLineIfNotEmpty: true })}
|
|
646
|
+
|
|
647
|
+
${isActor(document) ? expandToNode `
|
|
648
|
+
// Reapply Active Effects for calculated values
|
|
649
|
+
${joinToNode(toBeReapplied, name => expandToNode `this.reapplyActiveEffectsForName("${name}");`, { appendNewLineIfNotEmpty: true })}
|
|
650
|
+
|
|
651
|
+
// Bridge fighter.system.* active effect overrides into system.* for flat (non-nested) fields.
|
|
652
|
+
// allApplicableEffects cannot strip the type prefix in V14 (changes is getter-only),
|
|
653
|
+
// so Foundry applies flat fields (e.g. bonusdamage, resistanceflat) to actor.fighter.system.*
|
|
654
|
+
// Nested fields (e.g. fight.value) are handled by reapplyActiveEffectsForName above.
|
|
655
|
+
const _typeOverrides = this.overrides?.${document.name.toLowerCase()}?.system ?? {};
|
|
656
|
+
for (const [_k, _v] of Object.entries(_typeOverrides)) {
|
|
657
|
+
if (typeof _v !== 'object' && _k in this.system) {
|
|
658
|
+
this.system[_k] = _v;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
` : ""}
|
|
662
|
+
}
|
|
663
|
+
`.appendNewLineIfNotEmpty().appendNewLine();
|
|
664
|
+
}
|
|
665
|
+
function generateActorPreUpdate(document) {
|
|
666
|
+
const allResources = getAllOfType(document.body, isResourceExp);
|
|
667
|
+
const healthResource = allResources.find(x => x.tag == "health");
|
|
668
|
+
if (healthResource == undefined)
|
|
669
|
+
return expandToNode `
|
|
670
|
+
_handlePreUpdate${document.name}Delta(changes, deltas) {
|
|
671
|
+
// No health resource defined
|
|
672
|
+
}
|
|
673
|
+
`.appendNewLine();
|
|
674
|
+
return expandToNode `
|
|
675
|
+
_handlePreUpdate${document.name}Delta(changes, deltas) {
|
|
676
|
+
// Health resource updates
|
|
677
|
+
if (changes.system.${healthResource.name.toLowerCase()} === undefined) return;
|
|
678
|
+
|
|
679
|
+
// Store value and temp changes
|
|
680
|
+
const valueChange = changes.system.${healthResource.name.toLowerCase()}.value;
|
|
681
|
+
const tempChange = changes.system.${healthResource.name.toLowerCase()}.temp;
|
|
682
|
+
|
|
683
|
+
// Calculate delta
|
|
684
|
+
if (valueChange !== undefined) {
|
|
685
|
+
const delta = valueChange - this.system.${healthResource.name.toLowerCase()}.value;
|
|
686
|
+
if (delta !== 0) {
|
|
687
|
+
deltas.${healthResource.name.toLowerCase()} = delta;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Calculate temp delta
|
|
692
|
+
if (tempChange !== undefined) {
|
|
693
|
+
const tempDelta = tempChange - this.system.${healthResource.name.toLowerCase()}.temp;
|
|
694
|
+
if (tempDelta !== 0) {
|
|
695
|
+
deltas.${healthResource.name.toLowerCase()}Temp = tempDelta;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
`.appendNewLine();
|
|
700
|
+
}
|
|
701
|
+
function generateDeltas(documents) {
|
|
702
|
+
if (type != "Actor")
|
|
703
|
+
return;
|
|
704
|
+
function generateHealthResourceAssignments(documents) {
|
|
705
|
+
function documentHealthResource(document) {
|
|
706
|
+
const healthResource = getAllOfType(document.body, isResourceExp).find(x => x.tag == "health");
|
|
707
|
+
if (healthResource == undefined)
|
|
708
|
+
return;
|
|
709
|
+
return expandToNode `
|
|
710
|
+
case "${document.name.toLowerCase()}": {
|
|
711
|
+
// ${healthResource.name} health resource
|
|
712
|
+
|
|
713
|
+
if ( !data.prototypeToken.bar1.attribute ) data.prototypeToken.bar1.attribute = "${healthResource.name.toLowerCase()}";
|
|
714
|
+
if ( !data.prototypeToken.displayBars ) data.prototypeToken.displayBars = CONST.TOKEN_DISPLAY_MODES.ALWAYS;
|
|
715
|
+
}
|
|
716
|
+
`.appendNewLineIfNotEmpty();
|
|
717
|
+
}
|
|
718
|
+
return expandToNode `
|
|
719
|
+
switch ( data.type ) {
|
|
720
|
+
${joinToNode(documents.filter(x => isActor(x)), document => documentHealthResource(document), { appendNewLineIfNotEmpty: true })}
|
|
721
|
+
}
|
|
722
|
+
`.appendNewLineIfNotEmpty();
|
|
723
|
+
}
|
|
724
|
+
return expandToNode `
|
|
725
|
+
async _preUpdate(data, options, userId) {
|
|
726
|
+
await super._preUpdate(data, options, userId);
|
|
727
|
+
if (!options.diff || data === undefined) return;
|
|
728
|
+
let changes = {};
|
|
729
|
+
|
|
730
|
+
// Foundry v12 no longer has diffed data during _preUpdate, so we need to compute it ourselves.
|
|
731
|
+
if (game.release.version >= 12) {
|
|
732
|
+
// Retrieve a copy of the existing actor data.
|
|
733
|
+
let newData = game.system.utils.flattenObject(data);
|
|
734
|
+
let oldData = game.system.utils.flattenObject(this);
|
|
735
|
+
|
|
736
|
+
// Limit data to just the new data.
|
|
737
|
+
const diffData = foundry.utils.diffObject(oldData, newData);
|
|
738
|
+
changes = foundry.utils.expandObject(diffData);
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
changes = foundry.utils.duplicate(data);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Handle name changes
|
|
745
|
+
if (changes.name) {
|
|
746
|
+
let tokenData = {};
|
|
747
|
+
|
|
748
|
+
// Propagate name update to prototype token if same as actor
|
|
749
|
+
if (changes.name && this.name == this.prototypeToken.name) {
|
|
750
|
+
data.prototypeToken = {name: data.name};
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Update tokens.
|
|
754
|
+
let tokens = this.getActiveTokens();
|
|
755
|
+
tokens.forEach(token => {
|
|
756
|
+
let updateData = foundry.utils.duplicate(tokenData);
|
|
757
|
+
|
|
758
|
+
// Propagate name update to token if same as actor
|
|
759
|
+
if (data.name && this.name == token.name) {
|
|
760
|
+
updateData.name = data.name;
|
|
761
|
+
}
|
|
762
|
+
token.document.update(updateData);
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (changes.system === undefined) return; // Nothing more to do
|
|
767
|
+
|
|
768
|
+
const deltas = {};
|
|
769
|
+
|
|
770
|
+
${joinToNode(documents.filter(x => isActor(x)), document => expandToNode `
|
|
771
|
+
if (this.type == "${document.name.toLowerCase()}") this._handlePreUpdate${document.name}Delta(changes, deltas);
|
|
772
|
+
`, { appendNewLineIfNotEmpty: true })}
|
|
773
|
+
|
|
774
|
+
options.fromPreUpdate = deltas;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/* -------------------------------------------- */
|
|
778
|
+
|
|
779
|
+
${joinToNode(documents.filter(x => isActor(x)), document => generateActorPreUpdate(document), { appendNewLineIfNotEmpty: true })}
|
|
780
|
+
|
|
781
|
+
/* -------------------------------------------- */
|
|
782
|
+
|
|
783
|
+
async _onUpdate(data, options, userId) {
|
|
784
|
+
await super._onUpdate(data, options, userId);
|
|
785
|
+
|
|
786
|
+
// Iterate over all objects in fromPreUpdate, showing scrolling text for each.
|
|
787
|
+
if (options.fromPreUpdate) {
|
|
788
|
+
for (const [key, delta] of Object.entries(options.fromPreUpdate)) {
|
|
789
|
+
this._showScrollingText(delta, key);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Add / remove status effects
|
|
794
|
+
const calculatedStatusEffects = CONFIG.statusEffects.filter(effect => effect.calculated);
|
|
795
|
+
for (const effect of calculatedStatusEffects) {
|
|
796
|
+
const key = effect.id;
|
|
797
|
+
const active = this.system[key] ?? false;
|
|
798
|
+
const existing = this.effects.find(e => e.statuses.has(key));
|
|
799
|
+
|
|
800
|
+
if ((active && existing) || (!active && !existing)) continue;
|
|
801
|
+
|
|
802
|
+
// If the effect is active the AE doesn't exist, add it
|
|
803
|
+
if (active && !existing) {
|
|
804
|
+
const cls = getDocumentClass("ActiveEffect");
|
|
805
|
+
const createData = foundry.utils.deepClone(effect);
|
|
806
|
+
createData.statuses = [key];
|
|
807
|
+
delete createData.id;
|
|
808
|
+
createData.name = game.i18n.localize(createData.name);
|
|
809
|
+
await cls.create(createData, {parent: this});
|
|
810
|
+
if (key == "dead") Hooks.callAll("death", this);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// If the effect is active the AE doesn't exist, add it
|
|
814
|
+
if (!active && existing) {
|
|
815
|
+
this.deleteEmbeddedDocuments("ActiveEffect", [existing.id]);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/* -------------------------------------------- */
|
|
821
|
+
|
|
822
|
+
async _onCreate(data, options, userId) {
|
|
823
|
+
await super._onCreate(data, options, userId);
|
|
824
|
+
|
|
825
|
+
console.log("onCreate", data, options, userId);
|
|
826
|
+
|
|
827
|
+
${generateHealthResourceAssignments(documents)}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/* -------------------------------------------- */
|
|
831
|
+
|
|
832
|
+
_showScrollingText(delta, suffix="", overrideOptions={}) {
|
|
833
|
+
// Show scrolling text of hp update
|
|
834
|
+
const tokens = this.isToken ? [this.token?.object] : this.getActiveTokens(true);
|
|
835
|
+
if (delta != 0 && tokens.length > 0) {
|
|
836
|
+
let color = delta < 0 ? 0xcc0000 : 0x00cc00;
|
|
837
|
+
for ( let token of tokens ) {
|
|
838
|
+
let textOptions = {
|
|
839
|
+
anchor: CONST.TEXT_ANCHOR_POINTS.CENTER,
|
|
840
|
+
direction: CONST.TEXT_ANCHOR_POINTS.TOP,
|
|
841
|
+
fontSize: 32,
|
|
842
|
+
fill: color,
|
|
843
|
+
stroke: 0x000000,
|
|
844
|
+
strokeThickness: 4,
|
|
845
|
+
duration: 3000
|
|
846
|
+
};
|
|
847
|
+
canvas.interface.createScrollingText(
|
|
848
|
+
token.center,
|
|
849
|
+
delta.signedString()+" "+suffix,
|
|
850
|
+
foundry.utils.mergeObject(textOptions, overrideOptions)
|
|
851
|
+
);
|
|
852
|
+
// Flash dynamic token rings.
|
|
853
|
+
if (token?.ring) {
|
|
854
|
+
const flashColor = delta < 0 ? Color.fromString('#ff0000') : Color.fromString('#00ff00');
|
|
855
|
+
token.ring.flashColor(flashColor, {
|
|
856
|
+
duration: 600,
|
|
857
|
+
easing: foundry.canvas.tokens.TokenRing.easeTwoPeaks,
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
`.appendNewLineIfNotEmpty();
|
|
864
|
+
}
|
|
865
|
+
function generateInitiativeFormula(document) {
|
|
866
|
+
var _a;
|
|
867
|
+
const initiativeAttribute = getAllOfType(document.body, isInitiativeProperty);
|
|
868
|
+
if (initiativeAttribute.length == 0) {
|
|
869
|
+
return expandToNode `
|
|
870
|
+
case "${document.name.toLowerCase()}": return "0";
|
|
871
|
+
`.appendNewLineIfNotEmpty();
|
|
872
|
+
}
|
|
873
|
+
let initiative = (_a = initiativeAttribute[0]) === null || _a === void 0 ? void 0 : _a.value;
|
|
874
|
+
if (initiative == undefined) {
|
|
875
|
+
return expandToNode `
|
|
876
|
+
case "${document.name.toLowerCase()}": return "0";
|
|
877
|
+
`.appendNewLineIfNotEmpty();
|
|
878
|
+
}
|
|
879
|
+
console.log("Initiative Formula");
|
|
880
|
+
return expandToNode `
|
|
881
|
+
case "${document.name.toLowerCase()}": return "${translateExpression(entry, id, initiative, true, initiativeAttribute[0])}";
|
|
882
|
+
`.appendNewLineIfNotEmpty();
|
|
883
|
+
}
|
|
884
|
+
function generateDocumentHooks(document) {
|
|
885
|
+
const hooks = getAllOfType(document.body, isHookHandler);
|
|
886
|
+
function generateHook(hook) {
|
|
887
|
+
var _a;
|
|
888
|
+
let name = hook.name;
|
|
889
|
+
function generateBody() {
|
|
890
|
+
return expandToNode `
|
|
891
|
+
const ${entry.config.name}Roll = game.system.rollClass;
|
|
892
|
+
const ${entry.config.name}DamageRoll = game.system.damageRollClass;
|
|
893
|
+
const context = {
|
|
894
|
+
object: document,
|
|
895
|
+
target: game.user.getTargetOrNothing()
|
|
896
|
+
};
|
|
897
|
+
const system = document.system;
|
|
898
|
+
let update = {};
|
|
899
|
+
let embeddedUpdate = {};
|
|
900
|
+
let parentUpdate = {};
|
|
901
|
+
let parentEmbeddedUpdate = {};
|
|
902
|
+
let targetUpdate = {};
|
|
903
|
+
let targetEmbeddedUpdate = {};
|
|
904
|
+
let selfDeleted = false;
|
|
905
|
+
|
|
906
|
+
${translateBodyExpressionToJavascript(entry, id, hook.body, false, hook)}
|
|
907
|
+
|
|
908
|
+
if (!selfDeleted && Object.keys(update).length > 0) {
|
|
909
|
+
await document.update(update);
|
|
910
|
+
}
|
|
911
|
+
if (!selfDeleted && Object.keys(embeddedUpdate).length > 0) {
|
|
912
|
+
for (let key of Object.keys(embeddedUpdate)) {
|
|
913
|
+
await document.updateEmbeddedDocuments("Item", embeddedUpdate[key]);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (Object.keys(parentUpdate).length > 0) {
|
|
917
|
+
await document.parent.update(parentUpdate);
|
|
918
|
+
}
|
|
919
|
+
if (Object.keys(parentEmbeddedUpdate).length > 0) {
|
|
920
|
+
for (let key of Object.keys(parentEmbeddedUpdate)) {
|
|
921
|
+
await document.parent.updateEmbeddedDocuments("Item", parentEmbeddedUpdate[key]);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
if (Object.keys(targetUpdate).length > 0) {
|
|
925
|
+
await context.target.update(targetUpdate);
|
|
926
|
+
}
|
|
927
|
+
if (Object.keys(targetEmbeddedUpdate).length > 0) {
|
|
928
|
+
for (let key of Object.keys(targetEmbeddedUpdate)) {
|
|
929
|
+
await context.target.updateEmbeddedDocuments("Item", targetEmbeddedUpdate[key]);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
`;
|
|
933
|
+
}
|
|
934
|
+
switch (name) {
|
|
935
|
+
case "combatStart":
|
|
936
|
+
return expandToNode `
|
|
937
|
+
Hooks.on("combatStart", async (${hook.params.map(p => p.name).join(", ")}) => {
|
|
938
|
+
${generateBody()}
|
|
939
|
+
});
|
|
940
|
+
`.appendNewLineIfNotEmpty();
|
|
941
|
+
case "combatEnd":
|
|
942
|
+
return expandToNode `
|
|
943
|
+
Hooks.on("deleteCombat", async (${hook.params.map(p => p.name).join(", ")}) => {
|
|
944
|
+
${generateBody()}
|
|
945
|
+
});
|
|
946
|
+
`.appendNewLineIfNotEmpty();
|
|
947
|
+
case "roundStart":
|
|
948
|
+
return expandToNode `
|
|
949
|
+
Hooks.on("combatRound", async (combat, updateData, updateOptions) => {
|
|
950
|
+
const roundStart = async (${hook.params.map(p => p.name).join(", ")}) => {
|
|
951
|
+
${generateBody()}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (updateData.turn == 0) {
|
|
955
|
+
await roundStart(updateData.round);
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
`.appendNewLineIfNotEmpty();
|
|
959
|
+
case "roundEnd":
|
|
960
|
+
return expandToNode `
|
|
961
|
+
Hooks.on("combatRound", async (combat, updateData, updateOptions) => {
|
|
962
|
+
const roundEnd = async (${hook.params.map(p => p.name).join(", ")}) => {
|
|
963
|
+
${generateBody()}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
if (updateData.round > 0) {
|
|
967
|
+
roundEnd(updateData.round - 1);
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
`.appendNewLineIfNotEmpty();
|
|
971
|
+
case "turnStart":
|
|
972
|
+
return expandToNode `
|
|
973
|
+
Hooks.on("combatTurnChange", async (combat, updateData, updateOptions) => {
|
|
974
|
+
const turnStart = async () => {
|
|
975
|
+
${generateBody()}
|
|
976
|
+
}
|
|
977
|
+
if (combat.combatant.actor.uuid == document.uuid) {
|
|
978
|
+
await turnStart();
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
`.appendNewLineIfNotEmpty();
|
|
982
|
+
case "turnEnd":
|
|
983
|
+
return expandToNode `
|
|
984
|
+
Hooks.on("combatTurnChange", async (combat, updateData, updateOptions) => {
|
|
985
|
+
const turnEnd = async () => {
|
|
986
|
+
${generateBody()}
|
|
987
|
+
}
|
|
988
|
+
const previousCombatant = combat.combatants.get(combat.previous?.combatantId);
|
|
989
|
+
if (previousCombatant?.actor?.uuid == document.uuid) {
|
|
990
|
+
await turnEnd();
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
`.appendNewLineIfNotEmpty();
|
|
994
|
+
case "turnIsNext":
|
|
995
|
+
return expandToNode `
|
|
996
|
+
Hooks.on("combatTurnChange", async (combat, updateData, updateOptions) => {
|
|
997
|
+
const turnIsNext = async () => {
|
|
998
|
+
${generateBody()}
|
|
999
|
+
};
|
|
1000
|
+
if (combat.nextCombatant.actor.uuid == document.uuid) {
|
|
1001
|
+
await turnIsNext();
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
`.appendNewLineIfNotEmpty();
|
|
1005
|
+
case "death":
|
|
1006
|
+
return expandToNode `
|
|
1007
|
+
Hooks.on("death", async (deadDocument) => {
|
|
1008
|
+
const onDeath = async () => {
|
|
1009
|
+
${generateBody()}
|
|
1010
|
+
};
|
|
1011
|
+
if ( deadDocument.uuid == document.uuid ) {
|
|
1012
|
+
await onDeath();
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
`.appendNewLineIfNotEmpty();
|
|
1016
|
+
case "preApplyDamage":
|
|
1017
|
+
case "preApplyHealing":
|
|
1018
|
+
case "preApplyTemp":
|
|
1019
|
+
return expandToNode `
|
|
1020
|
+
if (game.system.documentHooks.has("${name}-" + this.uuid)) return;
|
|
1021
|
+
const on${name} = async (document, context) => {
|
|
1022
|
+
const preApply = async (${hook.params.map(p => p.name).join(", ")}) => {
|
|
1023
|
+
${generateBody()}
|
|
1024
|
+
return ${(_a = hook.params.shift()) === null || _a === void 0 ? void 0 : _a.name};
|
|
1025
|
+
}
|
|
1026
|
+
if (document.uuid == this.uuid) {
|
|
1027
|
+
context.amount = await preApply(context.amount, context.damageType, context.damageMetadata);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
game.system.documentHooks.set("${name}-" + this.uuid, on${name});
|
|
1031
|
+
Hooks.on("${name}", on${name});
|
|
1032
|
+
`.appendNewLineIfNotEmpty();
|
|
1033
|
+
case "appliedDamage":
|
|
1034
|
+
case "appliedHealing":
|
|
1035
|
+
case "appliedTemp":
|
|
1036
|
+
return expandToNode `
|
|
1037
|
+
if (game.system.documentHooks.has("${name}-" + this.uuid)) return;
|
|
1038
|
+
const on${name} = async (document, context) => {
|
|
1039
|
+
const applied = async (${hook.params.map(p => p.name).join(", ")}) => {
|
|
1040
|
+
${generateBody()}
|
|
1041
|
+
}
|
|
1042
|
+
if (document.uuid == this.uuid) {
|
|
1043
|
+
await applied(context.amount, context.damageType, context.damageMetadata);
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
game.system.documentHooks.set("${name}-" + this.uuid, on${name});
|
|
1047
|
+
Hooks.on("${name}", on${name});
|
|
1048
|
+
`.appendNewLineIfNotEmpty();
|
|
1049
|
+
default:
|
|
1050
|
+
return expandToNode `
|
|
1051
|
+
Hooks.on("${name}", async (${hook.params.map(p => p.name).join(", ")}) => {
|
|
1052
|
+
${generateBody()}
|
|
1053
|
+
});
|
|
1054
|
+
`.appendNewLineIfNotEmpty();
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return expandToNode `
|
|
1058
|
+
_register${document.name}Hooks(document) {
|
|
1059
|
+
${joinToNode(hooks, generateHook, { appendNewLineIfNotEmpty: true })}
|
|
1060
|
+
}
|
|
1061
|
+
`.appendNewLineIfNotEmpty().appendNewLine();
|
|
1062
|
+
}
|
|
1063
|
+
const fileNode = expandToNode `
|
|
1064
|
+
export default class ${entry.config.name}${type} extends ${type} {
|
|
1065
|
+
/** @override */
|
|
1066
|
+
prepareDerivedData() {
|
|
1067
|
+
switch ( this.type ) {
|
|
1068
|
+
${joinToNode(entry.documents.filter(d => type == "Actor" ? isActor(d) : isItem(d)), document => `case "${document.name.toLowerCase()}": return this._prepare${document.name}DerivedData();`, { appendNewLineIfNotEmpty: true })}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/* -------------------------------------------- */
|
|
1073
|
+
|
|
1074
|
+
${joinToNode(entry.documents.filter(d => type == "Actor" ? isActor(d) : isItem(d)), document => generateDerivedData(document), { appendNewLineIfNotEmpty: true })}
|
|
1075
|
+
|
|
1076
|
+
/* -------------------------------------------- */
|
|
1077
|
+
|
|
1078
|
+
${generateDeltas(entry.documents)}
|
|
1079
|
+
|
|
1080
|
+
/* -------------------------------------------- */
|
|
1081
|
+
|
|
1082
|
+
reapplyActiveEffectsForName(name) {
|
|
1083
|
+
if (this.documentName !== "Actor") return;
|
|
1084
|
+
for (const effect of this.appliedEffects) {
|
|
1085
|
+
for (const change of effect.changes) {
|
|
1086
|
+
if (change.key == name) {
|
|
1087
|
+
const changes = effect.apply(this, change);
|
|
1088
|
+
Object.assign(this.overrides, changes);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/* -------------------------------------------- */
|
|
1095
|
+
|
|
1096
|
+
/** @override */
|
|
1097
|
+
_initialize(options = {}) {
|
|
1098
|
+
super._initialize(options);
|
|
1099
|
+
|
|
1100
|
+
switch ( this.type ) {
|
|
1101
|
+
${joinToNode(entry.documents.filter(d => type == "Actor" ? isActor(d) : isItem(d)), document => `case "${document.name.toLowerCase()}": return this._register${document.name}Hooks(this);`, { appendNewLineIfNotEmpty: true })}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/* -------------------------------------------- */
|
|
1106
|
+
|
|
1107
|
+
${joinToNode(entry.documents.filter(d => type == "Actor" ? isActor(d) : isItem(d)), generateDocumentHooks, { appendNewLineIfNotEmpty: true })}
|
|
1108
|
+
|
|
1109
|
+
/* -------------------------------------------- */
|
|
1110
|
+
|
|
1111
|
+
// In order to support per-document type effects, we need to override the allApplicableEffects method to yield virtualized effects with only changes that match the document type
|
|
1112
|
+
/** @override */
|
|
1113
|
+
*allApplicableEffects() {
|
|
1114
|
+
const systemFlags = this.flags["${id}"] ?? {};
|
|
1115
|
+
const edit = systemFlags["edit-mode"] ?? true;
|
|
1116
|
+
|
|
1117
|
+
function getTypedEffect(type, edit, effect, source) {
|
|
1118
|
+
// Pre-build data to avoid mutating ActiveEffect getters (changes is getter-only in v14).
|
|
1119
|
+
const data = foundry.utils.duplicate(effect);
|
|
1120
|
+
data.changes = (data.changes ?? []).filter(c => c.key.startsWith(type));
|
|
1121
|
+
for ( const change of data.changes ) {
|
|
1122
|
+
if (change.mode == 0) continue;
|
|
1123
|
+
change.key = change.key.replace(type + ".", "");
|
|
1124
|
+
}
|
|
1125
|
+
if ( edit ) data.disabled = true;
|
|
1126
|
+
data.flags ??= {};
|
|
1127
|
+
data.flags["${id}"] ??= {};
|
|
1128
|
+
data.flags["${id}"].source = source;
|
|
1129
|
+
return new ActiveEffect(data, {parent: effect.parent});
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
for ( const effect of this.effects ) {
|
|
1133
|
+
yield getTypedEffect(this.type, edit, effect, game.i18n.localize("Self"));
|
|
1134
|
+
}
|
|
1135
|
+
${type == "Actor" ? expandToNode `
|
|
1136
|
+
for ( const item of this.items ) {
|
|
1137
|
+
for ( const effect of item.effects ) {
|
|
1138
|
+
if ( effect.transfer ) yield getTypedEffect(this.type, edit, effect, item.name);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
`.appendNewLine() : ""}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
/* -------------------------------------------- */
|
|
1146
|
+
|
|
1147
|
+
_onCreateDescendantDocuments(parent, collection, documents, data, options, userId) {
|
|
1148
|
+
super._onCreateDescendantDocuments(parent, collection, documents, data, options, userId);
|
|
1149
|
+
|
|
1150
|
+
for (const document of documents) {
|
|
1151
|
+
if (document.documentName !== "ActiveEffect") continue;
|
|
1152
|
+
|
|
1153
|
+
for (const change of document.changes) {
|
|
1154
|
+
if (change.mode != 0) continue;
|
|
1155
|
+
const customMode = foundry.utils.getProperty(document.flags["${id}"], change.key + "-custommode");
|
|
1156
|
+
switch (customMode) {
|
|
1157
|
+
case 1: // Add Once
|
|
1158
|
+
this._effectAddOnce(parent, document, change);
|
|
1159
|
+
break;
|
|
1160
|
+
default:
|
|
1161
|
+
console.error("Unknown custom mode", customMode);
|
|
1162
|
+
break;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/* -------------------------------------------- */
|
|
1169
|
+
|
|
1170
|
+
_effectAddOnce(parent, ae, change) {
|
|
1171
|
+
console.dir("AddOnce", parent, ae, change);
|
|
1172
|
+
|
|
1173
|
+
const key = change.key.replace(parent.type + ".", "");
|
|
1174
|
+
const currentValue = foundry.utils.getProperty(parent, key);
|
|
1175
|
+
|
|
1176
|
+
// Create an update for the parent
|
|
1177
|
+
const update = {
|
|
1178
|
+
[key]: currentValue + parseInt(change.value)
|
|
1179
|
+
};
|
|
1180
|
+
parent.update(update);
|
|
1181
|
+
|
|
1182
|
+
// Create a chat card
|
|
1183
|
+
const chatData = {
|
|
1184
|
+
user: game.user._id,
|
|
1185
|
+
speaker: ChatMessage.getSpeaker({ actor: parent }),
|
|
1186
|
+
content: \`<p>Added "\${ae.name}" once</p>\`
|
|
1187
|
+
};
|
|
1188
|
+
ChatMessage.create(chatData);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/* -------------------------------------------- */
|
|
1192
|
+
|
|
1193
|
+
static async createDialog(data = {}, { parent = null, pack = null, types = null, ...options } = {}) {
|
|
1194
|
+
types ??= game.documentTypes[this.documentName].filter(t => (t !== CONST.BASE_DOCUMENT_TYPE) && (CONFIG[this.documentName].typeCreatables[t] !== false));
|
|
1195
|
+
if (!types.length) return null;
|
|
1196
|
+
|
|
1197
|
+
const collection = parent ? null : pack ? game.packs.get(pack) : game.collections.get(this.documentName);
|
|
1198
|
+
const folders = collection?._formatFolderSelectOptions() ?? [];
|
|
1199
|
+
|
|
1200
|
+
const label = game.i18n.localize(this.metadata.label);
|
|
1201
|
+
const title = game.i18n.format("DOCUMENT.Create", { type: label });
|
|
1202
|
+
const name = data.name || game.i18n.format("DOCUMENT.New", { type: label });
|
|
1203
|
+
|
|
1204
|
+
let type = data.type || CONFIG[this.documentName]?.defaultType;
|
|
1205
|
+
if (!types.includes(type)) type = types[0];
|
|
1206
|
+
|
|
1207
|
+
// If there's only one type, no need to prompt
|
|
1208
|
+
if (types.length === 1) {
|
|
1209
|
+
let createName = this.defaultName();
|
|
1210
|
+
const createData = {
|
|
1211
|
+
name: createName,
|
|
1212
|
+
type
|
|
1213
|
+
};
|
|
1214
|
+
return this.create(createData, { parent, pack, renderSheet: true });
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
const createResponse = await game.system.documentCreateDialog.prompt({
|
|
1218
|
+
type,
|
|
1219
|
+
types: types.reduce((arr, typer) => {
|
|
1220
|
+
arr.push({
|
|
1221
|
+
type: typer,
|
|
1222
|
+
label: game.i18n.has(typer) ? game.i18n.localize(typer) : typer,
|
|
1223
|
+
icon: this.getDefaultArtwork({ type: typer })?.img ?? "icons/svg/item-bag.svg",
|
|
1224
|
+
description: CONFIG[this.documentName]?.typeDescriptions?.[typer] ?? "",
|
|
1225
|
+
selected: type === typer
|
|
1226
|
+
});
|
|
1227
|
+
return arr;
|
|
1228
|
+
}, []).sort((a, b) => a.label.localeCompare(b.label, game.i18n.lang)),
|
|
1229
|
+
name,
|
|
1230
|
+
title,
|
|
1231
|
+
label,
|
|
1232
|
+
folders,
|
|
1233
|
+
folder: data.folder
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
const createData = foundry.utils.mergeObject(data, createResponse, { inplace: false });
|
|
1237
|
+
createData.type = createData.type || type;
|
|
1238
|
+
createData.type = createData.type.toLowerCase();
|
|
1239
|
+
if (!createData.folder) delete createData.folder;
|
|
1240
|
+
if (!createData.name?.trim()) createData.name = this.defaultName();
|
|
1241
|
+
return this.create(createData, { parent, pack, renderSheet: true });
|
|
1242
|
+
|
|
1243
|
+
const content = await renderTemplate("systems/${id}/system/templates/document-create.hbs", {
|
|
1244
|
+
folders, name, type,
|
|
1245
|
+
folder: data.folder,
|
|
1246
|
+
hasFolders: folders.length > 0,
|
|
1247
|
+
types: types.reduce((arr, typer) => {
|
|
1248
|
+
arr.push({
|
|
1249
|
+
type: typer,
|
|
1250
|
+
label: game.i18n.has(typer) ? game.i18n.localize(typer) : typer,
|
|
1251
|
+
icon: this.getDefaultArtwork({ type: typer })?.img ?? "icons/svg/item-bag.svg",
|
|
1252
|
+
description: CONFIG[this.documentName]?.typeDescriptions?.[typer] ?? "",
|
|
1253
|
+
selected: type === typer
|
|
1254
|
+
});
|
|
1255
|
+
return arr;
|
|
1256
|
+
}, []).sort((a, b) => a.label.localeCompare(b.label, game.i18n.lang))
|
|
1257
|
+
});
|
|
1258
|
+
return Dialog.prompt({
|
|
1259
|
+
title, content,
|
|
1260
|
+
label: title,
|
|
1261
|
+
render: html => {
|
|
1262
|
+
const app = html.closest(".app");
|
|
1263
|
+
const folder = app.querySelector("select");
|
|
1264
|
+
if (folder) app.querySelector(".dialog-buttons").insertAdjacentElement("afterbegin", folder);
|
|
1265
|
+
app.querySelectorAll(".window-header .header-button").forEach(btn => {
|
|
1266
|
+
const label = btn.innerText;
|
|
1267
|
+
const icon = btn.querySelector("i");
|
|
1268
|
+
btn.innerHTML = icon.outerHTML;
|
|
1269
|
+
btn.dataset.tooltip = label;
|
|
1270
|
+
btn.setAttribute("aria-label", label);
|
|
1271
|
+
});
|
|
1272
|
+
app.querySelector(".document-name").select();
|
|
1273
|
+
},
|
|
1274
|
+
callback: html => {
|
|
1275
|
+
const form = html.querySelector("form");
|
|
1276
|
+
const fd = new FormDataExtended(form);
|
|
1277
|
+
const createData = foundry.utils.mergeObject(data, fd.object, { inplace: false });
|
|
1278
|
+
|
|
1279
|
+
},
|
|
1280
|
+
rejectClose: false,
|
|
1281
|
+
options: { ...options, jQuery: false, width: 700, height: 'auto', classes: ["${id}", "create-document", "dialog"] }
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/* -------------------------------------------- */
|
|
1286
|
+
|
|
1287
|
+
static getDefaultArtwork(itemData = {}) {
|
|
1288
|
+
const { type } = itemData;
|
|
1289
|
+
const { img, texture } = super.getDefaultArtwork(itemData);
|
|
1290
|
+
return { img: CONFIG[this.documentName]?.typeArtworks?.[type] ?? img, texture: texture };
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
/* -------------------------------------------- */
|
|
1294
|
+
|
|
1295
|
+
getRollData() {
|
|
1296
|
+
const data = super.getRollData();
|
|
1297
|
+
const rollData = foundry.utils.duplicate(data);
|
|
1298
|
+
rollData.system = this.system;
|
|
1299
|
+
return rollData;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
/* -------------------------------------------- */
|
|
1303
|
+
|
|
1304
|
+
/** @override */
|
|
1305
|
+
async modifyTokenAttribute(attribute, value, isDelta, isBar) {
|
|
1306
|
+
const resource = foundry.utils.getProperty(this.system, attribute);
|
|
1307
|
+
|
|
1308
|
+
if (isDelta && value < 0) {
|
|
1309
|
+
// Apply to temp first
|
|
1310
|
+
resource.temp += value;
|
|
1311
|
+
|
|
1312
|
+
// If temp is negative, apply to value
|
|
1313
|
+
if (resource.temp < 0) {
|
|
1314
|
+
resource.value += resource.temp;
|
|
1315
|
+
resource.temp = 0;
|
|
1316
|
+
}
|
|
1317
|
+
await this.update({ ["system." + attribute]: resource });
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
return super.modifyTokenAttribute(attribute, value, isDelta, isBar);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
${type == "Actor" ? expandToNode `
|
|
1325
|
+
/* -------------------------------------------- */
|
|
1326
|
+
|
|
1327
|
+
getInitiativeFormula() {
|
|
1328
|
+
switch ( this.type ) {
|
|
1329
|
+
${joinToNode(entry.documents.filter(d => isActor(d)), document => generateInitiativeFormula(document), { appendNewLineIfNotEmpty: true })}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
`.appendNewLine() : ""}
|
|
1333
|
+
}
|
|
1334
|
+
`.appendNewLineIfNotEmpty();
|
|
1335
|
+
fs.writeFileSync(generatedFilePath, toString(fileNode));
|
|
1336
|
+
}
|
|
1337
|
+
generateExtendedDocumentClass("Actor", entry);
|
|
1338
|
+
generateExtendedDocumentClass("Item", entry);
|
|
1339
|
+
}
|
|
1340
|
+
//# sourceMappingURL=derived-data-generator.js.map
|