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.
Files changed (209) hide show
  1. package/.claude/agents/langium-language-designer.md +38 -0
  2. package/.claude/agents/typescript-vscode-expert.md +29 -0
  3. package/.claude/agents/ui-ux-designer.md +36 -0
  4. package/.claude/settings.local.json +33 -0
  5. package/.idea/inspectionProfiles/Project_Default.xml +7 -0
  6. package/.idea/isdl.iml +14 -0
  7. package/.idea/modules.xml +9 -0
  8. package/.idea/vcs.xml +7 -0
  9. package/.idea/watcherTasks.xml +4 -0
  10. package/.vscodeignore +18 -0
  11. package/LICENSE +674 -0
  12. package/README.md +86 -0
  13. package/bin/cli.js +4 -0
  14. package/bin/lsp.js +8 -0
  15. package/isdl.png +0 -0
  16. package/out/_backgrounds.scss +91 -0
  17. package/out/_handlebars.scss +505 -0
  18. package/out/_isdlStyles.scss +1357 -0
  19. package/out/_vuetifyOverrides.scss +425 -0
  20. package/out/_vuetifyStyles.scss +31957 -0
  21. package/out/cli/cli-util.js +39 -0
  22. package/out/cli/cli-util.js.map +1 -0
  23. package/out/cli/components/_backgrounds.scss +91 -0
  24. package/out/cli/components/_handlebars.scss +505 -0
  25. package/out/cli/components/_isdlStyles.scss +1357 -0
  26. package/out/cli/components/_vuetifyOverrides.scss +425 -0
  27. package/out/cli/components/_vuetifyStyles.scss +31957 -0
  28. package/out/cli/components/active-effect-sheet-generator.js +643 -0
  29. package/out/cli/components/active-effect-sheet-generator.js.map +1 -0
  30. package/out/cli/components/base-actor-sheet-generator.js +125 -0
  31. package/out/cli/components/base-actor-sheet-generator.js.map +1 -0
  32. package/out/cli/components/base-sheet-generator.js +525 -0
  33. package/out/cli/components/base-sheet-generator.js.map +1 -0
  34. package/out/cli/components/chat-card-generator.js +683 -0
  35. package/out/cli/components/chat-card-generator.js.map +1 -0
  36. package/out/cli/components/css-generator.js +58 -0
  37. package/out/cli/components/css-generator.js.map +1 -0
  38. package/out/cli/components/damage-roll-generator.js +173 -0
  39. package/out/cli/components/damage-roll-generator.js.map +1 -0
  40. package/out/cli/components/datamodel-generator.js +672 -0
  41. package/out/cli/components/datamodel-generator.js.map +1 -0
  42. package/out/cli/components/derived-data-generator.js +1340 -0
  43. package/out/cli/components/derived-data-generator.js.map +1 -0
  44. package/out/cli/components/hotbar-drop-hook-generator.js +95 -0
  45. package/out/cli/components/hotbar-drop-hook-generator.js.map +1 -0
  46. package/out/cli/components/init-hook-generator.js +597 -0
  47. package/out/cli/components/init-hook-generator.js.map +1 -0
  48. package/out/cli/components/keywords-generator.js +220 -0
  49. package/out/cli/components/keywords-generator.js.map +1 -0
  50. package/out/cli/components/language-generator.js +110 -0
  51. package/out/cli/components/language-generator.js.map +1 -0
  52. package/out/cli/components/measured-template-preview.js +234 -0
  53. package/out/cli/components/measured-template-preview.js.map +1 -0
  54. package/out/cli/components/method-generator.js +1812 -0
  55. package/out/cli/components/method-generator.js.map +1 -0
  56. package/out/cli/components/ready-hook-generator.js +448 -0
  57. package/out/cli/components/ready-hook-generator.js.map +1 -0
  58. package/out/cli/components/token-generator.js +138 -0
  59. package/out/cli/components/token-generator.js.map +1 -0
  60. package/out/cli/components/utils.js +176 -0
  61. package/out/cli/components/utils.js.map +1 -0
  62. package/out/cli/components/vue/base-components/vue-attribute.js +148 -0
  63. package/out/cli/components/vue/base-components/vue-attribute.js.map +1 -0
  64. package/out/cli/components/vue/base-components/vue-boolean.js +77 -0
  65. package/out/cli/components/vue/base-components/vue-boolean.js.map +1 -0
  66. package/out/cli/components/vue/base-components/vue-calculator.js +106 -0
  67. package/out/cli/components/vue/base-components/vue-calculator.js.map +1 -0
  68. package/out/cli/components/vue/base-components/vue-damage-application.js +369 -0
  69. package/out/cli/components/vue/base-components/vue-damage-application.js.map +1 -0
  70. package/out/cli/components/vue/base-components/vue-damage-bonuses.js +225 -0
  71. package/out/cli/components/vue/base-components/vue-damage-bonuses.js.map +1 -0
  72. package/out/cli/components/vue/base-components/vue-damage-resistances.js +256 -0
  73. package/out/cli/components/vue/base-components/vue-damage-resistances.js.map +1 -0
  74. package/out/cli/components/vue/base-components/vue-damage-track.js +134 -0
  75. package/out/cli/components/vue/base-components/vue-damage-track.js.map +1 -0
  76. package/out/cli/components/vue/base-components/vue-date-time.js +55 -0
  77. package/out/cli/components/vue/base-components/vue-date-time.js.map +1 -0
  78. package/out/cli/components/vue/base-components/vue-dice.js +111 -0
  79. package/out/cli/components/vue/base-components/vue-dice.js.map +1 -0
  80. package/out/cli/components/vue/base-components/vue-die.js +86 -0
  81. package/out/cli/components/vue/base-components/vue-die.js.map +1 -0
  82. package/out/cli/components/vue/base-components/vue-document-choice.js +172 -0
  83. package/out/cli/components/vue/base-components/vue-document-choice.js.map +1 -0
  84. package/out/cli/components/vue/base-components/vue-document-choices.js +203 -0
  85. package/out/cli/components/vue/base-components/vue-document-choices.js.map +1 -0
  86. package/out/cli/components/vue/base-components/vue-document-link.js +73 -0
  87. package/out/cli/components/vue/base-components/vue-document-link.js.map +1 -0
  88. package/out/cli/components/vue/base-components/vue-extended-choice.js +101 -0
  89. package/out/cli/components/vue/base-components/vue-extended-choice.js.map +1 -0
  90. package/out/cli/components/vue/base-components/vue-inventory.js +532 -0
  91. package/out/cli/components/vue/base-components/vue-inventory.js.map +1 -0
  92. package/out/cli/components/vue/base-components/vue-macro-choice.js +150 -0
  93. package/out/cli/components/vue/base-components/vue-macro-choice.js.map +1 -0
  94. package/out/cli/components/vue/base-components/vue-measured-template.js +543 -0
  95. package/out/cli/components/vue/base-components/vue-measured-template.js.map +1 -0
  96. package/out/cli/components/vue/base-components/vue-money.js +496 -0
  97. package/out/cli/components/vue/base-components/vue-money.js.map +1 -0
  98. package/out/cli/components/vue/base-components/vue-number.js +184 -0
  99. package/out/cli/components/vue/base-components/vue-number.js.map +1 -0
  100. package/out/cli/components/vue/base-components/vue-paperdoll.js +56 -0
  101. package/out/cli/components/vue/base-components/vue-paperdoll.js.map +1 -0
  102. package/out/cli/components/vue/base-components/vue-parent-property-reference.js +89 -0
  103. package/out/cli/components/vue/base-components/vue-parent-property-reference.js.map +1 -0
  104. package/out/cli/components/vue/base-components/vue-prosemirror.js +31 -0
  105. package/out/cli/components/vue/base-components/vue-prosemirror.js.map +1 -0
  106. package/out/cli/components/vue/base-components/vue-resource.js +149 -0
  107. package/out/cli/components/vue/base-components/vue-resource.js.map +1 -0
  108. package/out/cli/components/vue/base-components/vue-roll-visualizer.js +121 -0
  109. package/out/cli/components/vue/base-components/vue-roll-visualizer.js.map +1 -0
  110. package/out/cli/components/vue/base-components/vue-self-property-reference.js +75 -0
  111. package/out/cli/components/vue/base-components/vue-self-property-reference.js.map +1 -0
  112. package/out/cli/components/vue/base-components/vue-string-choice.js +111 -0
  113. package/out/cli/components/vue/base-components/vue-string-choice.js.map +1 -0
  114. package/out/cli/components/vue/base-components/vue-string-choices.js +216 -0
  115. package/out/cli/components/vue/base-components/vue-string-choices.js.map +1 -0
  116. package/out/cli/components/vue/base-components/vue-string.js +73 -0
  117. package/out/cli/components/vue/base-components/vue-string.js.map +1 -0
  118. package/out/cli/components/vue/base-components/vue-text-field.js +66 -0
  119. package/out/cli/components/vue/base-components/vue-text-field.js.map +1 -0
  120. package/out/cli/components/vue/base-components/vue-tracker.js +444 -0
  121. package/out/cli/components/vue/base-components/vue-tracker.js.map +1 -0
  122. package/out/cli/components/vue/vue-action-component-generator.js +88 -0
  123. package/out/cli/components/vue/vue-action-component-generator.js.map +1 -0
  124. package/out/cli/components/vue/vue-active-effect-sheet-generator.js +1016 -0
  125. package/out/cli/components/vue/vue-active-effect-sheet-generator.js.map +1 -0
  126. package/out/cli/components/vue/vue-base-components-generator.js +59 -0
  127. package/out/cli/components/vue/vue-base-components-generator.js.map +1 -0
  128. package/out/cli/components/vue/vue-datatable-component-generator.js +307 -0
  129. package/out/cli/components/vue/vue-datatable-component-generator.js.map +1 -0
  130. package/out/cli/components/vue/vue-datatable-sheet-class-generator.js +342 -0
  131. package/out/cli/components/vue/vue-datatable-sheet-class-generator.js.map +1 -0
  132. package/out/cli/components/vue/vue-datatable2-component-generator.js +939 -0
  133. package/out/cli/components/vue/vue-datatable2-component-generator.js.map +1 -0
  134. package/out/cli/components/vue/vue-document-creation-app.js +140 -0
  135. package/out/cli/components/vue/vue-document-creation-app.js.map +1 -0
  136. package/out/cli/components/vue/vue-document-creation-sheet.js +105 -0
  137. package/out/cli/components/vue/vue-document-creation-sheet.js.map +1 -0
  138. package/out/cli/components/vue/vue-generator.js +240 -0
  139. package/out/cli/components/vue/vue-generator.js.map +1 -0
  140. package/out/cli/components/vue/vue-mixin.js +338 -0
  141. package/out/cli/components/vue/vue-mixin.js.map +1 -0
  142. package/out/cli/components/vue/vue-pinned-datatable-component-generator.js +306 -0
  143. package/out/cli/components/vue/vue-pinned-datatable-component-generator.js.map +1 -0
  144. package/out/cli/components/vue/vue-prompt-generator.js +201 -0
  145. package/out/cli/components/vue/vue-prompt-generator.js.map +1 -0
  146. package/out/cli/components/vue/vue-prompt-sheet-class-generator.js +252 -0
  147. package/out/cli/components/vue/vue-prompt-sheet-class-generator.js.map +1 -0
  148. package/out/cli/components/vue/vue-sheet-application-generator.js +2008 -0
  149. package/out/cli/components/vue/vue-sheet-application-generator.js.map +1 -0
  150. package/out/cli/components/vue/vue-sheet-class-generator.js +484 -0
  151. package/out/cli/components/vue/vue-sheet-class-generator.js.map +1 -0
  152. package/out/cli/generator.js +659 -0
  153. package/out/cli/generator.js.map +1 -0
  154. package/out/cli/main.js +43 -0
  155. package/out/cli/main.js.map +1 -0
  156. package/out/datatables.min.css +54 -0
  157. package/out/datatables.min.js +178 -0
  158. package/out/extension/github/githubAuthProvider.js +345 -0
  159. package/out/extension/github/githubAuthProvider.js.map +1 -0
  160. package/out/extension/github/githubConfig.js +132 -0
  161. package/out/extension/github/githubConfig.js.map +1 -0
  162. package/out/extension/github/githubGistActions.js +251 -0
  163. package/out/extension/github/githubGistActions.js.map +1 -0
  164. package/out/extension/github/githubGistManager.js +255 -0
  165. package/out/extension/github/githubGistManager.js.map +1 -0
  166. package/out/extension/github/githubManager.js +1735 -0
  167. package/out/extension/github/githubManager.js.map +1 -0
  168. package/out/extension/github/githubQuickActions.js +659 -0
  169. package/out/extension/github/githubQuickActions.js.map +1 -0
  170. package/out/extension/github/githubTreeProvider.js +181 -0
  171. package/out/extension/github/githubTreeProvider.js.map +1 -0
  172. package/out/extension/github/system-workflow.yml +48 -0
  173. package/out/extension/main.cjs +70315 -0
  174. package/out/extension/main.cjs.map +7 -0
  175. package/out/extension/main.js +237 -0
  176. package/out/extension/main.js.map +1 -0
  177. package/out/extension/package.json +426 -0
  178. package/out/isdl.png +0 -0
  179. package/out/language/generated/ast.js +2992 -0
  180. package/out/language/generated/ast.js.map +1 -0
  181. package/out/language/generated/grammar.js +13970 -0
  182. package/out/language/generated/grammar.js.map +1 -0
  183. package/out/language/generated/module.js +20 -0
  184. package/out/language/generated/module.js.map +1 -0
  185. package/out/language/intelligent-system-design-language-formatter.js +85 -0
  186. package/out/language/intelligent-system-design-language-formatter.js.map +1 -0
  187. package/out/language/intelligent-system-design-language-module.js +69 -0
  188. package/out/language/intelligent-system-design-language-module.js.map +1 -0
  189. package/out/language/intelligent-system-design-language-quickfixes.js +37 -0
  190. package/out/language/intelligent-system-design-language-quickfixes.js.map +1 -0
  191. package/out/language/intelligent-system-design-language-validator.js +515 -0
  192. package/out/language/intelligent-system-design-language-validator.js.map +1 -0
  193. package/out/language/isdl-hover-provider.js +77 -0
  194. package/out/language/isdl-hover-provider.js.map +1 -0
  195. package/out/language/isdl-scope-provider.js +149 -0
  196. package/out/language/isdl-scope-provider.js.map +1 -0
  197. package/out/language/main.cjs +47655 -0
  198. package/out/language/main.cjs.map +7 -0
  199. package/out/language/main.js +11 -0
  200. package/out/language/main.js.map +1 -0
  201. package/out/missing-character.png +0 -0
  202. package/out/package.json +426 -0
  203. package/out/paperdoll_default.png +0 -0
  204. package/out/progressbar.min.js +7 -0
  205. package/out/styles.scss +722 -0
  206. package/out/test/formatting/formatter.test.js +46 -0
  207. package/out/test/formatting/formatter.test.js.map +1 -0
  208. package/out/vuetify.esm.js +30279 -0
  209. package/package.json +426 -0
