@carmaclouds/core 2.3.1
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/dist/cache/CacheManager.d.ts.map +1 -0
- package/dist/cache/CacheManager.js +131 -0
- package/dist/cache/CacheManager.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/index.d.ts +11 -0
- package/dist/ir/index.d.ts.map +1 -0
- package/dist/ir/index.js +9 -0
- package/dist/ir/index.js.map +1 -0
- package/dist/ir/normalize.d.ts +10 -0
- package/dist/ir/normalize.d.ts.map +1 -0
- package/dist/ir/normalize.js +207 -0
- package/dist/ir/normalize.js.map +1 -0
- package/dist/ir/persistence.d.ts +26 -0
- package/dist/ir/persistence.d.ts.map +1 -0
- package/dist/ir/persistence.js +21 -0
- package/dist/ir/persistence.js.map +1 -0
- package/dist/ir/sync.d.ts +12 -0
- package/dist/ir/sync.d.ts.map +1 -0
- package/dist/ir/sync.js +36 -0
- package/dist/ir/sync.js.map +1 -0
- package/dist/ir/types.d.ts +143 -0
- package/dist/ir/types.d.ts.map +1 -0
- package/dist/ir/types.js +13 -0
- package/dist/ir/types.js.map +1 -0
- package/dist/ir/views/dnd5e.d.ts +40 -0
- package/dist/ir/views/dnd5e.d.ts.map +1 -0
- package/dist/ir/views/dnd5e.js +50 -0
- package/dist/ir/views/dnd5e.js.map +1 -0
- package/dist/render/character.d.ts +19 -0
- package/dist/render/character.d.ts.map +1 -0
- package/dist/render/character.js +156 -0
- package/dist/render/character.js.map +1 -0
- package/dist/render/h.d.ts +27 -0
- package/dist/render/h.d.ts.map +1 -0
- package/dist/render/h.js +64 -0
- package/dist/render/h.js.map +1 -0
- package/dist/render/index.d.ts +11 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +8 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/mount.d.ts +31 -0
- package/dist/render/mount.d.ts.map +1 -0
- package/dist/render/mount.js +63 -0
- package/dist/render/mount.js.map +1 -0
- package/dist/supabase/fields.d.ts.map +1 -0
- package/dist/supabase/fields.js +120 -0
- package/dist/supabase/fields.js.map +1 -0
- package/dist/types/character.d.ts.map +1 -0
- package/dist/types/character.js +5 -0
- package/dist/types/character.js.map +1 -0
- package/package.json +73 -0
- package/src/browser.js +51 -0
- package/src/cache/CacheManager.ts +174 -0
- package/src/common/browser-polyfill.js +319 -0
- package/src/common/debug.js +123 -0
- package/src/common/html-utils.js +134 -0
- package/src/common/theme-manager.js +265 -0
- package/src/index.ts +25 -0
- package/src/ir/__fixtures__/dnd5e-character.json +75962 -0
- package/src/ir/__fixtures__/non-dnd-character.json +14218 -0
- package/src/ir/index.ts +10 -0
- package/src/ir/normalize.ts +245 -0
- package/src/ir/persistence.ts +37 -0
- package/src/ir/sync.ts +49 -0
- package/src/ir/types.ts +161 -0
- package/src/ir/views/dnd5e.ts +94 -0
- package/src/lib/indexeddb-cache.js +320 -0
- package/src/modules/action-announcements.js +102 -0
- package/src/modules/action-display.js +1557 -0
- package/src/modules/action-executor.js +860 -0
- package/src/modules/action-filters.js +167 -0
- package/src/modules/action-options.js +117 -0
- package/src/modules/card-creator.js +142 -0
- package/src/modules/character-portrait.js +169 -0
- package/src/modules/character-trait-popups.js +959 -0
- package/src/modules/character-traits.js +814 -0
- package/src/modules/class-feature-edge-cases.js +1320 -0
- package/src/modules/color-utils.js +69 -0
- package/src/modules/combat-maneuver-edge-cases.js +660 -0
- package/src/modules/companions-manager.js +178 -0
- package/src/modules/concentration-tracker.js +178 -0
- package/src/modules/data-manager.js +514 -0
- package/src/modules/dice-roller.js +719 -0
- package/src/modules/effects-manager.js +743 -0
- package/src/modules/feature-modals.js +1264 -0
- package/src/modules/formula-resolver.js +444 -0
- package/src/modules/gm-mode.js +184 -0
- package/src/modules/health-modals.js +399 -0
- package/src/modules/hp-management.js +752 -0
- package/src/modules/inventory-manager.js +242 -0
- package/src/modules/macro-system.js +825 -0
- package/src/modules/notification-system.js +92 -0
- package/src/modules/racial-feature-edge-cases.js +746 -0
- package/src/modules/resource-manager.js +775 -0
- package/src/modules/sheet-builder.js +654 -0
- package/src/modules/spell-action-modals.js +583 -0
- package/src/modules/spell-cards.js +602 -0
- package/src/modules/spell-casting.js +723 -0
- package/src/modules/spell-display.js +314 -0
- package/src/modules/spell-edge-cases.js +509 -0
- package/src/modules/spell-macros.js +201 -0
- package/src/modules/spell-modals.js +1221 -0
- package/src/modules/spell-slots.js +224 -0
- package/src/modules/status-bar-bridge.js +101 -0
- package/src/modules/ui-utilities.js +284 -0
- package/src/modules/warlock-invocations.js +219 -0
- package/src/modules/window-management.js +211 -0
- package/src/render/character.ts +234 -0
- package/src/render/h.ts +74 -0
- package/src/render/index.ts +10 -0
- package/src/render/mount.ts +94 -0
- package/src/supabase/client.js +1383 -0
- package/src/supabase/config.js +60 -0
- package/src/supabase/fields.ts +129 -0
- package/src/types/character.ts +85 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
// Spell Edge Cases Configuration
|
|
2
|
+
// Note: debug utility is available globally via window.debug from debug.js
|
|
3
|
+
// Handles special spell mechanics that need custom behavior
|
|
4
|
+
|
|
5
|
+
const SPELL_EDGE_CASES = {
|
|
6
|
+
// Healing spells that should announce when used
|
|
7
|
+
'cure wounds': {
|
|
8
|
+
type: 'healing_announcement',
|
|
9
|
+
description: 'Healing spell that announces usage'
|
|
10
|
+
},
|
|
11
|
+
'healing word': {
|
|
12
|
+
type: 'healing_announcement',
|
|
13
|
+
description: 'Healing spell that announces usage'
|
|
14
|
+
},
|
|
15
|
+
'lesser restoration': {
|
|
16
|
+
type: 'healing_announcement',
|
|
17
|
+
description: 'Healing spell that announces usage'
|
|
18
|
+
},
|
|
19
|
+
'mass cure wounds': {
|
|
20
|
+
type: 'healing_announcement',
|
|
21
|
+
description: 'Healing spell that announces usage'
|
|
22
|
+
},
|
|
23
|
+
'mass healing word': {
|
|
24
|
+
type: 'healing_announcement',
|
|
25
|
+
description: 'Healing spell that announces usage'
|
|
26
|
+
},
|
|
27
|
+
'heal': {
|
|
28
|
+
type: 'healing_announcement',
|
|
29
|
+
description: 'Healing spell that announces usage'
|
|
30
|
+
},
|
|
31
|
+
'regenerate': {
|
|
32
|
+
type: 'healing_announcement',
|
|
33
|
+
description: 'Healing spell that announces usage'
|
|
34
|
+
},
|
|
35
|
+
'mass heal': {
|
|
36
|
+
type: 'healing_announcement',
|
|
37
|
+
description: 'Healing spell that announces usage'
|
|
38
|
+
},
|
|
39
|
+
'true resurrection': {
|
|
40
|
+
type: 'healing_announcement',
|
|
41
|
+
description: 'Healing spell that announces usage'
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// Spells that are too complicated for normal casting
|
|
45
|
+
'wish': {
|
|
46
|
+
type: 'too_complicated',
|
|
47
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
48
|
+
},
|
|
49
|
+
'miracle': {
|
|
50
|
+
type: 'too_complicated',
|
|
51
|
+
description: 'Too complicated for normal casting - requires DM intervention',
|
|
52
|
+
ruleset: '2014',
|
|
53
|
+
notes: '3.5e spell, not in 2024 PHB'
|
|
54
|
+
},
|
|
55
|
+
'true polymorph': {
|
|
56
|
+
type: 'too_complicated',
|
|
57
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
58
|
+
},
|
|
59
|
+
'shapechange': {
|
|
60
|
+
type: 'too_complicated',
|
|
61
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
62
|
+
},
|
|
63
|
+
'astral projection': {
|
|
64
|
+
type: 'too_complicated',
|
|
65
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
66
|
+
},
|
|
67
|
+
'etherealness': {
|
|
68
|
+
type: 'too_complicated',
|
|
69
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
70
|
+
},
|
|
71
|
+
'plane shift': {
|
|
72
|
+
type: 'too_complicated',
|
|
73
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
74
|
+
},
|
|
75
|
+
'teleport': {
|
|
76
|
+
type: 'too_complicated',
|
|
77
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
78
|
+
},
|
|
79
|
+
'word of recall': {
|
|
80
|
+
type: 'too_complicated',
|
|
81
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
82
|
+
},
|
|
83
|
+
'contingency': {
|
|
84
|
+
type: 'too_complicated',
|
|
85
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
86
|
+
},
|
|
87
|
+
'glyph of warding': {
|
|
88
|
+
type: 'too_complicated',
|
|
89
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
90
|
+
},
|
|
91
|
+
'symbol': {
|
|
92
|
+
type: 'conditional_damage',
|
|
93
|
+
description: 'Has conditional/situational damage - adds Cast button'
|
|
94
|
+
},
|
|
95
|
+
'meld into stone': {
|
|
96
|
+
type: 'conditional_damage',
|
|
97
|
+
description: 'Has conditional/situational damage - adds Cast button'
|
|
98
|
+
},
|
|
99
|
+
'geas': {
|
|
100
|
+
type: 'conditional_damage',
|
|
101
|
+
description: 'Has conditional/situational damage - adds Cast button'
|
|
102
|
+
},
|
|
103
|
+
'programmed illusion': {
|
|
104
|
+
type: 'too_complicated',
|
|
105
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
106
|
+
},
|
|
107
|
+
'sequester': {
|
|
108
|
+
type: 'too_complicated',
|
|
109
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
110
|
+
},
|
|
111
|
+
'clone': {
|
|
112
|
+
type: 'too_complicated',
|
|
113
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
114
|
+
},
|
|
115
|
+
'magic jar': {
|
|
116
|
+
type: 'too_complicated',
|
|
117
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
118
|
+
},
|
|
119
|
+
'imprisonment': {
|
|
120
|
+
type: 'too_complicated',
|
|
121
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
122
|
+
},
|
|
123
|
+
'time stop': {
|
|
124
|
+
type: 'too_complicated',
|
|
125
|
+
description: 'Too complicated for normal casting - requires DM intervention',
|
|
126
|
+
ruleset: '2014',
|
|
127
|
+
notes: '1d4+1 rounds'
|
|
128
|
+
},
|
|
129
|
+
'time stop (2024)': {
|
|
130
|
+
type: 'too_complicated',
|
|
131
|
+
description: 'Too complicated for normal casting - requires DM intervention',
|
|
132
|
+
ruleset: '2024',
|
|
133
|
+
notes: '1d4+1 turns (changed from rounds)'
|
|
134
|
+
},
|
|
135
|
+
'mirage arcane': {
|
|
136
|
+
type: 'too_complicated',
|
|
137
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
138
|
+
},
|
|
139
|
+
'forcecage': {
|
|
140
|
+
type: 'too_complicated',
|
|
141
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
142
|
+
},
|
|
143
|
+
'maze': {
|
|
144
|
+
type: 'too_complicated',
|
|
145
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
146
|
+
},
|
|
147
|
+
'simulacrum': {
|
|
148
|
+
type: 'too_complicated',
|
|
149
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
150
|
+
},
|
|
151
|
+
'gate': {
|
|
152
|
+
type: 'too_complicated',
|
|
153
|
+
description: 'Too complicated for normal casting - requires DM intervention'
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
// Reusable spells that need checkbox
|
|
157
|
+
'spiritual weapon': {
|
|
158
|
+
type: 'reusable',
|
|
159
|
+
description: 'Can be recast without using spell slot',
|
|
160
|
+
ruleset: '2014',
|
|
161
|
+
notes: 'Bonus action to summon, separate action to attack'
|
|
162
|
+
},
|
|
163
|
+
'spiritual weapon (2024)': {
|
|
164
|
+
type: 'reusable',
|
|
165
|
+
description: 'Can be recast without using spell slot',
|
|
166
|
+
ruleset: '2024',
|
|
167
|
+
notes: 'Bonus action to summon AND attack on same turn'
|
|
168
|
+
},
|
|
169
|
+
'mage armor': {
|
|
170
|
+
type: 'reusable',
|
|
171
|
+
description: 'Can be recast without using spell slot'
|
|
172
|
+
},
|
|
173
|
+
'shield': {
|
|
174
|
+
type: 'reusable',
|
|
175
|
+
description: 'Can be recast without using spell slot'
|
|
176
|
+
},
|
|
177
|
+
'detect magic': {
|
|
178
|
+
type: 'reusable',
|
|
179
|
+
description: 'Can be recast without using spell slot',
|
|
180
|
+
ruleset: '2014',
|
|
181
|
+
notes: 'Standard casting'
|
|
182
|
+
},
|
|
183
|
+
'detect magic (2024)': {
|
|
184
|
+
type: 'reusable',
|
|
185
|
+
description: 'Can be recast without using spell slot',
|
|
186
|
+
ruleset: '2024',
|
|
187
|
+
notes: 'Now a ritual spell'
|
|
188
|
+
},
|
|
189
|
+
'guidance': {
|
|
190
|
+
type: 'reusable',
|
|
191
|
+
description: 'Can be recast without using spell slot',
|
|
192
|
+
ruleset: '2014',
|
|
193
|
+
notes: 'Action, must be used before roll'
|
|
194
|
+
},
|
|
195
|
+
'guidance (2024)': {
|
|
196
|
+
type: 'reusable',
|
|
197
|
+
description: 'Can be recast without using spell slot',
|
|
198
|
+
ruleset: '2024',
|
|
199
|
+
notes: 'Reaction, can be used after the roll'
|
|
200
|
+
},
|
|
201
|
+
'resistance': {
|
|
202
|
+
type: 'reusable',
|
|
203
|
+
description: 'Can be recast without using spell slot'
|
|
204
|
+
},
|
|
205
|
+
'virtue': {
|
|
206
|
+
type: 'reusable',
|
|
207
|
+
description: 'Can be recast without using spell slot',
|
|
208
|
+
ruleset: '2014',
|
|
209
|
+
notes: '3.5e spell, not in 2024 PHB'
|
|
210
|
+
},
|
|
211
|
+
'light': {
|
|
212
|
+
type: 'reusable',
|
|
213
|
+
description: 'Can be recast without using spell slot'
|
|
214
|
+
},
|
|
215
|
+
'fire bolt': {
|
|
216
|
+
type: 'reusable',
|
|
217
|
+
description: 'Can be recast without using spell slot'
|
|
218
|
+
},
|
|
219
|
+
'ray of frost': {
|
|
220
|
+
type: 'reusable',
|
|
221
|
+
description: 'Can be recast without using spell slot'
|
|
222
|
+
},
|
|
223
|
+
'shocking grasp': {
|
|
224
|
+
type: 'reusable',
|
|
225
|
+
description: 'Can be recast without using spell slot'
|
|
226
|
+
},
|
|
227
|
+
'toll the dead': {
|
|
228
|
+
type: 'reusable',
|
|
229
|
+
description: 'Can be recast without using spell slot'
|
|
230
|
+
},
|
|
231
|
+
'sacred flame': {
|
|
232
|
+
type: 'reusable',
|
|
233
|
+
description: 'Can be recast without using spell slot'
|
|
234
|
+
},
|
|
235
|
+
'eldritch blast': {
|
|
236
|
+
type: 'reusable',
|
|
237
|
+
description: 'Can be recast without using spell slot'
|
|
238
|
+
},
|
|
239
|
+
'poison spray': {
|
|
240
|
+
type: 'reusable',
|
|
241
|
+
description: 'Can be recast without using spell slot'
|
|
242
|
+
},
|
|
243
|
+
'frostbite': {
|
|
244
|
+
type: 'reusable',
|
|
245
|
+
description: 'Can be recast without using spell slot'
|
|
246
|
+
},
|
|
247
|
+
'true strike': {
|
|
248
|
+
type: 'reusable',
|
|
249
|
+
description: 'Can be recast without using spell slot',
|
|
250
|
+
ruleset: '2014',
|
|
251
|
+
notes: 'Advantage on next attack roll'
|
|
252
|
+
},
|
|
253
|
+
'true strike (2024)': {
|
|
254
|
+
type: 'reusable',
|
|
255
|
+
description: 'Can be recast without using spell slot',
|
|
256
|
+
ruleset: '2024',
|
|
257
|
+
notes: 'COMPLETELY REDESIGNED - advantage on next attack within same turn + extra damage'
|
|
258
|
+
},
|
|
259
|
+
'blade ward': {
|
|
260
|
+
type: 'reusable',
|
|
261
|
+
description: 'Can be recast without using spell slot'
|
|
262
|
+
},
|
|
263
|
+
'chill touch': {
|
|
264
|
+
type: 'reusable',
|
|
265
|
+
description: 'Can be recast without using spell slot'
|
|
266
|
+
},
|
|
267
|
+
'minor illusion': {
|
|
268
|
+
type: 'reusable',
|
|
269
|
+
description: 'Can be recast without using spell slot'
|
|
270
|
+
},
|
|
271
|
+
'prestidigitation': {
|
|
272
|
+
type: 'reusable',
|
|
273
|
+
description: 'Can be recast without using spell slot'
|
|
274
|
+
},
|
|
275
|
+
'thaumaturgy': {
|
|
276
|
+
type: 'reusable',
|
|
277
|
+
description: 'Can be recast without using spell slot'
|
|
278
|
+
},
|
|
279
|
+
'druidcraft': {
|
|
280
|
+
type: 'reusable',
|
|
281
|
+
description: 'Can be recast without using spell slot'
|
|
282
|
+
},
|
|
283
|
+
'mending': {
|
|
284
|
+
type: 'reusable',
|
|
285
|
+
description: 'Can be recast without using spell slot'
|
|
286
|
+
},
|
|
287
|
+
'arcane vigor': {
|
|
288
|
+
type: 'reusable',
|
|
289
|
+
description: 'Can be recast without using spell slot',
|
|
290
|
+
ruleset: '2024',
|
|
291
|
+
notes: 'New 2024 cantrip - grants temporary HP'
|
|
292
|
+
},
|
|
293
|
+
'starry wisp': {
|
|
294
|
+
type: 'reusable',
|
|
295
|
+
description: 'Can be recast without using spell slot',
|
|
296
|
+
ruleset: '2024',
|
|
297
|
+
notes: 'New 2024 cantrip - light and guidance effect'
|
|
298
|
+
},
|
|
299
|
+
'sorcerous burst': {
|
|
300
|
+
type: 'reusable',
|
|
301
|
+
description: 'Can be recast without using spell slot',
|
|
302
|
+
ruleset: '2024',
|
|
303
|
+
notes: 'New 2024 sorcerer cantrip'
|
|
304
|
+
},
|
|
305
|
+
'conjure minor elementals': {
|
|
306
|
+
type: 'too_complicated',
|
|
307
|
+
description: 'Too complicated for normal casting - requires DM intervention',
|
|
308
|
+
ruleset: '2024',
|
|
309
|
+
notes: '2024 replacement for old summoning spells'
|
|
310
|
+
},
|
|
311
|
+
'conjure celestial': {
|
|
312
|
+
type: 'too_complicated',
|
|
313
|
+
description: 'Too complicated for normal casting - requires DM intervention',
|
|
314
|
+
ruleset: '2024',
|
|
315
|
+
notes: '2024 redesigned summoning spell'
|
|
316
|
+
},
|
|
317
|
+
'summon beast': {
|
|
318
|
+
type: 'too_complicated',
|
|
319
|
+
description: 'Too complicated for normal casting - requires DM intervention',
|
|
320
|
+
ruleset: '2024',
|
|
321
|
+
notes: '2024 standardized summoning spell'
|
|
322
|
+
},
|
|
323
|
+
'summon fey': {
|
|
324
|
+
type: 'too_complicated',
|
|
325
|
+
description: 'Too complicated for normal casting - requires DM intervention',
|
|
326
|
+
ruleset: '2024',
|
|
327
|
+
notes: '2024 standardized summoning spell'
|
|
328
|
+
},
|
|
329
|
+
'hunter\'s mark (2024)': {
|
|
330
|
+
type: 'reusable',
|
|
331
|
+
description: 'Can be recast without using spell slot',
|
|
332
|
+
ruleset: '2024',
|
|
333
|
+
notes: 'Now a spell known for free, concentration changes'
|
|
334
|
+
},
|
|
335
|
+
'hex (2024)': {
|
|
336
|
+
type: 'reusable',
|
|
337
|
+
description: 'Can be recast without using spell slot',
|
|
338
|
+
ruleset: '2024',
|
|
339
|
+
notes: 'Similar to Hunter\'s Mark changes'
|
|
340
|
+
},
|
|
341
|
+
'counterspell (2024)': {
|
|
342
|
+
type: 'too_complicated',
|
|
343
|
+
description: 'Too complicated for normal casting - requires DM intervention',
|
|
344
|
+
ruleset: '2024',
|
|
345
|
+
notes: 'Different DC calculation in 2024'
|
|
346
|
+
},
|
|
347
|
+
'healing spirit (2024)': {
|
|
348
|
+
type: 'too_complicated',
|
|
349
|
+
description: 'Too complicated for normal casting - requires DM intervention',
|
|
350
|
+
ruleset: '2024',
|
|
351
|
+
notes: 'Nerfed heavily in 2024'
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// Check if a spell has an edge case
|
|
356
|
+
function isEdgeCase(spellName) {
|
|
357
|
+
if (!spellName) return false;
|
|
358
|
+
return SPELL_EDGE_CASES.hasOwnProperty(spellName.toLowerCase());
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Get edge case configuration for a spell
|
|
362
|
+
function getEdgeCase(spellName, ruleset = null) {
|
|
363
|
+
if (!spellName) return null;
|
|
364
|
+
const lowerName = spellName.toLowerCase();
|
|
365
|
+
|
|
366
|
+
// If ruleset is specified, try to get the specific version
|
|
367
|
+
if (ruleset) {
|
|
368
|
+
const specificVersion = SPELL_EDGE_CASES[`${lowerName} (${ruleset})`];
|
|
369
|
+
if (specificVersion) return specificVersion;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Fall back to default version
|
|
373
|
+
return SPELL_EDGE_CASES[lowerName] || null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Apply edge case modifications to spell options
|
|
377
|
+
function applyEdgeCaseModifications(spell, options, characterData = null) {
|
|
378
|
+
// Detect ruleset from character data if not provided
|
|
379
|
+
const ruleset = characterData ? detectRulesetFromCharacterData(characterData) : '2014';
|
|
380
|
+
const edgeCase = getEdgeCase(spell.name, ruleset);
|
|
381
|
+
|
|
382
|
+
if (!edgeCase) return { options, skipNormalButtons: false };
|
|
383
|
+
|
|
384
|
+
debug.log(`🎯 Applying ${ruleset} edge case for spell: ${spell.name}`);
|
|
385
|
+
|
|
386
|
+
let modifiedOptions = options;
|
|
387
|
+
let skipNormalButtons = false;
|
|
388
|
+
|
|
389
|
+
// Apply edge case logic based on type
|
|
390
|
+
switch (edgeCase.type) {
|
|
391
|
+
case 'healing_announcement':
|
|
392
|
+
// Healing spells should announce usage
|
|
393
|
+
modifiedOptions = options.map(option => ({
|
|
394
|
+
...option,
|
|
395
|
+
edgeCaseNote: edgeCase.notes || 'Announces healing usage'
|
|
396
|
+
}));
|
|
397
|
+
break;
|
|
398
|
+
|
|
399
|
+
case 'too_complicated':
|
|
400
|
+
// Too complicated spells get special handling
|
|
401
|
+
modifiedOptions = [];
|
|
402
|
+
skipNormalButtons = true;
|
|
403
|
+
break;
|
|
404
|
+
|
|
405
|
+
case 'reusable':
|
|
406
|
+
// Reusable spells get checkbox option
|
|
407
|
+
modifiedOptions = options.map(option => ({
|
|
408
|
+
...option,
|
|
409
|
+
edgeCaseNote: edgeCase.notes || 'Can be recast without spell slot'
|
|
410
|
+
}));
|
|
411
|
+
break;
|
|
412
|
+
|
|
413
|
+
case 'conditional_damage':
|
|
414
|
+
// Spells with conditional/situational damage get a "Cast" button
|
|
415
|
+
if (options.length > 0) {
|
|
416
|
+
modifiedOptions = [
|
|
417
|
+
{
|
|
418
|
+
type: 'cast',
|
|
419
|
+
label: 'Cast Spell',
|
|
420
|
+
icon: '✨',
|
|
421
|
+
color: '#9b59b6',
|
|
422
|
+
edgeCaseNote: edgeCase.notes || 'Spell has conditional damage'
|
|
423
|
+
},
|
|
424
|
+
...options
|
|
425
|
+
];
|
|
426
|
+
}
|
|
427
|
+
break;
|
|
428
|
+
|
|
429
|
+
default:
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return { options: modifiedOptions, skipNormalButtons };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Check if a spell is reusable
|
|
437
|
+
function isReuseableSpell(spellName, characterData = null) {
|
|
438
|
+
const ruleset = characterData ? detectRulesetFromCharacterData(characterData) : '2014';
|
|
439
|
+
const edgeCase = getEdgeCase(spellName, ruleset);
|
|
440
|
+
return edgeCase && edgeCase.type === 'reusable';
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Check if a spell is too complicated
|
|
444
|
+
function isTooComplicatedSpell(spellName, characterData = null) {
|
|
445
|
+
const ruleset = characterData ? detectRulesetFromCharacterData(characterData) : '2014';
|
|
446
|
+
const edgeCase = getEdgeCase(spellName, ruleset);
|
|
447
|
+
return edgeCase && edgeCase.type === 'too_complicated';
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Detect ruleset from character data (shared function)
|
|
451
|
+
function detectRulesetFromCharacterData(characterData) {
|
|
452
|
+
if (!characterData) return '2014'; // Default to 2014
|
|
453
|
+
|
|
454
|
+
// Check for 2024-specific features or changes
|
|
455
|
+
const features = characterData.features || [];
|
|
456
|
+
const actions = characterData.actions || [];
|
|
457
|
+
const spells = characterData.spells || [];
|
|
458
|
+
|
|
459
|
+
// More specific detection logic
|
|
460
|
+
const has2024Indicators = [
|
|
461
|
+
// Explicit 2024 markers (most reliable)
|
|
462
|
+
() => features.some(f => f.name && f.name.includes('2024')),
|
|
463
|
+
() => spells.some(s => s.description && s.description.includes('2024')),
|
|
464
|
+
|
|
465
|
+
// 2024-specific resource patterns
|
|
466
|
+
() => (characterData.resources || []).some(r => r.name && (
|
|
467
|
+
r.name.includes('proficiency') && r.name.includes('uses')
|
|
468
|
+
)),
|
|
469
|
+
|
|
470
|
+
// 2024-specific feature wording (more specific)
|
|
471
|
+
() => features.some(f => f.description && typeof f.description === 'string' && (
|
|
472
|
+
f.description.includes('uses = proficiency bonus') ||
|
|
473
|
+
f.description.includes('proficiency bonus uses')
|
|
474
|
+
)),
|
|
475
|
+
|
|
476
|
+
// 2024-specific class feature patterns
|
|
477
|
+
() => features.some(f => f.description && typeof f.description === 'string' && (
|
|
478
|
+
f.description.includes('bonus action') && f.description.includes('reaction')
|
|
479
|
+
)),
|
|
480
|
+
|
|
481
|
+
// 2024-specific spell patterns
|
|
482
|
+
() => spells.some(s => s.description && typeof s.description === 'string' && (
|
|
483
|
+
s.description.includes('ritual') && s.level > 0
|
|
484
|
+
))
|
|
485
|
+
];
|
|
486
|
+
|
|
487
|
+
// If any 2024 indicators are found, assume 2024 ruleset
|
|
488
|
+
const has2024Features = has2024Indicators.some(check => check());
|
|
489
|
+
|
|
490
|
+
if (has2024Features) {
|
|
491
|
+
debug.log('🔍 Detected 2024 ruleset from character data');
|
|
492
|
+
return '2024';
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Default to 2014
|
|
496
|
+
debug.log('🔍 Defaulting to 2014 ruleset');
|
|
497
|
+
return '2014';
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Expose to globalThis for importScripts usage
|
|
501
|
+
if (typeof globalThis !== 'undefined') {
|
|
502
|
+
globalThis.SPELL_EDGE_CASES = SPELL_EDGE_CASES;
|
|
503
|
+
globalThis.isEdgeCase = isEdgeCase;
|
|
504
|
+
globalThis.getEdgeCase = getEdgeCase;
|
|
505
|
+
globalThis.applyEdgeCaseModifications = applyEdgeCaseModifications;
|
|
506
|
+
globalThis.isReuseableSpell = isReuseableSpell;
|
|
507
|
+
globalThis.isTooComplicatedSpell = isTooComplicatedSpell;
|
|
508
|
+
globalThis.detectRulesetFromCharacterData = detectRulesetFromCharacterData;
|
|
509
|
+
}
|