intelligent-system-design-language 0.3.21 → 0.3.23

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.
Files changed (116) hide show
  1. package/.claude/agents/langium-language-designer.md +38 -38
  2. package/.claude/agents/typescript-vscode-expert.md +29 -29
  3. package/.claude/agents/ui-ux-designer.md +36 -36
  4. package/.claude/settings.local.json +33 -33
  5. package/.idea/inspectionProfiles/Project_Default.xml +6 -6
  6. package/.idea/isdl.iml +13 -13
  7. package/.idea/modules.xml +8 -8
  8. package/.idea/vcs.xml +6 -6
  9. package/.idea/watcherTasks.xml +3 -3
  10. package/.vscodeignore +18 -18
  11. package/LICENSE +673 -673
  12. package/README.md +86 -86
  13. package/bin/cli.js +4 -4
  14. package/bin/lsp.js +8 -8
  15. package/out/_backgrounds.scss +91 -91
  16. package/out/_handlebars.scss +497 -497
  17. package/out/_isdlStyles.scss +1444 -1381
  18. package/out/_vuetifyOverrides.scss +425 -425
  19. package/out/_vuetifyStyles.scss +31957 -31957
  20. package/out/cli/components/_backgrounds.scss +91 -91
  21. package/out/cli/components/_handlebars.scss +497 -497
  22. package/out/cli/components/_isdlStyles.scss +1444 -1381
  23. package/out/cli/components/_vuetifyOverrides.scss +425 -425
  24. package/out/cli/components/_vuetifyStyles.scss +31957 -31957
  25. package/out/cli/components/active-effect-sheet-generator.js +453 -453
  26. package/out/cli/components/chat-card-generator.js +654 -651
  27. package/out/cli/components/chat-card-generator.js.map +1 -1
  28. package/out/cli/components/css-generator.js +4 -4
  29. package/out/cli/components/damage-roll-generator.js +160 -160
  30. package/out/cli/components/datamodel-generator.js +264 -257
  31. package/out/cli/components/datamodel-generator.js.map +1 -1
  32. package/out/cli/components/derived-data-generator.js +923 -923
  33. package/out/cli/components/hotbar-drop-hook-generator.js +82 -82
  34. package/out/cli/components/init-hook-generator.js +495 -495
  35. package/out/cli/components/language-generator.js +1 -1
  36. package/out/cli/components/language-generator.js.map +1 -1
  37. package/out/cli/components/measured-template-preview.js +221 -221
  38. package/out/cli/components/method-generator.js +979 -887
  39. package/out/cli/components/method-generator.js.map +1 -1
  40. package/out/cli/components/ready-hook-generator.js +404 -404
  41. package/out/cli/components/token-generator.js +116 -116
  42. package/out/cli/components/vue/base-components/vue-attribute.js +138 -138
  43. package/out/cli/components/vue/base-components/vue-boolean.js +64 -64
  44. package/out/cli/components/vue/base-components/vue-calculator.js +93 -93
  45. package/out/cli/components/vue/base-components/vue-damage-application.js +356 -356
  46. package/out/cli/components/vue/base-components/vue-damage-bonuses.js +165 -165
  47. package/out/cli/components/vue/base-components/vue-damage-resistances.js +196 -196
  48. package/out/cli/components/vue/base-components/vue-damage-track.js +121 -121
  49. package/out/cli/components/vue/base-components/vue-date-time.js +42 -42
  50. package/out/cli/components/vue/base-components/vue-dice.js +98 -98
  51. package/out/cli/components/vue/base-components/vue-die.js +73 -73
  52. package/out/cli/components/vue/base-components/vue-document-choice.js +149 -149
  53. package/out/cli/components/vue/base-components/vue-document-choices.js +179 -179
  54. package/out/cli/components/vue/base-components/vue-document-link.js +60 -60
  55. package/out/cli/components/vue/base-components/vue-extended-choice.js +88 -88
  56. package/out/cli/components/vue/base-components/vue-inventory.js +519 -519
  57. package/out/cli/components/vue/base-components/vue-macro-choice.js +138 -138
  58. package/out/cli/components/vue/base-components/vue-measured-template.js +530 -530
  59. package/out/cli/components/vue/base-components/vue-money.js +483 -483
  60. package/out/cli/components/vue/base-components/vue-number.js +174 -174
  61. package/out/cli/components/vue/base-components/vue-paperdoll.js +43 -43
  62. package/out/cli/components/vue/base-components/vue-parent-property-reference.js +76 -76
  63. package/out/cli/components/vue/base-components/vue-prosemirror.js +18 -18
  64. package/out/cli/components/vue/base-components/vue-resource.js +136 -136
  65. package/out/cli/components/vue/base-components/vue-roll-visualizer.js +286 -109
  66. package/out/cli/components/vue/base-components/vue-roll-visualizer.js.map +1 -1
  67. package/out/cli/components/vue/base-components/vue-self-property-reference.js +62 -62
  68. package/out/cli/components/vue/base-components/vue-string-choice.js +98 -98
  69. package/out/cli/components/vue/base-components/vue-string-choices.js +203 -203
  70. package/out/cli/components/vue/base-components/vue-string.js +60 -60
  71. package/out/cli/components/vue/base-components/vue-text-field.js +53 -53
  72. package/out/cli/components/vue/base-components/vue-tracker.js +431 -431
  73. package/out/cli/components/vue/vue-action-component-generator.js +64 -64
  74. package/out/cli/components/vue/vue-active-effect-sheet-generator.js +856 -856
  75. package/out/cli/components/vue/vue-datatable-sheet-class-generator.js +292 -292
  76. package/out/cli/components/vue/vue-datatable2-component-generator.js +824 -824
  77. package/out/cli/components/vue/vue-document-creation-app.js +121 -121
  78. package/out/cli/components/vue/vue-document-creation-sheet.js +94 -94
  79. package/out/cli/components/vue/vue-generator.js +40 -40
  80. package/out/cli/components/vue/vue-mixin.js +296 -296
  81. package/out/cli/components/vue/vue-pinned-datatable-component-generator.js +260 -260
  82. package/out/cli/components/vue/vue-prompt-generator.js +91 -76
  83. package/out/cli/components/vue/vue-prompt-generator.js.map +1 -1
  84. package/out/cli/components/vue/vue-prompt-sheet-class-generator.js +317 -317
  85. package/out/cli/components/vue/vue-sheet-application-generator.js +1177 -1167
  86. package/out/cli/components/vue/vue-sheet-application-generator.js.map +1 -1
  87. package/out/cli/components/vue/vue-sheet-class-generator.js +510 -510
  88. package/out/cli/generator.js +438 -433
  89. package/out/cli/generator.js.map +1 -1
  90. package/out/extension/github/githubAuthProvider.js +71 -29
  91. package/out/extension/github/githubAuthProvider.js.map +1 -1
  92. package/out/extension/github/githubGistManager.js +4 -3
  93. package/out/extension/github/githubGistManager.js.map +1 -1
  94. package/out/extension/github/githubManager.js +40 -38
  95. package/out/extension/github/githubManager.js.map +1 -1
  96. package/out/extension/github/githubQuickActions.js +120 -120
  97. package/out/extension/github/system-workflow.yml +47 -47
  98. package/out/extension/main.cjs +909 -532
  99. package/out/extension/main.cjs.map +3 -3
  100. package/out/extension/package.json +419 -419
  101. package/out/language/generated/ast.js +51 -2
  102. package/out/language/generated/ast.js.map +1 -1
  103. package/out/language/generated/grammar.js +14240 -13991
  104. package/out/language/generated/grammar.js.map +1 -1
  105. package/out/language/intelligent-system-design-language-validator.js +32 -2
  106. package/out/language/intelligent-system-design-language-validator.js.map +1 -1
  107. package/out/language/isdl-scope-provider.js +14 -1
  108. package/out/language/isdl-scope-provider.js.map +1 -1
  109. package/out/language/main.cjs +913 -569
  110. package/out/language/main.cjs.map +3 -3
  111. package/out/package.json +419 -419
  112. package/out/progressbar.min.js +6 -6
  113. package/out/styles.scss +762 -747
  114. package/out/test/validating/diagnostics.test.js +40 -0
  115. package/out/test/validating/diagnostics.test.js.map +1 -1
  116. package/package.json +419 -419