@@ -0,0 +1,683 @@
1
+ import { expandToNode, joinToNode, toString } from "langium/generate";
2
+ import { isResourceExp, isConfigExpression } from "../../language/generated/ast.js";
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ import { getAllOfType, getSystemPath } from "./utils.js";
6
+ export function generateChatCardClass(entry, destination) {
7
+ const generatedFileDir = path.join(destination, "system", "documents");
8
+ const generatedFilePath = path.join(generatedFileDir, `chat-card.mjs`);
9
+ if (!fs.existsSync(generatedFileDir)) {
10
+ fs.mkdirSync(generatedFileDir, { recursive: true });
11
+ }
12
+ const id = entry.config.body.find(x => isConfigExpression(x) && x.type == "id").value;
13
+ function generateHpElement(document) {
14
+ const healthResource = getAllOfType(document.body, isResourceExp).find(x => x.tag == "health");
15
+ if (!healthResource) {
16
+ return expandToNode `
17
+ case '${document.name.toLocaleLowerCase()}':
18
+ // No health resource found.
19
+ break;
20
+ `;
21
+ }
22
+ return expandToNode `
23
+ case '${document.name.toLocaleLowerCase()}':
24
+
25
+ // If the type is temp, add to the temp health.
26
+ if ( type === 'temp' ) {
27
+ update['${getSystemPath(healthResource, ['temp'], undefined, false)}'] = target.actor.${getSystemPath(healthResource, ['temp'], undefined, false)} + (finalDamage !== undefined ? finalDamage : roll);
28
+ break;
29
+ }
30
+
31
+ // If the type is damage and we have temp health, apply to temp health first.
32
+ if ( type === 'damage' && target.actor.${getSystemPath(healthResource, ['temp'], undefined, false)} > 0 ) {
33
+ const appliedDamage = finalDamage !== undefined ? finalDamage : roll;
34
+ update['${getSystemPath(healthResource, ['temp'], undefined, false)}'] = target.actor.${getSystemPath(healthResource, ['temp'], undefined, false)} - appliedDamage;
35
+
36
+ if ( update['${getSystemPath(healthResource, ['temp'], undefined, false)}'] < 0 ) {
37
+ update['${getSystemPath(healthResource)}'] = target.actor.${getSystemPath(healthResource)} + update['${getSystemPath(healthResource, ['temp'], undefined, false)}'];
38
+ update['${getSystemPath(healthResource, ['temp'], undefined, false)}'] = 0;
39
+ }
40
+ }
41
+ else {
42
+ // Otherwise, apply to the main health.
43
+ update['${getSystemPath(healthResource)}'] = target.actor.${getSystemPath(healthResource)} - (finalDamage !== undefined ? finalDamage : roll);
44
+ }
45
+ break;
46
+ `;
47
+ }
48
+ const fileNode = expandToNode `
49
+ import { ContextMenu2 } from '../contextMenu2.js';
50
+
51
+ export default class ${entry.config.name}ChatCard {
52
+
53
+ static activateListeners(html) {
54
+ html.on("click", ".collapsible", ${entry.config.name}ChatCard._onChatCardToggleCollapsible.bind(this));
55
+ html.on("click", ".action", ${entry.config.name}ChatCard._handleActionClick.bind(this));
56
+ html.on("click", ".revert-target-damage", ${entry.config.name}ChatCard._onRevertTargetDamage.bind(this));
57
+ html.on("click", ".dice-roll", event => {
58
+ const rollElement = event.currentTarget;
59
+ rollElement.classList.toggle("expanded");
60
+ });
61
+
62
+ // Customize the drag data of effects
63
+ html.find(".effect").each((i, li) => {
64
+ li.setAttribute("draggable", true);
65
+ li.addEventListener("dragstart", async ev => {
66
+ let dragData = {
67
+ type: "ActiveEffect",
68
+ uuid: li.dataset.uuid
69
+ };
70
+ ev.dataTransfer.setData("text/plain", JSON.stringify(dragData));
71
+ }, false);
72
+ });
73
+
74
+ // If this is not the latest message, default to collapsed
75
+ const thisMessageId = html.data("messageId");
76
+ const messages = Array.from(game.messages);
77
+ const latestMessageId = messages[game.messages.size - 1]._id;
78
+ if (thisMessageId !== latestMessageId) {
79
+ html.find(".collapsible").addClass("collapsed");
80
+ }
81
+
82
+ // Collapse the previous message automatically if it is not already collapsed
83
+ const previousMessageId = messages[game.messages.size - 2]?._id;
84
+ const previousMessage = window.document.querySelector(\`#chat .chat-message[data-message-id="\${previousMessageId}"]\`);
85
+ if (previousMessage) {
86
+ for (const collapsible of previousMessage.querySelectorAll(".collapsible") ?? []) {
87
+ if (!collapsible.classList.contains("collapsed")) {
88
+ collapsible.classList.add("collapsed");
89
+ }
90
+ }
91
+ }
92
+
93
+ function uuidv4() {
94
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
95
+ var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
96
+ return v.toString(16);
97
+ });
98
+ }
99
+
100
+ function applyMenus(roll) {
101
+ var uuid = uuidv4();
102
+
103
+ // Add a way to uniquely identify this roll
104
+ $(this)[0].dataset.uuid = uuid;
105
+ $(this).off("contextmenu");
106
+
107
+ // Determine if applying damage to targets is allowed.
108
+ const allowTargeting = game.settings.get('${id}', 'allowTargetDamageApplication');
109
+ let targetType = game.settings.get('${id}', 'userTargetDamageApplicationType');
110
+ if (!allowTargeting && targetType !== 'selected') {
111
+ game.settings.set('${id}', 'userTargetDamageApplicationType', 'selected');
112
+ targetType = 'selected';
113
+ }
114
+
115
+ let menuItems = [];
116
+
117
+ function getRollFromElement(rollElement) {
118
+ const element = rollElement.hasClass('inline-roll')
119
+ ? rollElement
120
+ : rollElement.find('.result');
121
+
122
+ if (element.length === 0) return null;
123
+
124
+ // Check if this is a damage roll by looking for damage roll attributes
125
+ const isDamageRoll = rollElement.hasClass('damage-roll') || rollElement.closest('.damage-roll').length > 0;
126
+ const damageRollElement = isDamageRoll ? (rollElement.hasClass('damage-roll') ? rollElement : rollElement.closest('.damage-roll')) : null;
127
+
128
+ const rollValue = getRollValue(element);
129
+
130
+ // If this is a damage roll, extract metadata
131
+ if (isDamageRoll && damageRollElement && damageRollElement.length > 0) {
132
+ const damageType = damageRollElement.attr('data-damage-type') || null;
133
+ const damageTypeSpan = damageRollElement.find('.damage-type');
134
+ const damageColor = damageTypeSpan.length > 0 ? damageTypeSpan.css('color') : null;
135
+ const damageIcon = damageRollElement.find('i').first().attr('class') || null;
136
+
137
+ // Extract custom metadata from damage-metadata section if present
138
+ const metadata = {};
139
+ damageRollElement.find('.damage-metadata .damage-property').each(function() {
140
+ const property = $(this).attr('data-property');
141
+ const text = $(this).text();
142
+ const colonIndex = text.indexOf(':');
143
+ if (colonIndex !== -1 && property) {
144
+ const value = text.substring(colonIndex + 1).trim();
145
+ metadata[property] = value;
146
+ }
147
+ });
148
+
149
+ return {
150
+ value: rollValue,
151
+ isDamageRoll: true,
152
+ damageType: damageType,
153
+ damageColor: damageColor,
154
+ damageIcon: damageIcon,
155
+ metadata: metadata
156
+ };
157
+ }
158
+
159
+ return { value: rollValue, isDamageRoll: false };
160
+ }
161
+
162
+ function getRollValue(roll) {
163
+ if (Number.isInteger(roll)) {
164
+ return roll;
165
+ }
166
+ if (roll instanceof Roll) {
167
+ return roll.total;
168
+ }
169
+ // Try the regex for expanded rolls.
170
+ const REGEX_EXPANDED_INLINE_ROLL = /.*=\s(\d+)/gm;
171
+ let match = REGEX_EXPANDED_INLINE_ROLL.exec(roll[0].innerText);
172
+ if (match) return Number.parseInt(match[1]);
173
+
174
+ // Regex failed to match, try grabbing the inner text.
175
+ match = Number.parseInt(roll[0].innerText.trim());
176
+ return match || 0; // Fallback if we failed to parse
177
+ }
178
+
179
+ function getTargets(targetType) {
180
+ const targets = targetType === 'targeted'
181
+ ? [...game.user.targets]
182
+ : (canvas?.tokens?.controlled ?? []);
183
+
184
+ if (!targets || targets?.length < 1) {
185
+ ui.notifications.warn(game.i18n.localize(\`NOTIFICATIONS.\${targetType === 'targeted' ? 'NoTokenTargeted' : 'NoTokenSelected'}\`));
186
+ return [];
187
+ }
188
+
189
+ return targets;
190
+ }
191
+
192
+ async function apply(element, event, type) {
193
+ const menu = element.find('#context-menu2')?.[0];
194
+ const applyTargetType = menu?.dataset?.target ?? 'selected';
195
+ const applyMod = menu?.dataset?.mod ? Number(menu.dataset.mod) : 1;
196
+ const bonusDamage = menu?.dataset?.bonus ? Number(menu.dataset.bonus) : 0;
197
+
198
+ let rollData = getRollFromElement(element);
199
+ if ( !rollData ) return;
200
+
201
+ let baseRoll = rollData.value;
202
+ if ( type === 'healing' ) {
203
+ baseRoll = -baseRoll;
204
+ }
205
+
206
+ baseRoll *= applyMod;
207
+
208
+ const targets = getTargets(applyTargetType);
209
+ const applicationSummary = {
210
+ type: type,
211
+ originalDamage: rollData.value,
212
+ bonusDamage: bonusDamage,
213
+ multiplier: applyMod,
214
+ totalBaseDamage: baseRoll,
215
+ damageType: rollData.isDamageRoll ? rollData.damageType : null,
216
+ damageColor: rollData.isDamageRoll ? rollData.damageColor : null,
217
+ damageIcon: rollData.isDamageRoll ? rollData.damageIcon : null,
218
+ targets: [],
219
+ timestamp: Date.now(),
220
+ userId: game.user.id
221
+ };
222
+
223
+ for ( const target of targets ) {
224
+ console.log(type, baseRoll, target);
225
+ const update = {};
226
+
227
+ let roll = foundry.utils.duplicate(baseRoll);
228
+
229
+ // Create enhanced context with damage type and metadata if available
230
+ const context = {
231
+ amount: roll,
232
+ damageType: rollData.isDamageRoll ? rollData.damageType : null,
233
+ damageMetadata: rollData.isDamageRoll ? rollData.metadata : {},
234
+ color: rollData.isDamageRoll ? rollData.damageColor : null,
235
+ icon: rollData.isDamageRoll ? rollData.damageIcon : null,
236
+ isDamageRoll: rollData.isDamageRoll
237
+ };
238
+
239
+ await Hooks.callAllAsync('preApply' + type.titleCase(), target.actor, context);
240
+ roll = context.amount;
241
+
242
+ // Calculate resistance information for this target
243
+ let flatResistance = 0;
244
+ let percentResistance = 0;
245
+ let finalDamage = roll;
246
+
247
+ if (rollData.isDamageRoll && rollData.damageType && type === 'damage') {
248
+ // Get resistance information from actor using datamodel field names
249
+ const damageTypeKey = rollData.damageType.toLowerCase().replace(/\s+/g, '');
250
+ const flatResistanceField = \`\${damageTypeKey}damageresistanceflat\`;
251
+ const percentResistanceField = \`\${damageTypeKey}damageresistancepercent\`;
252
+
253
+ flatResistance = target.actor.system[flatResistanceField] || 0;
254
+ percentResistance = target.actor.system[percentResistanceField] || 0;
255
+
256
+ if (flatResistance > 0 || percentResistance > 0) {
257
+ // Apply resistance: (damage - flat) * (1 - percent/100)
258
+ finalDamage = Math.max(0, (roll - flatResistance) * (1 - percentResistance / 100));
259
+ finalDamage = Math.floor(finalDamage);
260
+ }
261
+ }
262
+
263
+ // Store pre-application state for revert functionality
264
+ const preState = {};
265
+ const targetSummary = {
266
+ uuid: target.document.uuid,
267
+ name: target.actor.name,
268
+ appliedAmount: roll,
269
+ finalDamage: finalDamage,
270
+ flatResistance: flatResistance,
271
+ percentResistance: percentResistance,
272
+ preState: {},
273
+ postState: {}
274
+ };
275
+
276
+ switch ( target.actor.type ) {
277
+ ${joinToNode(entry.documents, document => generateHpElement(document), { appendNewLineIfNotEmpty: true })}
278
+ }
279
+
280
+ // Store pre-state for revert
281
+ for (const [key, value] of Object.entries(update)) {
282
+ targetSummary.preState[key] = foundry.utils.getProperty(target.actor, key);
283
+ targetSummary.postState[key] = value;
284
+ }
285
+
286
+ await target.actor.update(update);
287
+ applicationSummary.targets.push(targetSummary);
288
+
289
+ // Call the applied hook with enhanced context
290
+ Hooks.callAll('applied' + type.titleCase(), target.actor, context);
291
+ }
292
+
293
+ // Create damage application summary chat card
294
+ await createDamageApplicationChatCard(applicationSummary);
295
+ }
296
+
297
+ async function createDamageApplicationChatCard(summary) {
298
+ const setting = game.settings.get('${id}', 'damageApplicationChatCard');
299
+ if (setting === 'none') return;
300
+
301
+ const whisper = setting === 'gm' ? ChatMessage.getWhisperRecipients('GM') : [];
302
+ const typeLabel = summary.type === 'damage' ? 'Damage' : summary.type === 'healing' ? 'Healing' : 'Temp HP';
303
+
304
+ // Create HTML content for the chat card
305
+ const targetRows = summary.targets.map((target, index) => {
306
+ let resistanceText = 'N/A';
307
+
308
+ if (summary.type === 'damage' && summary.damageType) {
309
+ const hasFlat = target.flatResistance > 0;
310
+ const hasPercent = target.percentResistance > 0;
311
+
312
+ if (hasFlat || hasPercent) {
313
+ const parts = [];
314
+ if (hasFlat) parts.push(\`\${target.flatResistance}\`);
315
+ if (hasPercent) parts.push(\`\${target.percentResistance}%\`);
316
+ resistanceText = parts.join(', ');
317
+ } else {
318
+ resistanceText = 'None';
319
+ }
320
+ }
321
+
322
+ return \`
323
+ <tr class="target-row" data-target-index="\${index}">
324
+ <td><strong>\${target.name}</strong></td>
325
+ <td>\${resistanceText}</td>
326
+ <td><strong>\${summary.type === 'healing' ? '+' : ''}\${target.finalDamage}</strong></td>
327
+ <td>
328
+ <button type="button" class="revert-target-damage" data-target-index="\${index}" data-summary='\${JSON.stringify(summary)}' title="Revert this target">
329
+ <i class="fas fa-undo"></i>
330
+ </button>
331
+ </td>
332
+ </tr>
333
+ \`;
334
+ }).join('');
335
+
336
+ const content = \`
337
+ <div class="damage-application-summary">
338
+ <div class="card-header">
339
+ <h3><i class="fas fa-sword-cross"></i> \${typeLabel} Applied</h3>
340
+ </div>
341
+
342
+ <div class="damage-summary">
343
+ <div class="base-damage">
344
+ <strong>Base Damage:</strong> \${summary.originalDamage}
345
+ \${summary.bonusDamage > 0 ? \`+ \${summary.bonusDamage} bonus\` : ''}
346
+ \${summary.multiplier !== 1 ? \` × \${summary.multiplier}\` : ''}
347
+ = <strong>\${summary.totalBaseDamage}</strong>
348
+ \${summary.damageType ? \`<br><strong>Type:</strong> <span class="damage-type" style="color: \${summary.damageColor || '#666'}">\${summary.damageIcon ? \`<i class="\${summary.damageIcon}"></i> \` : ''}\${summary.damageType}</span>\` : ''}
349
+ </div>
350
+ </div>
351
+
352
+ <div class="targets-summary">
353
+ <h4>Applied to Targets:</h4>
354
+ <table class="damage-targets">
355
+ <thead>
356
+ <tr>
357
+ <th>Target</th>
358
+ <th>Resistance</th>
359
+ <th>Final Damage</th>
360
+ <th>Revert</th>
361
+ </tr>
362
+ </thead>
363
+ <tbody>
364
+ \${targetRows}
365
+ </tbody>
366
+ </table>
367
+ </div>
368
+ </div>
369
+ \`;
370
+
371
+ await ChatMessage.create({
372
+ user: game.user.id,
373
+ content: content,
374
+ whisper: whisper,
375
+ flags: {
376
+ [game.system.id]: {
377
+ damageApplicationSummary: summary
378
+ }
379
+ }
380
+ });
381
+ }
382
+
383
+ if ( allowTargeting ) {
384
+ menuItems.push({
385
+ name: \`
386
+ <div class="damage-target flex flexrow">
387
+ <button type="button" data-target="targeted"><i class="fa-solid fa-bullseye"></i> \${game.i18n.localize('Targeted')}</button>
388
+ <button type="button" data-target="selected"><i class="fa-solid fa-expand"></i> \${game.i18n.localize('Selected')}</button>
389
+ </div>\`,
390
+ id: 'targets',
391
+ icon: '',
392
+ preventClose: true,
393
+ callback: (inlineRoll, event) => {
394
+ const button = event?.target ?? event?.currentTarget;
395
+ if (button?.dataset?.target) {
396
+ // Deactivate the other target type.
397
+ const activeButtons = inlineRoll.find('button[data-target].active');
398
+ activeButtons.removeClass('active');
399
+ // Set the target type on the menu for later reference.
400
+ const menu = inlineRoll.find('#context-menu2')[0];
401
+ if (menu) {
402
+ menu.dataset.target = button.dataset.target;
403
+ }
404
+ // Toggle the active button and update the user setting.
405
+ button.classList.add('active');
406
+ game.settings.set('${id}', 'userTargetDamageApplicationType', button.dataset.target);
407
+ }
408
+ }
409
+ });
410
+ }
411
+
412
+ // Add damage multipliers.
413
+ menuItems.push({
414
+ name: \`
415
+ <div class="damage-modifiers flex flexrow">
416
+ <button type="button" data-mod="0.25">&frac14;x</button>
417
+ <button type="button" data-mod="0.5">&frac12;x</button>
418
+ <button type="button" data-mod="1" class="active">1x</button>
419
+ <button type="button" data-mod="1.5">1.5x</button>
420
+ <button type="button" data-mod="2">2x</button>
421
+ <button type="button" data-mod="3">3x</button>
422
+ <button type="button" data-mod="4">4x</button>
423
+ </div>\`,
424
+ id: 'modifiers',
425
+ icon: '',
426
+ preventClose: true,
427
+ callback: (inlineRoll, event) => {
428
+ const button = event?.target ?? event?.currentTarget;
429
+ if (button?.dataset?.mod) {
430
+ // Deactivate the other target type.
431
+ const activeButtons = inlineRoll.find('button[data-mod].active');
432
+ activeButtons.removeClass('active');
433
+
434
+ // Set the target type on the menu for later reference.
435
+ const menu = inlineRoll.find('#context-menu2')[0];
436
+ if (menu) {
437
+ menu.dataset.mod = button.dataset.mod;
438
+ }
439
+
440
+ // Toggle the active button and update the user setting.
441
+ button.classList.add('active');
442
+ }
443
+ }
444
+ });
445
+
446
+ menuItems.push(
447
+ {
448
+ name: game.i18n.localize("CONTEXT.ApplyDamage"),
449
+ id: 'damage',
450
+ icon: '<i class="fas fa-tint"></i>',
451
+ callback: (inlineRoll, event) => apply(inlineRoll, event, 'damage')
452
+ },
453
+ {
454
+ name: game.i18n.localize("CONTEXT.ApplyHealing"),
455
+ id: 'healing',
456
+ icon: '<i class="fas fa-medkit"></i>',
457
+ callback: (inlineRoll, event) => apply(inlineRoll, event, 'healing')
458
+ },
459
+ {
460
+ name: game.i18n.localize("CONTEXT.ApplyTemp"),
461
+ id: 'temp-healing',
462
+ icon: '<i class="fas fa-heart"></i>',
463
+ callback: (inlineRoll, event) => apply(inlineRoll, event, 'temp')
464
+ }
465
+ );
466
+ new ContextMenu2($(this).parent(), \`[data-uuid=\${uuid}]\`, menuItems);
467
+ }
468
+ html.find('.inline-roll').each(applyMenus);
469
+ html.find('.dice-total').each(applyMenus);
470
+ }
471
+
472
+ /* -------------------------------------------- */
473
+
474
+ static _onChatCardToggleCollapsible(event) {
475
+ const target = event.currentTarget;
476
+
477
+ // If the target is a content-link, ignore the click event
478
+ if (event.target.classList.contains("content-link")) return;
479
+
480
+ event.preventDefault();
481
+ target.classList.toggle("collapsed");
482
+
483
+ // Clear the height from the chat popout container so that it appropriately resizes.
484
+ const popout = target.closest(".chat-popout");
485
+ if ( popout ) popout.style.height = "";
486
+ }
487
+
488
+ /* -------------------------------------------- */
489
+
490
+ static _handleActionClick(event) {
491
+ event.preventDefault();
492
+ const action = event.currentTarget.dataset.action;
493
+
494
+ switch (action) {
495
+ case "place":
496
+ const template = event.currentTarget.closest(".measured-template");
497
+ if (!template) return;
498
+
499
+ const context = {
500
+ type: template.dataset.type,
501
+ distance: template.dataset.distance,
502
+ direction: template.dataset.direction,
503
+ angle: template.dataset.angle,
504
+ width: template.dataset.width
505
+ };
506
+
507
+ // Trigger the place action on the template
508
+ game.system.measuredTemplatePreviewClass.place(context, game.user.character?.sheet);
509
+ break;
510
+ }
511
+ }
512
+
513
+ static async _onRevertTargetDamage(event) {
514
+ const button = event.currentTarget;
515
+ const summaryData = JSON.parse(button.dataset.summary);
516
+ const targetIndex = parseInt(button.dataset.targetIndex);
517
+ const targetData = summaryData.targets[targetIndex];
518
+
519
+ if (!targetData) {
520
+ ui.notifications.error("Target data not found");
521
+ return;
522
+ }
523
+
524
+ // Confirm revert
525
+ const confirm = await Dialog.confirm({
526
+ title: "Revert Target Damage",
527
+ content: \`<p>Are you sure you want to revert the \${summaryData.type} application to <strong>\${targetData.name}</strong>?</p><p>This will restore the actor to their previous state.</p>\`,
528
+ yes: () => true,
529
+ no: () => false
530
+ });
531
+
532
+ if (!confirm) return;
533
+
534
+ const tokenDocument = await fromUuid(targetData.uuid);
535
+ if (!tokenDocument || !tokenDocument.actor) {
536
+ ui.notifications.warn(\`Cannot find target: \${targetData.name}\`);
537
+ return;
538
+ }
539
+
540
+ try {
541
+ await tokenDocument.actor.update(targetData.preState);
542
+ console.log(\`Reverted \${targetData.name} to pre-application state\`);
543
+
544
+ // Update the button to show it's been reverted
545
+ button.disabled = true;
546
+ button.innerHTML = '<i class="fas fa-check"></i>';
547
+ button.classList.add('reverted');
548
+ button.title = 'Reverted';
549
+
550
+ // Mark the table row as reverted
551
+ const targetRow = button.closest('.target-row');
552
+ if (targetRow) {
553
+ targetRow.classList.add('reverted');
554
+ targetRow.style.opacity = '0.6';
555
+ targetRow.style.textDecoration = 'line-through';
556
+ }
557
+
558
+ ui.notifications.info(\`Damage application reverted for \${targetData.name}\`);
559
+ } catch (error) {
560
+ ui.notifications.error(\`Failed to revert \${targetData.name}: \${error.message}\`);
561
+ }
562
+ }
563
+ }
564
+ `.appendNewLineIfNotEmpty();
565
+ fs.writeFileSync(generatedFilePath, toString(fileNode));
566
+ }
567
+ export function generateStandardChatCardTemplate(destination) {
568
+ const generatedFileDir = path.join(destination, "system", "templates", "chat");
569
+ const generatedFilePath = path.join(generatedFileDir, `standard-card.hbs`);
570
+ if (!fs.existsSync(generatedFileDir)) {
571
+ fs.mkdirSync(generatedFileDir, { recursive: true });
572
+ }
573
+ const fileNode = expandToNode `
574
+ <div class="{{cssClass}} standard-chat-card chat-card">
575
+ {{#if hasDescription}}
576
+ <div class="chat-header collapsible collapsed">
577
+ <header class="flexrow">
578
+ <img src="{{document.img}}" title="{{document.name}}" width="50" height="50">
579
+ <div class="title">
580
+ <div class="name">{{document.name}}</div>
581
+ <div class="type">{{localize document.type}}</div>
582
+ </div>
583
+ <i class="collapse-icon fas fa-chevron-down fa-fw"></i>
584
+ </header>
585
+
586
+ <section class="description collapsible-content">
587
+ {{{description}}}
588
+ </section>
589
+ </div>
590
+ {{else}}
591
+ <div class="chat-header">
592
+ <header class="flexrow">
593
+ <img src="{{document.img}}" title="{{document.name}}" width="50" height="50">
594
+ <div class="title">
595
+ <div class="name">{{document.name}}</div>
596
+ <div class="type">{{localize document.type}}</div>
597
+ </div>
598
+ </header>
599
+ </div>
600
+ {{/if}}
601
+ <div class="chat-info">
602
+ <dl>
603
+ {{#each parts}}
604
+ {{#if this.isRoll}}
605
+ {{#if this.isDamageRoll}}
606
+ <div class="dice-roll damage-roll wide" data-damage-type="{{this.damageType}}">
607
+ <div class="dice-result">
608
+ <h4 class="dice-total">
609
+ {{#if this.damageIcon}}<i class="{{this.damageIcon}}" style="color: {{this.damageColor}};"></i>{{else}}<i class="fa-solid fa-dice-d20"></i>{{/if}}
610
+ <span class="dice-info" data-tooltip="{{this.value.cleanFormula}}">
611
+ <span class="label">{{this.label}}:</span>
612
+ <span class="formula">{{#if this.value._displayFormula}}{{this.value._displayFormula}}{{else}}{{this.value.cleanFormula}}{{/if}}</span>
613
+ {{#if this.damageType}}<span class="damage-type" style="color: {{this.damageColor}};">[{{this.damageType}}]</span>{{/if}}
614
+ </span>
615
+ <span class="result">{{this.value._total}}</span>
616
+ </h4>
617
+ {{{this.tooltip}}}
618
+ </div>
619
+ </div>
620
+ {{else}}
621
+ <div class="dice-roll wide">
622
+ <div class="dice-result">
623
+ <h4 class="dice-total"><i class="fa-solid fa-dice-d20"></i> <span class="dice-info" data-tooltip="{{this.value.cleanFormula}}"><span class="label">{{this.label}}:</span> <span class="formula">{{#if this.value._displayFormula}}{{this.value._displayFormula}}{{else}}{{this.value.cleanFormula}}{{/if}}</span></span> <span class="result">{{this.value._total}}</span></h4>
624
+ {{{this.tooltip}}}
625
+ </div>
626
+ </div>
627
+ {{/if}}
628
+ {{else if this.isMeasuredTemplate}}
629
+ <div class="measured-template wide" data-type="{{this.object.type}}" data-distance="{{this.object.distance}}" data-direction="{{this.object.direction}}" data-angle="{{this.object.angle}}" data-width="{{this.object.width}}">
630
+ <div class="measured-template-button">
631
+ <h4 class="summary"><i class="fa-solid fa-ruler-combined"></i> <span class="info">{{this.value}}</span><span class="result action" data-action="place" data-tooltip="Place"><i class="fa-solid fa-border-outer"></i></span></h4>
632
+ </div>
633
+ </div>
634
+ {{else if this.isParagraph}}
635
+ <div class="wide">
636
+ <dt>{{this.value}}</dt>
637
+ <dd></dd>
638
+ </div>
639
+ {{else}}
640
+ {{#if this.hasValue}}
641
+ {{#if this.wide}}
642
+ <div class="wide collapsible">
643
+ <dt class="title">{{this.label}} <i class="collapse-icon fas fa-chevron-down fa-fw"></i></dt>
644
+ <dd class="collapsible-content">{{{this.value}}}</dd>
645
+ </div>
646
+ {{else}}
647
+ <dt>{{this.label}}</dt>
648
+ <dd>{{{this.value}}}</dd>
649
+ {{/if}}
650
+ {{/if}}
651
+ {{/if}}
652
+ {{/each}}
653
+ </dl>
654
+
655
+ <div class="chat-info-tags">
656
+ {{#each tags}}
657
+ {{#if this.hasValue}}
658
+ <div class="tag"><span class="label">{{this.label}}</span> {{this.value}}</div>
659
+ {{/if}}
660
+ {{/each}}
661
+ </div>
662
+ </div>
663
+ {{#if hasEffects}}
664
+ <div class="chat-effects collapsible">
665
+ <h3 class="title">{{localize "EFFECTS.TabEffects"}} <i class="collapse-icon fas fa-chevron-down fa-fw"></i></h3>
666
+ <div class="effects collapsible-content">
667
+ {{#each document.effects}}
668
+ <div class="effect" draggable="true" data-uuid="{{this.uuid}}">
669
+ <header class="flexrow">
670
+ <img src="{{this.img}}" title="{{this.name}}" width="30" height="30">
671
+ <div class="name">{{this.name}}</div>
672
+ </header>
673
+ <div class="effect-content">{{{this.description}}}</div>
674
+ </div>
675
+ {{/each}}
676
+ </div>
677
+ </div>
678
+ {{/if}}
679
+ </div>
680
+ `.appendNewLineIfNotEmpty();
681
+ fs.writeFileSync(generatedFilePath, toString(fileNode));
682
+ }
683
+ //# sourceMappingURL=chat-card-generator.js.map