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.
- package/.claude/agents/langium-language-designer.md +38 -38
- package/.claude/agents/typescript-vscode-expert.md +29 -29
- package/.claude/agents/ui-ux-designer.md +36 -36
- package/.claude/settings.local.json +33 -33
- package/.idea/inspectionProfiles/Project_Default.xml +6 -6
- package/.idea/isdl.iml +13 -13
- package/.idea/modules.xml +8 -8
- package/.idea/vcs.xml +6 -6
- package/.idea/watcherTasks.xml +3 -3
- package/.vscodeignore +18 -18
- package/LICENSE +673 -673
- package/README.md +86 -86
- package/bin/cli.js +4 -4
- package/bin/lsp.js +8 -8
- package/out/_backgrounds.scss +91 -91
- package/out/_handlebars.scss +497 -497
- package/out/_isdlStyles.scss +1444 -1381
- package/out/_vuetifyOverrides.scss +425 -425
- package/out/_vuetifyStyles.scss +31957 -31957
- package/out/cli/components/_backgrounds.scss +91 -91
- package/out/cli/components/_handlebars.scss +497 -497
- package/out/cli/components/_isdlStyles.scss +1444 -1381
- package/out/cli/components/_vuetifyOverrides.scss +425 -425
- package/out/cli/components/_vuetifyStyles.scss +31957 -31957
- package/out/cli/components/active-effect-sheet-generator.js +453 -453
- package/out/cli/components/chat-card-generator.js +654 -651
- package/out/cli/components/chat-card-generator.js.map +1 -1
- package/out/cli/components/css-generator.js +4 -4
- package/out/cli/components/damage-roll-generator.js +160 -160
- package/out/cli/components/datamodel-generator.js +264 -257
- package/out/cli/components/datamodel-generator.js.map +1 -1
- package/out/cli/components/derived-data-generator.js +923 -923
- package/out/cli/components/hotbar-drop-hook-generator.js +82 -82
- package/out/cli/components/init-hook-generator.js +495 -495
- package/out/cli/components/language-generator.js +1 -1
- package/out/cli/components/language-generator.js.map +1 -1
- package/out/cli/components/measured-template-preview.js +221 -221
- package/out/cli/components/method-generator.js +979 -887
- package/out/cli/components/method-generator.js.map +1 -1
- package/out/cli/components/ready-hook-generator.js +404 -404
- package/out/cli/components/token-generator.js +116 -116
- package/out/cli/components/vue/base-components/vue-attribute.js +138 -138
- package/out/cli/components/vue/base-components/vue-boolean.js +64 -64
- package/out/cli/components/vue/base-components/vue-calculator.js +93 -93
- package/out/cli/components/vue/base-components/vue-damage-application.js +356 -356
- package/out/cli/components/vue/base-components/vue-damage-bonuses.js +165 -165
- package/out/cli/components/vue/base-components/vue-damage-resistances.js +196 -196
- package/out/cli/components/vue/base-components/vue-damage-track.js +121 -121
- package/out/cli/components/vue/base-components/vue-date-time.js +42 -42
- package/out/cli/components/vue/base-components/vue-dice.js +98 -98
- package/out/cli/components/vue/base-components/vue-die.js +73 -73
- package/out/cli/components/vue/base-components/vue-document-choice.js +149 -149
- package/out/cli/components/vue/base-components/vue-document-choices.js +179 -179
- package/out/cli/components/vue/base-components/vue-document-link.js +60 -60
- package/out/cli/components/vue/base-components/vue-extended-choice.js +88 -88
- package/out/cli/components/vue/base-components/vue-inventory.js +519 -519
- package/out/cli/components/vue/base-components/vue-macro-choice.js +138 -138
- package/out/cli/components/vue/base-components/vue-measured-template.js +530 -530
- package/out/cli/components/vue/base-components/vue-money.js +483 -483
- package/out/cli/components/vue/base-components/vue-number.js +174 -174
- package/out/cli/components/vue/base-components/vue-paperdoll.js +43 -43
- package/out/cli/components/vue/base-components/vue-parent-property-reference.js +76 -76
- package/out/cli/components/vue/base-components/vue-prosemirror.js +18 -18
- package/out/cli/components/vue/base-components/vue-resource.js +136 -136
- package/out/cli/components/vue/base-components/vue-roll-visualizer.js +286 -109
- package/out/cli/components/vue/base-components/vue-roll-visualizer.js.map +1 -1
- package/out/cli/components/vue/base-components/vue-self-property-reference.js +62 -62
- package/out/cli/components/vue/base-components/vue-string-choice.js +98 -98
- package/out/cli/components/vue/base-components/vue-string-choices.js +203 -203
- package/out/cli/components/vue/base-components/vue-string.js +60 -60
- package/out/cli/components/vue/base-components/vue-text-field.js +53 -53
- package/out/cli/components/vue/base-components/vue-tracker.js +431 -431
- package/out/cli/components/vue/vue-action-component-generator.js +64 -64
- package/out/cli/components/vue/vue-active-effect-sheet-generator.js +856 -856
- package/out/cli/components/vue/vue-datatable-sheet-class-generator.js +292 -292
- package/out/cli/components/vue/vue-datatable2-component-generator.js +824 -824
- package/out/cli/components/vue/vue-document-creation-app.js +121 -121
- package/out/cli/components/vue/vue-document-creation-sheet.js +94 -94
- package/out/cli/components/vue/vue-generator.js +40 -40
- package/out/cli/components/vue/vue-mixin.js +296 -296
- package/out/cli/components/vue/vue-pinned-datatable-component-generator.js +260 -260
- package/out/cli/components/vue/vue-prompt-generator.js +91 -76
- package/out/cli/components/vue/vue-prompt-generator.js.map +1 -1
- package/out/cli/components/vue/vue-prompt-sheet-class-generator.js +317 -317
- package/out/cli/components/vue/vue-sheet-application-generator.js +1177 -1167
- package/out/cli/components/vue/vue-sheet-application-generator.js.map +1 -1
- package/out/cli/components/vue/vue-sheet-class-generator.js +510 -510
- package/out/cli/generator.js +438 -433
- package/out/cli/generator.js.map +1 -1
- package/out/extension/github/githubAuthProvider.js +71 -29
- package/out/extension/github/githubAuthProvider.js.map +1 -1
- package/out/extension/github/githubGistManager.js +4 -3
- package/out/extension/github/githubGistManager.js.map +1 -1
- package/out/extension/github/githubManager.js +40 -38
- package/out/extension/github/githubManager.js.map +1 -1
- package/out/extension/github/githubQuickActions.js +120 -120
- package/out/extension/github/system-workflow.yml +47 -47
- package/out/extension/main.cjs +909 -532
- package/out/extension/main.cjs.map +3 -3
- package/out/extension/package.json +419 -419
- package/out/language/generated/ast.js +51 -2
- package/out/language/generated/ast.js.map +1 -1
- package/out/language/generated/grammar.js +14240 -13991
- package/out/language/generated/grammar.js.map +1 -1
- package/out/language/intelligent-system-design-language-validator.js +32 -2
- package/out/language/intelligent-system-design-language-validator.js.map +1 -1
- package/out/language/isdl-scope-provider.js +14 -1
- package/out/language/isdl-scope-provider.js.map +1 -1
- package/out/language/main.cjs +913 -569
- package/out/language/main.cjs.map +3 -3
- package/out/package.json +419 -419
- package/out/progressbar.min.js +6 -6
- package/out/styles.scss +762 -747
- package/out/test/validating/diagnostics.test.js +40 -0
- package/out/test/validating/diagnostics.test.js.map +1 -1
- package/package.json +419 -419
|
@@ -13,554 +13,557 @@ export function generateChatCardClass(entry, destination) {
|
|
|
13
13
|
function generateHpElement(document) {
|
|
14
14
|
const healthResource = getAllOfType(document.body, isResourceExp).find(x => x.tag == "health");
|
|
15
15
|
if (!healthResource) {
|
|
16
|
-
return expandToNode `
|
|
17
|
-
case '${document.name.toLocaleLowerCase()}':
|
|
18
|
-
// No health resource found.
|
|
19
|
-
break;
|
|
16
|
+
return expandToNode `
|
|
17
|
+
case '${document.name.toLocaleLowerCase()}':
|
|
18
|
+
// No health resource found.
|
|
19
|
+
break;
|
|
20
20
|
`;
|
|
21
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;
|
|
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
46
|
`;
|
|
47
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
|
-
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
let
|
|
245
|
-
let
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
finalDamage = Math.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
targetSummary.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
<td>\${
|
|
326
|
-
<td
|
|
327
|
-
<td>
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
\${summary.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
<
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
<th>
|
|
359
|
-
<th>
|
|
360
|
-
<th>
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
<button type="button" data-target="
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
<button type="button" data-mod="
|
|
419
|
-
<button type="button" data-mod="
|
|
420
|
-
<button type="button" data-mod="
|
|
421
|
-
<button type="button" data-mod="
|
|
422
|
-
<button type="button" data-mod="
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
event
|
|
481
|
-
target.classList.
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (!
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
button
|
|
548
|
-
button.
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
+
// The menu is mounted on <body>, not inside the roll element, so look it up by id.
|
|
194
|
+
const menu = $('#context-menu2')?.[0];
|
|
195
|
+
const applyTargetType = menu?.dataset?.target ?? 'selected';
|
|
196
|
+
const applyMod = menu?.dataset?.mod ? Number(menu.dataset.mod) : 1;
|
|
197
|
+
const bonusDamage = menu?.dataset?.bonus ? Number(menu.dataset.bonus) : 0;
|
|
198
|
+
|
|
199
|
+
let rollData = getRollFromElement(element);
|
|
200
|
+
if ( !rollData ) return;
|
|
201
|
+
|
|
202
|
+
let baseRoll = rollData.value;
|
|
203
|
+
if ( type === 'healing' ) {
|
|
204
|
+
baseRoll = -baseRoll;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
baseRoll *= applyMod;
|
|
208
|
+
|
|
209
|
+
const targets = getTargets(applyTargetType);
|
|
210
|
+
const applicationSummary = {
|
|
211
|
+
type: type,
|
|
212
|
+
originalDamage: rollData.value,
|
|
213
|
+
bonusDamage: bonusDamage,
|
|
214
|
+
multiplier: applyMod,
|
|
215
|
+
totalBaseDamage: baseRoll,
|
|
216
|
+
damageType: rollData.isDamageRoll ? rollData.damageType : null,
|
|
217
|
+
damageColor: rollData.isDamageRoll ? rollData.damageColor : null,
|
|
218
|
+
damageIcon: rollData.isDamageRoll ? rollData.damageIcon : null,
|
|
219
|
+
targets: [],
|
|
220
|
+
timestamp: Date.now(),
|
|
221
|
+
userId: game.user.id
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
for ( const target of targets ) {
|
|
225
|
+
console.log(type, baseRoll, target);
|
|
226
|
+
const update = {};
|
|
227
|
+
|
|
228
|
+
let roll = foundry.utils.duplicate(baseRoll);
|
|
229
|
+
|
|
230
|
+
// Create enhanced context with damage type and metadata if available
|
|
231
|
+
const context = {
|
|
232
|
+
amount: roll,
|
|
233
|
+
damageType: rollData.isDamageRoll ? rollData.damageType : null,
|
|
234
|
+
damageMetadata: rollData.isDamageRoll ? rollData.metadata : {},
|
|
235
|
+
color: rollData.isDamageRoll ? rollData.damageColor : null,
|
|
236
|
+
icon: rollData.isDamageRoll ? rollData.damageIcon : null,
|
|
237
|
+
isDamageRoll: rollData.isDamageRoll
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
await Hooks.callAllAsync('preApply' + type.titleCase(), target.actor, context);
|
|
241
|
+
roll = context.amount;
|
|
242
|
+
|
|
243
|
+
// Calculate resistance information for this target
|
|
244
|
+
let flatResistance = 0;
|
|
245
|
+
let percentResistance = 0;
|
|
246
|
+
let finalDamage = roll;
|
|
247
|
+
|
|
248
|
+
if (rollData.isDamageRoll && rollData.damageType && type === 'damage') {
|
|
249
|
+
// Get resistance information from actor using datamodel field names
|
|
250
|
+
const damageTypeKey = rollData.damageType.toLowerCase().replace(/\s+/g, '');
|
|
251
|
+
const flatResistanceField = \`\${damageTypeKey}damageresistanceflat\`;
|
|
252
|
+
const percentResistanceField = \`\${damageTypeKey}damageresistancepercent\`;
|
|
253
|
+
|
|
254
|
+
flatResistance = target.actor.system[flatResistanceField] || 0;
|
|
255
|
+
percentResistance = target.actor.system[percentResistanceField] || 0;
|
|
256
|
+
|
|
257
|
+
if (flatResistance > 0 || percentResistance > 0) {
|
|
258
|
+
// Apply resistance: (damage - flat) * (1 - percent/100)
|
|
259
|
+
finalDamage = Math.max(0, (roll - flatResistance) * (1 - percentResistance / 100));
|
|
260
|
+
finalDamage = Math.floor(finalDamage);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Store pre-application state for revert functionality
|
|
265
|
+
const preState = {};
|
|
266
|
+
const targetSummary = {
|
|
267
|
+
uuid: target.document.uuid,
|
|
268
|
+
name: target.actor.name,
|
|
269
|
+
appliedAmount: roll,
|
|
270
|
+
finalDamage: finalDamage,
|
|
271
|
+
flatResistance: flatResistance,
|
|
272
|
+
percentResistance: percentResistance,
|
|
273
|
+
preState: {},
|
|
274
|
+
postState: {}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
switch ( target.actor.type ) {
|
|
278
|
+
${joinToNode(entry.documents, document => generateHpElement(document), { appendNewLineIfNotEmpty: true })}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Store pre-state for revert
|
|
282
|
+
for (const [key, value] of Object.entries(update)) {
|
|
283
|
+
targetSummary.preState[key] = foundry.utils.getProperty(target.actor, key);
|
|
284
|
+
targetSummary.postState[key] = value;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
await target.actor.update(update);
|
|
288
|
+
applicationSummary.targets.push(targetSummary);
|
|
289
|
+
|
|
290
|
+
// Call the applied hook with enhanced context
|
|
291
|
+
Hooks.callAll('applied' + type.titleCase(), target.actor, context);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Create damage application summary chat card
|
|
295
|
+
await createDamageApplicationChatCard(applicationSummary);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function createDamageApplicationChatCard(summary) {
|
|
299
|
+
const setting = game.settings.get('${id}', 'damageApplicationChatCard');
|
|
300
|
+
if (setting === 'none') return;
|
|
301
|
+
|
|
302
|
+
const whisper = setting === 'gm' ? ChatMessage.getWhisperRecipients('GM') : [];
|
|
303
|
+
const typeLabel = summary.type === 'damage' ? 'Damage' : summary.type === 'healing' ? 'Healing' : 'Temp HP';
|
|
304
|
+
|
|
305
|
+
// Create HTML content for the chat card
|
|
306
|
+
const targetRows = summary.targets.map((target, index) => {
|
|
307
|
+
let resistanceText = 'N/A';
|
|
308
|
+
|
|
309
|
+
if (summary.type === 'damage' && summary.damageType) {
|
|
310
|
+
const hasFlat = target.flatResistance > 0;
|
|
311
|
+
const hasPercent = target.percentResistance > 0;
|
|
312
|
+
|
|
313
|
+
if (hasFlat || hasPercent) {
|
|
314
|
+
const parts = [];
|
|
315
|
+
if (hasFlat) parts.push(\`\${target.flatResistance}\`);
|
|
316
|
+
if (hasPercent) parts.push(\`\${target.percentResistance}%\`);
|
|
317
|
+
resistanceText = parts.join(', ');
|
|
318
|
+
} else {
|
|
319
|
+
resistanceText = 'None';
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return \`
|
|
324
|
+
<tr class="target-row" data-target-index="\${index}">
|
|
325
|
+
<td><strong>\${target.name}</strong></td>
|
|
326
|
+
<td>\${resistanceText}</td>
|
|
327
|
+
<td><strong>\${summary.type === 'healing' ? '+' : ''}\${target.finalDamage}</strong></td>
|
|
328
|
+
<td>
|
|
329
|
+
<button type="button" class="revert-target-damage" data-target-index="\${index}" data-summary='\${JSON.stringify(summary)}' title="Revert this target">
|
|
330
|
+
<i class="fas fa-undo"></i>
|
|
331
|
+
</button>
|
|
332
|
+
</td>
|
|
333
|
+
</tr>
|
|
334
|
+
\`;
|
|
335
|
+
}).join('');
|
|
336
|
+
|
|
337
|
+
const content = \`
|
|
338
|
+
<div class="damage-application-summary">
|
|
339
|
+
<div class="card-header">
|
|
340
|
+
<h3><i class="fas fa-sword-cross"></i> \${typeLabel} Applied</h3>
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
<div class="damage-summary">
|
|
344
|
+
<div class="base-damage">
|
|
345
|
+
<strong>Base Damage:</strong> \${summary.originalDamage}
|
|
346
|
+
\${summary.bonusDamage > 0 ? \`+ \${summary.bonusDamage} bonus\` : ''}
|
|
347
|
+
\${summary.multiplier !== 1 ? \` × \${summary.multiplier}\` : ''}
|
|
348
|
+
= <strong>\${summary.totalBaseDamage}</strong>
|
|
349
|
+
\${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>\` : ''}
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
352
|
+
|
|
353
|
+
<div class="targets-summary">
|
|
354
|
+
<h4>Applied to Targets:</h4>
|
|
355
|
+
<table class="damage-targets">
|
|
356
|
+
<thead>
|
|
357
|
+
<tr>
|
|
358
|
+
<th>Target</th>
|
|
359
|
+
<th>Resistance</th>
|
|
360
|
+
<th>Final Damage</th>
|
|
361
|
+
<th>Revert</th>
|
|
362
|
+
</tr>
|
|
363
|
+
</thead>
|
|
364
|
+
<tbody>
|
|
365
|
+
\${targetRows}
|
|
366
|
+
</tbody>
|
|
367
|
+
</table>
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
\`;
|
|
371
|
+
|
|
372
|
+
await ChatMessage.create({
|
|
373
|
+
user: game.user.id,
|
|
374
|
+
content: content,
|
|
375
|
+
whisper: whisper,
|
|
376
|
+
flags: {
|
|
377
|
+
[game.system.id]: {
|
|
378
|
+
damageApplicationSummary: summary
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if ( allowTargeting ) {
|
|
385
|
+
menuItems.push({
|
|
386
|
+
name: \`
|
|
387
|
+
<div class="damage-target flex flexrow">
|
|
388
|
+
<button type="button" data-target="targeted"><i class="fa-solid fa-bullseye"></i> \${game.i18n.localize('Targeted')}</button>
|
|
389
|
+
<button type="button" data-target="selected"><i class="fa-solid fa-expand"></i> \${game.i18n.localize('Selected')}</button>
|
|
390
|
+
</div>\`,
|
|
391
|
+
id: 'targets',
|
|
392
|
+
icon: '',
|
|
393
|
+
preventClose: true,
|
|
394
|
+
callback: (inlineRoll, event) => {
|
|
395
|
+
const button = event?.target ?? event?.currentTarget;
|
|
396
|
+
if (button?.dataset?.target) {
|
|
397
|
+
// Deactivate the other target type. The menu lives on <body>,
|
|
398
|
+
// so scope lookups to the menu element rather than the roll.
|
|
399
|
+
const menuEl = $('#context-menu2');
|
|
400
|
+
menuEl.find('button[data-target].active').removeClass('active');
|
|
401
|
+
// Set the target type on the menu for later reference.
|
|
402
|
+
const menu = menuEl[0];
|
|
403
|
+
if (menu) {
|
|
404
|
+
menu.dataset.target = button.dataset.target;
|
|
405
|
+
}
|
|
406
|
+
// Toggle the active button and update the user setting.
|
|
407
|
+
button.classList.add('active');
|
|
408
|
+
game.settings.set('${id}', 'userTargetDamageApplicationType', button.dataset.target);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Add damage multipliers.
|
|
415
|
+
menuItems.push({
|
|
416
|
+
name: \`
|
|
417
|
+
<div class="damage-modifiers flex flexrow">
|
|
418
|
+
<button type="button" data-mod="0.25">¼x</button>
|
|
419
|
+
<button type="button" data-mod="0.5">½x</button>
|
|
420
|
+
<button type="button" data-mod="1" class="active">1x</button>
|
|
421
|
+
<button type="button" data-mod="1.5">1.5x</button>
|
|
422
|
+
<button type="button" data-mod="2">2x</button>
|
|
423
|
+
<button type="button" data-mod="3">3x</button>
|
|
424
|
+
<button type="button" data-mod="4">4x</button>
|
|
425
|
+
</div>\`,
|
|
426
|
+
id: 'modifiers',
|
|
427
|
+
icon: '',
|
|
428
|
+
preventClose: true,
|
|
429
|
+
callback: (inlineRoll, event) => {
|
|
430
|
+
const button = event?.target ?? event?.currentTarget;
|
|
431
|
+
if (button?.dataset?.mod) {
|
|
432
|
+
// Deactivate the other multiplier. The menu lives on <body>, so
|
|
433
|
+
// scope lookups to the menu element rather than the roll.
|
|
434
|
+
const menuEl = $('#context-menu2');
|
|
435
|
+
menuEl.find('button[data-mod].active').removeClass('active');
|
|
436
|
+
|
|
437
|
+
// Set the multiplier on the menu for later reference.
|
|
438
|
+
const menu = menuEl[0];
|
|
439
|
+
if (menu) {
|
|
440
|
+
menu.dataset.mod = button.dataset.mod;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Toggle the active button and update the user setting.
|
|
444
|
+
button.classList.add('active');
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
menuItems.push(
|
|
450
|
+
{
|
|
451
|
+
name: game.i18n.localize("CONTEXT.ApplyDamage"),
|
|
452
|
+
id: 'damage',
|
|
453
|
+
icon: '<i class="fas fa-tint"></i>',
|
|
454
|
+
callback: (inlineRoll, event) => apply(inlineRoll, event, 'damage')
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
name: game.i18n.localize("CONTEXT.ApplyHealing"),
|
|
458
|
+
id: 'healing',
|
|
459
|
+
icon: '<i class="fas fa-medkit"></i>',
|
|
460
|
+
callback: (inlineRoll, event) => apply(inlineRoll, event, 'healing')
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
name: game.i18n.localize("CONTEXT.ApplyTemp"),
|
|
464
|
+
id: 'temp-healing',
|
|
465
|
+
icon: '<i class="fas fa-heart"></i>',
|
|
466
|
+
callback: (inlineRoll, event) => apply(inlineRoll, event, 'temp')
|
|
467
|
+
}
|
|
468
|
+
);
|
|
469
|
+
new ContextMenu2($(this).parent(), \`[data-uuid=\${uuid}]\`, menuItems);
|
|
470
|
+
}
|
|
471
|
+
html.find('.inline-roll').each(applyMenus);
|
|
472
|
+
html.find('.dice-total').each(applyMenus);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/* -------------------------------------------- */
|
|
476
|
+
|
|
477
|
+
static _onChatCardToggleCollapsible(event) {
|
|
478
|
+
const target = event.currentTarget;
|
|
479
|
+
|
|
480
|
+
// If the target is a content-link, ignore the click event
|
|
481
|
+
if (event.target.classList.contains("content-link")) return;
|
|
482
|
+
|
|
483
|
+
event.preventDefault();
|
|
484
|
+
target.classList.toggle("collapsed");
|
|
485
|
+
|
|
486
|
+
// Clear the height from the chat popout container so that it appropriately resizes.
|
|
487
|
+
const popout = target.closest(".chat-popout");
|
|
488
|
+
if ( popout ) popout.style.height = "";
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/* -------------------------------------------- */
|
|
492
|
+
|
|
493
|
+
static _handleActionClick(event) {
|
|
494
|
+
event.preventDefault();
|
|
495
|
+
const action = event.currentTarget.dataset.action;
|
|
496
|
+
|
|
497
|
+
switch (action) {
|
|
498
|
+
case "place":
|
|
499
|
+
const template = event.currentTarget.closest(".measured-template");
|
|
500
|
+
if (!template) return;
|
|
501
|
+
|
|
502
|
+
const context = {
|
|
503
|
+
type: template.dataset.type,
|
|
504
|
+
distance: template.dataset.distance,
|
|
505
|
+
direction: template.dataset.direction,
|
|
506
|
+
angle: template.dataset.angle,
|
|
507
|
+
width: template.dataset.width
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
// Trigger the place action on the template
|
|
511
|
+
game.system.measuredTemplatePreviewClass.place(context, game.user.character?.sheet);
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
static async _onRevertTargetDamage(event) {
|
|
517
|
+
const button = event.currentTarget;
|
|
518
|
+
const summaryData = JSON.parse(button.dataset.summary);
|
|
519
|
+
const targetIndex = parseInt(button.dataset.targetIndex);
|
|
520
|
+
const targetData = summaryData.targets[targetIndex];
|
|
521
|
+
|
|
522
|
+
if (!targetData) {
|
|
523
|
+
ui.notifications.error("Target data not found");
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Confirm revert
|
|
528
|
+
const confirm = await Dialog.confirm({
|
|
529
|
+
title: "Revert Target Damage",
|
|
530
|
+
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>\`,
|
|
531
|
+
yes: () => true,
|
|
532
|
+
no: () => false
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
if (!confirm) return;
|
|
536
|
+
|
|
537
|
+
const tokenDocument = await fromUuid(targetData.uuid);
|
|
538
|
+
if (!tokenDocument || !tokenDocument.actor) {
|
|
539
|
+
ui.notifications.warn(\`Cannot find target: \${targetData.name}\`);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
try {
|
|
544
|
+
await tokenDocument.actor.update(targetData.preState);
|
|
545
|
+
console.log(\`Reverted \${targetData.name} to pre-application state\`);
|
|
546
|
+
|
|
547
|
+
// Update the button to show it's been reverted
|
|
548
|
+
button.disabled = true;
|
|
549
|
+
button.innerHTML = '<i class="fas fa-check"></i>';
|
|
550
|
+
button.classList.add('reverted');
|
|
551
|
+
button.title = 'Reverted';
|
|
552
|
+
|
|
553
|
+
// Mark the table row as reverted
|
|
554
|
+
const targetRow = button.closest('.target-row');
|
|
555
|
+
if (targetRow) {
|
|
556
|
+
targetRow.classList.add('reverted');
|
|
557
|
+
targetRow.style.opacity = '0.6';
|
|
558
|
+
targetRow.style.textDecoration = 'line-through';
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
ui.notifications.info(\`Damage application reverted for \${targetData.name}\`);
|
|
562
|
+
} catch (error) {
|
|
563
|
+
ui.notifications.error(\`Failed to revert \${targetData.name}: \${error.message}\`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
564
567
|
`.appendNewLineIfNotEmpty();
|
|
565
568
|
fs.writeFileSync(generatedFilePath, toString(fileNode));
|
|
566
569
|
}
|
|
@@ -570,113 +573,113 @@ export function generateStandardChatCardTemplate(destination) {
|
|
|
570
573
|
if (!fs.existsSync(generatedFileDir)) {
|
|
571
574
|
fs.mkdirSync(generatedFileDir, { recursive: true });
|
|
572
575
|
}
|
|
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>
|
|
576
|
+
const fileNode = expandToNode `
|
|
577
|
+
<div class="{{cssClass}} standard-chat-card chat-card">
|
|
578
|
+
{{#if hasDescription}}
|
|
579
|
+
<div class="chat-header collapsible collapsed">
|
|
580
|
+
<header class="flexrow">
|
|
581
|
+
<img src="{{document.img}}" title="{{document.name}}" width="50" height="50">
|
|
582
|
+
<div class="title">
|
|
583
|
+
<div class="name">{{document.name}}</div>
|
|
584
|
+
<div class="type">{{localize document.type}}</div>
|
|
585
|
+
</div>
|
|
586
|
+
<i class="collapse-icon fas fa-chevron-down fa-fw"></i>
|
|
587
|
+
</header>
|
|
588
|
+
|
|
589
|
+
<section class="description collapsible-content">
|
|
590
|
+
{{{description}}}
|
|
591
|
+
</section>
|
|
592
|
+
</div>
|
|
593
|
+
{{else}}
|
|
594
|
+
<div class="chat-header">
|
|
595
|
+
<header class="flexrow">
|
|
596
|
+
<img src="{{document.img}}" title="{{document.name}}" width="50" height="50">
|
|
597
|
+
<div class="title">
|
|
598
|
+
<div class="name">{{document.name}}</div>
|
|
599
|
+
<div class="type">{{localize document.type}}</div>
|
|
600
|
+
</div>
|
|
601
|
+
</header>
|
|
602
|
+
</div>
|
|
603
|
+
{{/if}}
|
|
604
|
+
<div class="chat-info">
|
|
605
|
+
<dl>
|
|
606
|
+
{{#each parts}}
|
|
607
|
+
{{#if this.isRoll}}
|
|
608
|
+
{{#if this.isDamageRoll}}
|
|
609
|
+
<div class="dice-roll damage-roll wide" data-damage-type="{{this.damageType}}">
|
|
610
|
+
<div class="dice-result">
|
|
611
|
+
<h4 class="dice-total">
|
|
612
|
+
{{#if this.damageIcon}}<i class="{{this.damageIcon}}" style="color: {{this.damageColor}};"></i>{{else}}<i class="fa-solid fa-dice-d20"></i>{{/if}}
|
|
613
|
+
<span class="dice-info" data-tooltip="{{this.value.cleanFormula}}">
|
|
614
|
+
<span class="label">{{this.label}}:</span>
|
|
615
|
+
<span class="formula">{{#if this.value._displayFormula}}{{this.value._displayFormula}}{{else}}{{this.value.cleanFormula}}{{/if}}</span>
|
|
616
|
+
{{#if this.damageType}}<span class="damage-type" style="color: {{this.damageColor}};">[{{this.damageType}}]</span>{{/if}}
|
|
617
|
+
</span>
|
|
618
|
+
<span class="result">{{this.value._total}}</span>
|
|
619
|
+
</h4>
|
|
620
|
+
{{{this.tooltip}}}
|
|
621
|
+
</div>
|
|
622
|
+
</div>
|
|
623
|
+
{{else}}
|
|
624
|
+
<div class="dice-roll wide">
|
|
625
|
+
<div class="dice-result">
|
|
626
|
+
<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>
|
|
627
|
+
{{{this.tooltip}}}
|
|
628
|
+
</div>
|
|
629
|
+
</div>
|
|
630
|
+
{{/if}}
|
|
631
|
+
{{else if this.isMeasuredTemplate}}
|
|
632
|
+
<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}}">
|
|
633
|
+
<div class="measured-template-button">
|
|
634
|
+
<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>
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
637
|
+
{{else if this.isParagraph}}
|
|
638
|
+
<div class="wide">
|
|
639
|
+
<dt>{{this.value}}</dt>
|
|
640
|
+
<dd></dd>
|
|
641
|
+
</div>
|
|
642
|
+
{{else}}
|
|
643
|
+
{{#if this.hasValue}}
|
|
644
|
+
{{#if this.wide}}
|
|
645
|
+
<div class="wide collapsible">
|
|
646
|
+
<dt class="title">{{this.label}} <i class="collapse-icon fas fa-chevron-down fa-fw"></i></dt>
|
|
647
|
+
<dd class="collapsible-content">{{{this.value}}}</dd>
|
|
648
|
+
</div>
|
|
649
|
+
{{else}}
|
|
650
|
+
<dt>{{this.label}}</dt>
|
|
651
|
+
<dd>{{{this.value}}}</dd>
|
|
652
|
+
{{/if}}
|
|
653
|
+
{{/if}}
|
|
654
|
+
{{/if}}
|
|
655
|
+
{{/each}}
|
|
656
|
+
</dl>
|
|
657
|
+
|
|
658
|
+
<div class="chat-info-tags">
|
|
659
|
+
{{#each tags}}
|
|
660
|
+
{{#if this.hasValue}}
|
|
661
|
+
<div class="tag"><span class="label">{{this.label}}</span> {{this.value}}</div>
|
|
662
|
+
{{/if}}
|
|
663
|
+
{{/each}}
|
|
664
|
+
</div>
|
|
665
|
+
</div>
|
|
666
|
+
{{#if hasEffects}}
|
|
667
|
+
<div class="chat-effects collapsible">
|
|
668
|
+
<h3 class="title">{{localize "EFFECTS.TabEffects"}} <i class="collapse-icon fas fa-chevron-down fa-fw"></i></h3>
|
|
669
|
+
<div class="effects collapsible-content">
|
|
670
|
+
{{#each document.effects}}
|
|
671
|
+
<div class="effect" draggable="true" data-uuid="{{this.uuid}}">
|
|
672
|
+
<header class="flexrow">
|
|
673
|
+
<img src="{{this.img}}" title="{{this.name}}" width="30" height="30">
|
|
674
|
+
<div class="name">{{this.name}}</div>
|
|
675
|
+
</header>
|
|
676
|
+
<div class="effect-content">{{{this.description}}}</div>
|
|
677
|
+
</div>
|
|
678
|
+
{{/each}}
|
|
679
|
+
</div>
|
|
680
|
+
</div>
|
|
681
|
+
{{/if}}
|
|
682
|
+
</div>
|
|
680
683
|
`.appendNewLineIfNotEmpty();
|
|
681
684
|
fs.writeFileSync(generatedFilePath, toString(fileNode));
|
|
682
685
|
}
|