@@ -13,11 +13,11 @@ export function generateInitHookMjs(entry, id, destination) {
13
13
  }
14
14
  function generateTrackableResourceBars(document) {
15
15
  let resourceExps = getAllOfType(document.body, isResourceExp);
16
- return expandToNode `
17
- "${document.name.toLowerCase()}": {
18
- "bar": [${joinToNode(resourceExps, x => `"${x.name.toLowerCase()}"`, { separator: ',' })}],
19
- "value": []
20
- }
16
+ return expandToNode `
17
+ "${document.name.toLowerCase()}": {
18
+ "bar": [${joinToNode(resourceExps, x => `"${x.name.toLowerCase()}"`, { separator: ',' })}],
19
+ "value": []
20
+ }
21
21
  `;
22
22
  }
23
23
  function generateStatusEffect(document) {
@@ -30,15 +30,15 @@ export function generateInitHookMjs(entry, id, destination) {
30
30
  svg = "icons/svg/upgrade.svg";
31
31
  }
32
32
  let calculated = document.params.find(p => isStatusParamWhen(p)) ? true : false;
33
- return expandToNode `
34
- {
35
- id: "${document.name.toLowerCase()}",
36
- name: "${document.name}",
37
- label: "${document.name}",
38
- icon: "${svg}",
39
- img: "${svg}",
40
- calculated: ${calculated}
41
- }
33
+ return expandToNode `
34
+ {
35
+ id: "${document.name.toLowerCase()}",
36
+ name: "${document.name}",
37
+ label: "${document.name}",
38
+ icon: "${svg}",
39
+ img: "${svg}",
40
+ calculated: ${calculated}
41
+ }
42
42
  `;
43
43
  }
44
44
  function generateKeywordConfig(entry) {
@@ -46,13 +46,13 @@ export function generateInitHookMjs(entry, id, destination) {
46
46
  if (keywords.size === 0) {
47
47
  return expandToNode ``;
48
48
  }
49
- return joinToNode(Array.from(keywords.entries()), ([key, data]) => `${key}: {
50
- name: "${data.name}",
51
- description: "${data.description.replace(/"/g, '\\"')}",
52
- color: "${data.color}",
53
- icon: "${data.icon}",
54
- type: "${data.type}"${data.label ? `,
55
- label: "${data.label}"` : ''}
49
+ return joinToNode(Array.from(keywords.entries()), ([key, data]) => `${key}: {
50
+ name: "${data.name}",
51
+ description: "${data.description.replace(/"/g, '\\"')}",
52
+ color: "${data.color}",
53
+ icon: "${data.icon}",
54
+ type: "${data.type}"${data.label ? `,
55
+ label: "${data.label}"` : ''}
56
56
  }`, { separator: ',\n ' }) || expandToNode ``;
57
57
  }
58
58
  function generateMacroActionConfig(entry) {
@@ -74,24 +74,24 @@ export function generateInitHookMjs(entry, id, destination) {
74
74
  function generateRegisterStatusEffects() {
75
75
  let statusEffects = getAllOfType(entry.documents, isStatusProperty, false);
76
76
  if (statusEffects.length === 0)
77
- return expandToNode `
78
- function registerStatusEffects() {
79
- game.system.statusEffects = [];
80
- }
77
+ return expandToNode `
78
+ function registerStatusEffects() {
79
+ game.system.statusEffects = [];
80
+ }
81
81
  `;
82
82
  let hasDead = statusEffects.find(x => x.name.toLowerCase() === "dead");
83
- return expandToNode `
84
- function registerStatusEffects() {
85
- CONFIG.statusEffects = [
86
- ${hasDead ? '' : `{"id":"dead","name":"EFFECT.StatusDead","img":"icons/svg/skull.svg"},`}
87
- {"id":"unconscious","name":"EFFECT.StatusUnconscious","img":"icons/svg/unconscious.svg"},
88
- {"id":"invisible","name":"EFFECT.StatusInvisible","img":"icons/svg/invisible.svg"},
89
- ${joinToNode(statusEffects, generateStatusEffect, { appendNewLineIfNotEmpty: true, separator: ',' })}
90
- ];
91
-
92
- // Also store for journal generation
93
- game.system.statusEffects = CONFIG.statusEffects;
94
- }
83
+ return expandToNode `
84
+ function registerStatusEffects() {
85
+ CONFIG.statusEffects = [
86
+ ${hasDead ? '' : `{"id":"dead","name":"EFFECT.StatusDead","img":"icons/svg/skull.svg"},`}
87
+ {"id":"unconscious","name":"EFFECT.StatusUnconscious","img":"icons/svg/unconscious.svg"},
88
+ {"id":"invisible","name":"EFFECT.StatusInvisible","img":"icons/svg/invisible.svg"},
89
+ ${joinToNode(statusEffects, generateStatusEffect, { appendNewLineIfNotEmpty: true, separator: ',' })}
90
+ ];
91
+
92
+ // Also store for journal generation
93
+ game.system.statusEffects = CONFIG.statusEffects;
94
+ }
95
95
  `.appendNewLine();
96
96
  }
97
97
  let actorDocs = entry.documents.filter(d => isActor(d)).map(d => d);
@@ -127,474 +127,474 @@ export function generateInitHookMjs(entry, id, destination) {
127
127
  return `${document.name.toLowerCase()}${identity}: ${document.name}${identity}PromptApp`;
128
128
  }), { appendNewLineIfNotEmpty: true, separator: ',' });
129
129
  }
130
- const fileNode = expandToNode `
131
- ${joinToNode(entry.documents, document => `import ${document.name}TypeDataModel from "../datamodels/${isActor(document) ? "actor" : "item"}/${document.name.toLowerCase()}.mjs"`, { appendNewLineIfNotEmpty: true })}
132
- ${joinToNode(entry.documents, document => `import ${document.name}VueSheet from "../sheets/vue/${isActor(document) ? "actor" : "item"}/${document.name.toLowerCase()}-sheet.mjs"`, { appendNewLineIfNotEmpty: true })}
133
- import DataTableApp from "../sheets/vue/datatable-app.mjs";
134
- ${joinToNode(entry.documents, generateDocumentPromptImports, { appendNewLineIfNotEmpty: true })}
135
- import ${entry.config.name}EffectVueSheet from "../sheets/vue/active-effect-sheet.mjs";
136
- import ${entry.config.name}Actor from "../documents/actor.mjs";
137
- import ${entry.config.name}Item from "../documents/item.mjs";
138
- import ${entry.config.name}Combatant from "../documents/combatant.mjs";
139
- import ${entry.config.name}TokenDocument from "../documents/token.mjs";
140
- import ${entry.config.name}Token from "../canvas/token.mjs";
141
- import ${entry.config.name}Roll from "../rolls/roll.mjs";
142
- import ${entry.config.name}DamageRoll from "../rolls/damage-roll.mjs";
143
- import DocumentCreationVueDialog from "../sheets/vue/document-creation-dialog.mjs";
144
- import MeasuredTemplatePreview from "../placeables/measured-template-preview.mjs";
145
-
146
- export function init() {
147
- console.log('${id} | Initializing System');
148
-
149
- CONFIG.ActiveEffect.legacyTransferral = false;
150
-
151
- registerSettings();
152
- registerDataModels();
153
- registerDocumentSheets();
154
- registerDocumentClasses();
155
- registerPromptClasses();
156
- registerCanvasClasses();
157
- registerTypeInfo();
158
- registerHandlebarsHelpers();
159
- registerResourceBars();
160
- registerStatusEffects();
161
- registerMacroActions();
162
- registerKeywords();
163
- setupKeywordEnricher();
164
- registerUtils();
165
- //addVueImportMap();
166
-
167
- game.system.documentHooks = new Map();
168
- game.system.datatableApp = DataTableApp;
169
- game.system.rollClass = ${entry.config.name}Roll;
170
- game.system.damageRollClass = ${entry.config.name}DamageRoll;
171
- CONFIG.Dice.rolls.push(${entry.config.name}Roll);
172
- CONFIG.Dice.rolls.push(${entry.config.name}DamageRoll);
173
- }
174
-
175
- /* -------------------------------------------- */
176
-
177
- function registerSettings() {
178
-
179
- game.settings.register('${id}', 'createSystemJournal', {
180
- name: game.i18n.localize("SETTINGS.CreateSystemJournalName"),
181
- hint: game.i18n.localize("SETTINGS.CreateSystemJournalHint"),
182
- scope: 'world',
183
- config: true,
184
- default: true,
185
- type: Boolean
186
- });
187
-
188
- game.settings.register('${id}', 'roundUpDamageApplication', {
189
- name: game.i18n.localize("SETTINGS.RoundUpDamageApplicationName"),
190
- hint: game.i18n.localize("SETTINGS.RoundUpDamageApplicationHint"),
191
- scope: 'world',
192
- config: true,
193
- default: true,
194
- type: Boolean
195
- });
196
-
197
- game.settings.register('${id}', 'allowTargetDamageApplication', {
198
- name: game.i18n.localize('SETTINGS.AllowTargetDamageApplicationName'),
199
- hint: game.i18n.localize('SETTINGS.AllowTargetDamageApplicationHint'),
200
- scope: 'world',
201
- config: true,
202
- default: false,
203
- type: Boolean,
204
- requiresReload: true
205
- });
206
-
207
- game.settings.register('${id}', 'userTargetDamageApplicationType', {
208
- scope: 'client',
209
- config: false,
210
- default: 'selected',
211
- type: String
212
- });
213
-
214
- game.settings.register('${id}', 'damageApplicationChatCard', {
215
- name: game.i18n.localize('SETTINGS.DamageApplicationChatCardName'),
216
- hint: game.i18n.localize('SETTINGS.DamageApplicationChatCardHint'),
217
- scope: 'world',
218
- config: true,
219
- default: 'gm',
220
- type: String,
221
- choices: {
222
- 'none': game.i18n.localize('SETTINGS.DamageApplicationChatCard.None'),
223
- 'public': game.i18n.localize('SETTINGS.DamageApplicationChatCard.Public'),
224
- 'gm': game.i18n.localize('SETTINGS.DamageApplicationChatCard.GM')
225
- }
226
- });
227
-
228
- game.settings.register('${id}', 'hotReloadLastState', {
229
- scope: 'client',
230
- config: false,
231
- default: { openWindows: [] },
232
- type: Object
233
- });
234
-
235
- game.settings.register('${id}', 'documentColorThemes', {
236
- scope: 'client',
237
- config: false,
238
- default: {},
239
- type: Object
240
- });
241
-
242
- game.settings.register('${id}', 'documentLastState', {
243
- scope: 'client',
244
- config: false,
245
- default: {},
246
- type: Object
247
- });
248
-
249
- game.settings.register('${id}', 'documentTableColumns', {
250
- scope: 'client',
251
- config: false,
252
- default: {},
253
- type: Object
254
- });
255
- }
256
-
257
- /* -------------------------------------------- */
258
-
259
- function registerDataModels() {
260
- CONFIG.Actor.dataModels = {
261
- ${joinToNode(entry.documents.filter(d => isActor(d)), document => `${document.name.toLowerCase()}: ${document.name}TypeDataModel`, { appendNewLineIfNotEmpty: true, separator: ',' })}
262
- };
263
-
264
- CONFIG.Item.dataModels = {
265
- ${joinToNode(entry.documents.filter(d => isItem(d)), document => `${document.name.toLowerCase()}: ${document.name}TypeDataModel`, { appendNewLineIfNotEmpty: true, separator: ',' })}
266
- };
267
- }
268
-
269
- /* -------------------------------------------- */
270
-
271
- function registerDocumentSheets() {
272
- Actors.unregisterSheet("core", ActorSheet);
273
- Items.unregisterSheet("core", ItemSheet);
274
-
275
- // Actors
276
- ${joinToNode(entry.documents.filter(d => isActor(d)), document => `Actors.registerSheet("${id}", ${document.name}VueSheet, {types: ["${document.name.toLowerCase()}"], makeDefault: true});`, { appendNewLineIfNotEmpty: true })}
277
-
278
- // Items
279
- ${joinToNode(entry.documents.filter(d => isItem(d)), document => `Items.registerSheet("${id}", ${document.name}VueSheet, {types: ["${document.name.toLowerCase()}"], makeDefault: true});`, { appendNewLineIfNotEmpty: true })}
280
-
281
- // Active Effects
282
- DocumentSheetConfig.registerSheet(ActiveEffect, "${id}", ${entry.config.name}EffectVueSheet, { makeDefault: true });
283
- }
284
-
285
- /* -------------------------------------------- */
286
-
287
- function registerDocumentClasses() {
288
- CONFIG.Actor.documentClass = ${entry.config.name}Actor;
289
- CONFIG.Item.documentClass = ${entry.config.name}Item;
290
- CONFIG.Combatant.documentClass = ${entry.config.name}Combatant;
291
- CONFIG.Token.documentClass = ${entry.config.name}TokenDocument;
292
-
293
- game.system.measuredTemplatePreviewClass = MeasuredTemplatePreview;
294
- }
295
-
296
- /* -------------------------------------------- */
297
-
298
- function registerPromptClasses() {
299
- game.system.prompts = {
300
- ${joinToNode(entry.documents, document => generateDocumentPromptAssignment(document), { separator: ',\n' })}
301
- };
302
- game.system.documentCreateDialog = DocumentCreationVueDialog;
303
- }
304
-
305
- /* -------------------------------------------- */
306
-
307
- function registerCanvasClasses() {
308
- CONFIG.Token.objectClass = ${entry.config.name}Token;
309
- }
310
-
311
- /* -------------------------------------------- */
312
-
313
- function registerTypeInfo() {
314
- CONFIG.Actor.defaultType = "${actorDefaultType}";
315
- CONFIG.Item.defaultType = "${itemDefaultType}";
316
-
317
- CONFIG.Actor.typeArtworks = {
130
+ const fileNode = expandToNode `
131
+ ${joinToNode(entry.documents, document => `import ${document.name}TypeDataModel from "../datamodels/${isActor(document) ? "actor" : "item"}/${document.name.toLowerCase()}.mjs"`, { appendNewLineIfNotEmpty: true })}
132
+ ${joinToNode(entry.documents, document => `import ${document.name}VueSheet from "../sheets/vue/${isActor(document) ? "actor" : "item"}/${document.name.toLowerCase()}-sheet.mjs"`, { appendNewLineIfNotEmpty: true })}
133
+ import DataTableApp from "../sheets/vue/datatable-app.mjs";
134
+ ${joinToNode(entry.documents, generateDocumentPromptImports, { appendNewLineIfNotEmpty: true })}
135
+ import ${entry.config.name}EffectVueSheet from "../sheets/vue/active-effect-sheet.mjs";
136
+ import ${entry.config.name}Actor from "../documents/actor.mjs";
137
+ import ${entry.config.name}Item from "../documents/item.mjs";
138
+ import ${entry.config.name}Combatant from "../documents/combatant.mjs";
139
+ import ${entry.config.name}TokenDocument from "../documents/token.mjs";
140
+ import ${entry.config.name}Token from "../canvas/token.mjs";
141
+ import ${entry.config.name}Roll from "../rolls/roll.mjs";
142
+ import ${entry.config.name}DamageRoll from "../rolls/damage-roll.mjs";
143
+ import DocumentCreationVueDialog from "../sheets/vue/document-creation-dialog.mjs";
144
+ import MeasuredTemplatePreview from "../placeables/measured-template-preview.mjs";
145
+
146
+ export function init() {
147
+ console.log('${id} | Initializing System');
148
+
149
+ CONFIG.ActiveEffect.legacyTransferral = false;
150
+
151
+ registerSettings();
152
+ registerDataModels();
153
+ registerDocumentSheets();
154
+ registerDocumentClasses();
155
+ registerPromptClasses();
156
+ registerCanvasClasses();
157
+ registerTypeInfo();
158
+ registerHandlebarsHelpers();
159
+ registerResourceBars();
160
+ registerStatusEffects();
161
+ registerMacroActions();
162
+ registerKeywords();
163
+ setupKeywordEnricher();
164
+ registerUtils();
165
+ //addVueImportMap();
166
+
167
+ game.system.documentHooks = new Map();
168
+ game.system.datatableApp = DataTableApp;
169
+ game.system.rollClass = ${entry.config.name}Roll;
170
+ game.system.damageRollClass = ${entry.config.name}DamageRoll;
171
+ CONFIG.Dice.rolls.push(${entry.config.name}Roll);
172
+ CONFIG.Dice.rolls.push(${entry.config.name}DamageRoll);
173
+ }
174
+
175
+ /* -------------------------------------------- */
176
+
177
+ function registerSettings() {
178
+
179
+ game.settings.register('${id}', 'createSystemJournal', {
180
+ name: game.i18n.localize("SETTINGS.CreateSystemJournalName"),
181
+ hint: game.i18n.localize("SETTINGS.CreateSystemJournalHint"),
182
+ scope: 'world',
183
+ config: true,
184
+ default: true,
185
+ type: Boolean
186
+ });
187
+
188
+ game.settings.register('${id}', 'roundUpDamageApplication', {
189
+ name: game.i18n.localize("SETTINGS.RoundUpDamageApplicationName"),
190
+ hint: game.i18n.localize("SETTINGS.RoundUpDamageApplicationHint"),
191
+ scope: 'world',
192
+ config: true,
193
+ default: true,
194
+ type: Boolean
195
+ });
196
+
197
+ game.settings.register('${id}', 'allowTargetDamageApplication', {
198
+ name: game.i18n.localize('SETTINGS.AllowTargetDamageApplicationName'),
199
+ hint: game.i18n.localize('SETTINGS.AllowTargetDamageApplicationHint'),
200
+ scope: 'world',
201
+ config: true,
202
+ default: false,
203
+ type: Boolean,
204
+ requiresReload: true
205
+ });
206
+
207
+ game.settings.register('${id}', 'userTargetDamageApplicationType', {
208
+ scope: 'client',
209
+ config: false,
210
+ default: 'selected',
211
+ type: String
212
+ });
213
+
214
+ game.settings.register('${id}', 'damageApplicationChatCard', {
215
+ name: game.i18n.localize('SETTINGS.DamageApplicationChatCardName'),
216
+ hint: game.i18n.localize('SETTINGS.DamageApplicationChatCardHint'),
217
+ scope: 'world',
218
+ config: true,
219
+ default: 'gm',
220
+ type: String,
221
+ choices: {
222
+ 'none': game.i18n.localize('SETTINGS.DamageApplicationChatCard.None'),
223
+ 'public': game.i18n.localize('SETTINGS.DamageApplicationChatCard.Public'),
224
+ 'gm': game.i18n.localize('SETTINGS.DamageApplicationChatCard.GM')
225
+ }
226
+ });
227
+
228
+ game.settings.register('${id}', 'hotReloadLastState', {
229
+ scope: 'client',
230
+ config: false,
231
+ default: { openWindows: [] },
232
+ type: Object
233
+ });
234
+
235
+ game.settings.register('${id}', 'documentColorThemes', {
236
+ scope: 'client',
237
+ config: false,
238
+ default: {},
239
+ type: Object
240
+ });
241
+
242
+ game.settings.register('${id}', 'documentLastState', {
243
+ scope: 'client',
244
+ config: false,
245
+ default: {},
246
+ type: Object
247
+ });
248
+
249
+ game.settings.register('${id}', 'documentTableColumns', {
250
+ scope: 'client',
251
+ config: false,
252
+ default: {},
253
+ type: Object
254
+ });
255
+ }
256
+
257
+ /* -------------------------------------------- */
258
+
259
+ function registerDataModels() {
260
+ CONFIG.Actor.dataModels = {
261
+ ${joinToNode(entry.documents.filter(d => isActor(d)), document => `${document.name.toLowerCase()}: ${document.name}TypeDataModel`, { appendNewLineIfNotEmpty: true, separator: ',' })}
262
+ };
263
+
264
+ CONFIG.Item.dataModels = {
265
+ ${joinToNode(entry.documents.filter(d => isItem(d)), document => `${document.name.toLowerCase()}: ${document.name}TypeDataModel`, { appendNewLineIfNotEmpty: true, separator: ',' })}
266
+ };
267
+ }
268
+
269
+ /* -------------------------------------------- */
270
+
271
+ function registerDocumentSheets() {
272
+ Actors.unregisterSheet("core", ActorSheet);
273
+ Items.unregisterSheet("core", ItemSheet);
274
+
275
+ // Actors
276
+ ${joinToNode(entry.documents.filter(d => isActor(d)), document => `Actors.registerSheet("${id}", ${document.name}VueSheet, {types: ["${document.name.toLowerCase()}"], makeDefault: true});`, { appendNewLineIfNotEmpty: true })}
277
+
278
+ // Items
279
+ ${joinToNode(entry.documents.filter(d => isItem(d)), document => `Items.registerSheet("${id}", ${document.name}VueSheet, {types: ["${document.name.toLowerCase()}"], makeDefault: true});`, { appendNewLineIfNotEmpty: true })}
280
+
281
+ // Active Effects
282
+ DocumentSheetConfig.registerSheet(ActiveEffect, "${id}", ${entry.config.name}EffectVueSheet, { makeDefault: true });
283
+ }
284
+
285
+ /* -------------------------------------------- */
286
+
287
+ function registerDocumentClasses() {
288
+ CONFIG.Actor.documentClass = ${entry.config.name}Actor;
289
+ CONFIG.Item.documentClass = ${entry.config.name}Item;
290
+ CONFIG.Combatant.documentClass = ${entry.config.name}Combatant;
291
+ CONFIG.Token.documentClass = ${entry.config.name}TokenDocument;
292
+
293
+ game.system.measuredTemplatePreviewClass = MeasuredTemplatePreview;
294
+ }
295
+
296
+ /* -------------------------------------------- */
297
+
298
+ function registerPromptClasses() {
299
+ game.system.prompts = {
300
+ ${joinToNode(entry.documents, document => generateDocumentPromptAssignment(document), { separator: ',\n' })}
301
+ };
302
+ game.system.documentCreateDialog = DocumentCreationVueDialog;
303
+ }
304
+
305
+ /* -------------------------------------------- */
306
+
307
+ function registerCanvasClasses() {
308
+ CONFIG.Token.objectClass = ${entry.config.name}Token;
309
+ }
310
+
311
+ /* -------------------------------------------- */
312
+
313
+ function registerTypeInfo() {
314
+ CONFIG.Actor.defaultType = "${actorDefaultType}";
315
+ CONFIG.Item.defaultType = "${itemDefaultType}";
316
+
317
+ CONFIG.Actor.typeArtworks = {
318
318
  ${joinToNode(actorArtworks, document => {
319
319
  var _a, _b;
320
320
  const path = (_b = (_a = document.params.find(p => isDocumentSvgParam(p))) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '';
321
321
  const fullPath = path.startsWith('/icons/') ? path : `systems/${id}/${path}`;
322
322
  return `"${document.name.toLowerCase()}": "${fullPath}"`;
323
- }, { appendNewLineIfNotEmpty: true, separator: ',' })}
324
- };
325
-
326
- CONFIG.Item.typeArtworks = {
323
+ }, { appendNewLineIfNotEmpty: true, separator: ',' })}
324
+ };
325
+
326
+ CONFIG.Item.typeArtworks = {
327
327
  ${joinToNode(itemArtworks, document => {
328
328
  var _a, _b;
329
329
  const path = (_b = (_a = document.params.find(p => isDocumentSvgParam(p))) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '';
330
330
  const fullPath = path.startsWith('/icons/') ? path : `systems/${id}/${path}`;
331
331
  return `"${document.name.toLowerCase()}": "${fullPath}"`;
332
- }, { appendNewLineIfNotEmpty: true, separator: ',' })}
333
- };
334
-
335
- CONFIG.Actor.typeDescriptions = {
336
- ${joinToNode(actorDescriptions, document => { var _a; return `"${document.name.toLowerCase()}": "${(_a = document.params.find(p => isDocumentDescriptionParam(p))) === null || _a === void 0 ? void 0 : _a.value}"`; }, { appendNewLineIfNotEmpty: true, separator: ',' })}
337
- }
338
- CONFIG.Item.typeDescriptions = {
339
- ${joinToNode(itemDescriptions, document => { var _a; return `"${document.name.toLowerCase()}": "${(_a = document.params.find(p => isDocumentDescriptionParam(p))) === null || _a === void 0 ? void 0 : _a.value}"`; }, { appendNewLineIfNotEmpty: true, separator: ',' })}
340
- }
341
-
342
- CONFIG.Actor.typeCreatables = {
343
- ${joinToNode(actorCreatables, document => { var _a; return `"${document.name.toLowerCase()}": ${(_a = document.params.find(p => isDocumentCreatableParam(p))) === null || _a === void 0 ? void 0 : _a.value}`; }, { appendNewLineIfNotEmpty: true, separator: ',' })}
344
- }
345
- CONFIG.Item.typeCreatables = {
346
- ${joinToNode(itemCreatables, document => { var _a; return `"${document.name.toLowerCase()}": ${(_a = document.params.find(p => isDocumentCreatableParam(p))) === null || _a === void 0 ? void 0 : _a.value}`; }, { appendNewLineIfNotEmpty: true, separator: ',' })}
347
- }
348
- }
349
-
350
- /* -------------------------------------------- */
351
-
352
- function registerHandlebarsHelpers() {
353
-
354
- // Convert a type and value to a localized label
355
- Handlebars.registerHelper("typeLabel", (type, value) => {
356
- return game.i18n.localize(CONFIG.SYSTEM[type][value]?.label);
357
- });
358
-
359
- // Truncate a string to a certain length with an ellipsis
360
- Handlebars.registerHelper("truncate", (str, len) => {
361
- if (str.length > len) {
362
- return \`\${str.slice(0, len)}...\`;
363
- }
364
- return str;
365
- });
366
-
367
- // Get a property on an object using a string key
368
- Handlebars.registerHelper("getProperty", (obj, key) => {
369
- if (obj == null) return "";
370
- return foundry.utils.getProperty(obj, key);
371
- });
372
-
373
- // Humanize a string
374
- Handlebars.registerHelper("humanize", (str) => {
375
- let humanized = str.replace(/_/g, " ");
376
- humanized = humanized.replace("system.", "").replaceAll(".", " ");
377
- humanized = humanized.charAt(0).toUpperCase() + humanized.slice(1);
378
- return humanized;
379
- });
380
- }
381
-
382
- /* -------------------------------------------- */
383
-
384
- function registerResourceBars() {
385
- CONFIG.Actor.trackableAttributes = {
386
- ${joinToNode(entry.documents.filter(d => isActor(d)), document => generateTrackableResourceBars(document), { appendNewLineIfNotEmpty: true, separator: ',' })}
387
- };
388
- }
389
-
390
- /* -------------------------------------------- */
391
-
392
- ${generateRegisterStatusEffects()}
393
-
394
- /* -------------------------------------------- */
395
-
396
- function registerMacroActions() {
397
- game.system.macroActions = {
398
- ${generateMacroActionConfig(entry)}
399
- };
400
- }
401
-
402
- /* -------------------------------------------- */
403
-
404
- function registerKeywords() {
405
- game.system.keywords = {
406
- ${generateKeywordConfig(entry)}
407
- };
408
- }
409
-
410
- /* -------------------------------------------- */
411
-
412
- function setupKeywordEnricher() {
413
- // Register keyword text enricher
414
- CONFIG.TextEditor.enrichers.push({
415
- pattern: /@([a-zA-Z][a-zA-Z0-9]*)/gi,
416
- replaceParent: true,
417
- enricher: async (match, options) => {
418
- const keywordName = match[1].toLowerCase();
419
- const keyword = game.system.keywords[keywordName];
420
-
421
- if (!keyword) return null;
422
-
423
- // Find the system documentation journal and determine which page the keyword is on
424
- const keywordsJournal = game.journal.find(j => j.getFlag('${id}', 'systemJournal') === true || j.getFlag('core', 'keywordsJournal') === true);
425
- const isDamageType = keyword.type === 'damage-type';
426
- const pageType = isDamageType ? 'damage-types' : 'keywords';
427
- const targetPage = keywordsJournal?.pages.find(p => p.getFlag('core', 'pageType') === pageType);
428
-
429
- const element = document.createElement('span');
430
- element.className = 'keyword-reference';
431
- element.setAttribute('data-keyword', keywordName);
432
- element.setAttribute('data-journal-uuid', keywordsJournal?.uuid || '');
433
- element.setAttribute('data-page-uuid', targetPage?.uuid || '');
434
- element.setAttribute('data-anchor', keywordName);
435
- element.title = keyword.description;
436
-
437
- if (keyword.icon) {
438
- const icon = document.createElement('i');
439
- icon.className = keyword.icon;
440
- icon.style.color = keyword.color;
441
- element.appendChild(icon);
442
- }
443
-
444
- element.appendChild(document.createTextNode(keyword.name));
445
-
446
- return element;
447
- }
448
- });
449
-
450
- // Register click handler for keyword references
451
- $(document).on('click', '.keyword-reference', async function() {
452
- const journalUuid = $(this).data('journal-uuid');
453
- const pageUuid = $(this).data('page-uuid');
454
- const anchor = $(this).data('anchor');
455
-
456
- if (journalUuid && pageUuid) {
457
- const journal = await fromUuid(journalUuid);
458
- const page = await fromUuid(pageUuid);
459
- if (journal && page) {
460
- journal.sheet.render(true, {pageId: page.id, anchor: anchor});
461
- }
462
- } else if (journalUuid) {
463
- const journal = await fromUuid(journalUuid);
464
- journal?.sheet.render(true);
465
- }
466
- });
467
- }
468
-
469
- /** -------------------------------------------- */
470
-
471
- function addVueImportMap() {
472
- let script = document.createElement('script');
473
- script.type = 'importmap';
474
- script.text = \`{
475
- "imports": {
476
- "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
477
- }
478
- }\`;
479
- document.head.appendChild(script);
480
- }
481
-
482
- /* -------------------------------------------- */
483
-
484
- function registerUtils() {
485
- game.system.utils = {};
486
-
487
- function flattenObject(obj, _d=0) {
488
- const flat = {};
489
- if ( _d > 100 ) {
490
- throw new Error("Maximum depth exceeded");
491
- }
492
- for ( let [k, v] of Object.entries(obj) ) {
493
- let t = foundry.utils.getType(v);
494
- if ( t === "Object" ) {
495
- if ( k == "parent" ) continue;
496
- if ( foundry.utils.isEmpty(v) ) flat[k] = v;
497
- let inner = flattenObject(v, _d+1);
498
- for ( let [ik, iv] of Object.entries(inner) ) {
499
- flat[\`\${k}.\${ik}\`] = iv;
500
- }
501
- }
502
- else flat[k] = v;
503
- }
504
- return flat;
505
- }
506
-
507
- game.system.utils.flattenObject = flattenObject;
508
-
509
- function toNearest(interval=1, method="round") {
510
- if (!Number.isNumeric(this)) {
511
- throw new Error("toNearest() must be called on a numeric looking value");
512
- }
513
- const number = Number.fromString(this);
514
- return number.toNearest(interval, method);
515
- }
516
-
517
- Object.defineProperties(String.prototype, {
518
- toNearest: {value: toNearest}
519
- });
520
-
521
- async function callAllAsync(hook, ...args) {
522
- if ( CONFIG.debug.hooks ) {
523
- console.log(\`DEBUG | Calling async \${hook} hook with args:\`);
524
- console.log(args);
525
- }
526
- const events = Hooks.events;
527
- if ( !(hook in events) ) return true;
528
- for ( const entry of Array.from(events[hook]) ) {
529
- await entry.fn(...args);
530
- }
531
- return true;
532
- }
533
-
534
- Hooks.callAllAsync = callAllAsync;
535
-
536
- let audioSources = new Map();
537
- let gainNodes = new Map();
538
-
539
- async function playAudio(id, url, onEndCallback, volume=0.5) {
540
- let audioCtx = new (window.AudioContext || window.webkitAudioContext)();
541
- let source = audioCtx.createBufferSource();
542
- let gainNode = audioCtx.createGain();
543
- audioSources.set(id, source);
544
- gainNodes.set(id, gainNode);
545
- source.connect(gainNode).connect(audioCtx.destination);
546
-
547
- let request = new XMLHttpRequest();
548
- request.open('GET', url, true);
549
- request.responseType = 'arraybuffer';
550
-
551
- request.onload = () => {
552
- let audioData = request.response;
553
- audioCtx.decodeAudioData(audioData,
554
- (buffer) => {
555
- source.buffer = buffer;
556
- gainNode.gain.value = volume;
557
- source.start(0);
558
- source.onended = () => {
559
- audioSources.delete(id);
560
- gainNodes.delete(id);
561
- onEndCallback();
562
- };
563
- },
564
- (e) => {
565
- ui.notifications.error("An error occurred while decoding audio data");
566
- console.log(url);
567
- console.log(audioData);
568
- console.log(e);
569
- onEndCallback();
570
- });
571
- };
572
- try {
573
- request.send();
574
- }
575
- catch (e) {
576
- console.error("Error playing sound effect:", e);
577
- onEndCallback();
578
- }
579
- }
580
-
581
- async function playSfx(url, volume=0.5) {
582
- // Invoke the playAudio function with the provided parameters and wait for it to complete vis the onEndCallback
583
- let finishedPromise = new Promise(async (resolve) => {
584
- let onEndCallback = () => {
585
- resolve();
586
- };
587
- // Attach base url
588
- if (!url.startsWith("http")) {
589
- url = \`\${window.location.origin}/systems/${id}/\${url}\`;
590
- }
591
-
592
- await playAudio(foundry.utils.randomID(), url, onEndCallback, volume);
593
- });
594
- return finishedPromise;
595
- }
596
- game.system.utils.playSfx = playSfx;
597
- }
332
+ }, { appendNewLineIfNotEmpty: true, separator: ',' })}
333
+ };
334
+
335
+ CONFIG.Actor.typeDescriptions = {
336
+ ${joinToNode(actorDescriptions, document => { var _a; return `"${document.name.toLowerCase()}": "${(_a = document.params.find(p => isDocumentDescriptionParam(p))) === null || _a === void 0 ? void 0 : _a.value}"`; }, { appendNewLineIfNotEmpty: true, separator: ',' })}
337
+ }
338
+ CONFIG.Item.typeDescriptions = {
339
+ ${joinToNode(itemDescriptions, document => { var _a; return `"${document.name.toLowerCase()}": "${(_a = document.params.find(p => isDocumentDescriptionParam(p))) === null || _a === void 0 ? void 0 : _a.value}"`; }, { appendNewLineIfNotEmpty: true, separator: ',' })}
340
+ }
341
+
342
+ CONFIG.Actor.typeCreatables = {
343
+ ${joinToNode(actorCreatables, document => { var _a; return `"${document.name.toLowerCase()}": ${(_a = document.params.find(p => isDocumentCreatableParam(p))) === null || _a === void 0 ? void 0 : _a.value}`; }, { appendNewLineIfNotEmpty: true, separator: ',' })}
344
+ }
345
+ CONFIG.Item.typeCreatables = {
346
+ ${joinToNode(itemCreatables, document => { var _a; return `"${document.name.toLowerCase()}": ${(_a = document.params.find(p => isDocumentCreatableParam(p))) === null || _a === void 0 ? void 0 : _a.value}`; }, { appendNewLineIfNotEmpty: true, separator: ',' })}
347
+ }
348
+ }
349
+
350
+ /* -------------------------------------------- */
351
+
352
+ function registerHandlebarsHelpers() {
353
+
354
+ // Convert a type and value to a localized label
355
+ Handlebars.registerHelper("typeLabel", (type, value) => {
356
+ return game.i18n.localize(CONFIG.SYSTEM[type][value]?.label);
357
+ });
358
+
359
+ // Truncate a string to a certain length with an ellipsis
360
+ Handlebars.registerHelper("truncate", (str, len) => {
361
+ if (str.length > len) {
362
+ return \`\${str.slice(0, len)}...\`;
363
+ }
364
+ return str;
365
+ });
366
+
367
+ // Get a property on an object using a string key
368
+ Handlebars.registerHelper("getProperty", (obj, key) => {
369
+ if (obj == null) return "";
370
+ return foundry.utils.getProperty(obj, key);
371
+ });
372
+
373
+ // Humanize a string
374
+ Handlebars.registerHelper("humanize", (str) => {
375
+ let humanized = str.replace(/_/g, " ");
376
+ humanized = humanized.replace("system.", "").replaceAll(".", " ");
377
+ humanized = humanized.charAt(0).toUpperCase() + humanized.slice(1);
378
+ return humanized;
379
+ });
380
+ }
381
+
382
+ /* -------------------------------------------- */
383
+
384
+ function registerResourceBars() {
385
+ CONFIG.Actor.trackableAttributes = {
386
+ ${joinToNode(entry.documents.filter(d => isActor(d)), document => generateTrackableResourceBars(document), { appendNewLineIfNotEmpty: true, separator: ',' })}
387
+ };
388
+ }
389
+
390
+ /* -------------------------------------------- */
391
+
392
+ ${generateRegisterStatusEffects()}
393
+
394
+ /* -------------------------------------------- */
395
+
396
+ function registerMacroActions() {
397
+ game.system.macroActions = {
398
+ ${generateMacroActionConfig(entry)}
399
+ };
400
+ }
401
+
402
+ /* -------------------------------------------- */
403
+
404
+ function registerKeywords() {
405
+ game.system.keywords = {
406
+ ${generateKeywordConfig(entry)}
407
+ };
408
+ }
409
+
410
+ /* -------------------------------------------- */
411
+
412
+ function setupKeywordEnricher() {
413
+ // Register keyword text enricher
414
+ CONFIG.TextEditor.enrichers.push({
415
+ pattern: /@([a-zA-Z][a-zA-Z0-9]*)/gi,
416
+ replaceParent: true,
417
+ enricher: async (match, options) => {
418
+ const keywordName = match[1].toLowerCase();
419
+ const keyword = game.system.keywords[keywordName];
420
+
421
+ if (!keyword) return null;
422
+
423
+ // Find the system documentation journal and determine which page the keyword is on
424
+ const keywordsJournal = game.journal.find(j => j.getFlag('${id}', 'systemJournal') === true || j.getFlag('core', 'keywordsJournal') === true);
425
+ const isDamageType = keyword.type === 'damage-type';
426
+ const pageType = isDamageType ? 'damage-types' : 'keywords';
427
+ const targetPage = keywordsJournal?.pages.find(p => p.getFlag('core', 'pageType') === pageType);
428
+
429
+ const element = document.createElement('span');
430
+ element.className = 'keyword-reference';
431
+ element.setAttribute('data-keyword', keywordName);
432
+ element.setAttribute('data-journal-uuid', keywordsJournal?.uuid || '');
433
+ element.setAttribute('data-page-uuid', targetPage?.uuid || '');
434
+ element.setAttribute('data-anchor', keywordName);
435
+ element.title = keyword.description;
436
+
437
+ if (keyword.icon) {
438
+ const icon = document.createElement('i');
439
+ icon.className = keyword.icon;
440
+ icon.style.color = keyword.color;
441
+ element.appendChild(icon);
442
+ }
443
+
444
+ element.appendChild(document.createTextNode(keyword.name));
445
+
446
+ return element;
447
+ }
448
+ });
449
+
450
+ // Register click handler for keyword references
451
+ $(document).on('click', '.keyword-reference', async function() {
452
+ const journalUuid = $(this).data('journal-uuid');
453
+ const pageUuid = $(this).data('page-uuid');
454
+ const anchor = $(this).data('anchor');
455
+
456
+ if (journalUuid && pageUuid) {
457
+ const journal = await fromUuid(journalUuid);
458
+ const page = await fromUuid(pageUuid);
459
+ if (journal && page) {
460
+ journal.sheet.render(true, {pageId: page.id, anchor: anchor});
461
+ }
462
+ } else if (journalUuid) {
463
+ const journal = await fromUuid(journalUuid);
464
+ journal?.sheet.render(true);
465
+ }
466
+ });
467
+ }
468
+
469
+ /** -------------------------------------------- */
470
+
471
+ function addVueImportMap() {
472
+ let script = document.createElement('script');
473
+ script.type = 'importmap';
474
+ script.text = \`{
475
+ "imports": {
476
+ "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
477
+ }
478
+ }\`;
479
+ document.head.appendChild(script);
480
+ }
481
+
482
+ /* -------------------------------------------- */
483
+
484
+ function registerUtils() {
485
+ game.system.utils = {};
486
+
487
+ function flattenObject(obj, _d=0) {
488
+ const flat = {};
489
+ if ( _d > 100 ) {
490
+ throw new Error("Maximum depth exceeded");
491
+ }
492
+ for ( let [k, v] of Object.entries(obj) ) {
493
+ let t = foundry.utils.getType(v);
494
+ if ( t === "Object" ) {
495
+ if ( k == "parent" ) continue;
496
+ if ( foundry.utils.isEmpty(v) ) flat[k] = v;
497
+ let inner = flattenObject(v, _d+1);
498
+ for ( let [ik, iv] of Object.entries(inner) ) {
499
+ flat[\`\${k}.\${ik}\`] = iv;
500
+ }
501
+ }
502
+ else flat[k] = v;
503
+ }
504
+ return flat;
505
+ }
506
+
507
+ game.system.utils.flattenObject = flattenObject;
508
+
509
+ function toNearest(interval=1, method="round") {
510
+ if (!Number.isNumeric(this)) {
511
+ throw new Error("toNearest() must be called on a numeric looking value");
512
+ }
513
+ const number = Number.fromString(this);
514
+ return number.toNearest(interval, method);
515
+ }
516
+
517
+ Object.defineProperties(String.prototype, {
518
+ toNearest: {value: toNearest}
519
+ });
520
+
521
+ async function callAllAsync(hook, ...args) {
522
+ if ( CONFIG.debug.hooks ) {
523
+ console.log(\`DEBUG | Calling async \${hook} hook with args:\`);
524
+ console.log(args);
525
+ }
526
+ const events = Hooks.events;
527
+ if ( !(hook in events) ) return true;
528
+ for ( const entry of Array.from(events[hook]) ) {
529
+ await entry.fn(...args);
530
+ }
531
+ return true;
532
+ }
533
+
534
+ Hooks.callAllAsync = callAllAsync;
535
+
536
+ let audioSources = new Map();
537
+ let gainNodes = new Map();
538
+
539
+ async function playAudio(id, url, onEndCallback, volume=0.5) {
540
+ let audioCtx = new (window.AudioContext || window.webkitAudioContext)();
541
+ let source = audioCtx.createBufferSource();
542
+ let gainNode = audioCtx.createGain();
543
+ audioSources.set(id, source);
544
+ gainNodes.set(id, gainNode);
545
+ source.connect(gainNode).connect(audioCtx.destination);
546
+
547
+ let request = new XMLHttpRequest();
548
+ request.open('GET', url, true);
549
+ request.responseType = 'arraybuffer';
550
+
551
+ request.onload = () => {
552
+ let audioData = request.response;
553
+ audioCtx.decodeAudioData(audioData,
554
+ (buffer) => {
555
+ source.buffer = buffer;
556
+ gainNode.gain.value = volume;
557
+ source.start(0);
558
+ source.onended = () => {
559
+ audioSources.delete(id);
560
+ gainNodes.delete(id);
561
+ onEndCallback();
562
+ };
563
+ },
564
+ (e) => {
565
+ ui.notifications.error("An error occurred while decoding audio data");
566
+ console.log(url);
567
+ console.log(audioData);
568
+ console.log(e);
569
+ onEndCallback();
570
+ });
571
+ };
572
+ try {
573
+ request.send();
574
+ }
575
+ catch (e) {
576
+ console.error("Error playing sound effect:", e);
577
+ onEndCallback();
578
+ }
579
+ }
580
+
581
+ async function playSfx(url, volume=0.5) {
582
+ // Invoke the playAudio function with the provided parameters and wait for it to complete vis the onEndCallback
583
+ let finishedPromise = new Promise(async (resolve) => {
584
+ let onEndCallback = () => {
585
+ resolve();
586
+ };
587
+ // Attach base url
588
+ if (!url.startsWith("http")) {
589
+ url = \`\${window.location.origin}/systems/${id}/\${url}\`;
590
+ }
591
+
592
+ await playAudio(foundry.utils.randomID(), url, onEndCallback, volume);
593
+ });
594
+ return finishedPromise;
595
+ }
596
+ game.system.utils.playSfx = playSfx;
597
+ }
598
598
  `.appendNewLineIfNotEmpty();
599
599
  fs.writeFileSync(generatedFilePath, toString(fileNode));
600
600
  }