@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,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Companions Manager Module
|
|
3
|
+
*
|
|
4
|
+
* Handles companion/familiar display and interaction.
|
|
5
|
+
* - Displays companion stats, abilities, and actions
|
|
6
|
+
* - Provides attack and damage roll buttons for companion actions
|
|
7
|
+
*
|
|
8
|
+
* Loaded as a plain script (no ES6 modules) to export to window.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
(function() {
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build and display companions list
|
|
16
|
+
* @param {Array} companions - Array of companion objects
|
|
17
|
+
*/
|
|
18
|
+
function buildCompanionsDisplay(companions) {
|
|
19
|
+
const container = document.getElementById('companions-container');
|
|
20
|
+
const section = document.getElementById('companions-section');
|
|
21
|
+
|
|
22
|
+
// Show the companions section
|
|
23
|
+
section.style.display = 'block';
|
|
24
|
+
|
|
25
|
+
container.innerHTML = '';
|
|
26
|
+
|
|
27
|
+
companions.forEach(companion => {
|
|
28
|
+
debug.log('🔍 DEBUG: Companion object in popup:', companion);
|
|
29
|
+
debug.log('🔍 DEBUG: Companion abilities:', companion.abilities);
|
|
30
|
+
debug.log('🔍 DEBUG: Companion abilities keys:', Object.keys(companion.abilities));
|
|
31
|
+
|
|
32
|
+
const companionCard = document.createElement('div');
|
|
33
|
+
companionCard.className = 'action-card';
|
|
34
|
+
companionCard.style.background = 'var(--bg-card)';
|
|
35
|
+
companionCard.style.borderColor = 'var(--border-card)';
|
|
36
|
+
|
|
37
|
+
// Header with name and basic info
|
|
38
|
+
const header = document.createElement('div');
|
|
39
|
+
header.className = 'action-header';
|
|
40
|
+
header.style.cursor = 'pointer';
|
|
41
|
+
|
|
42
|
+
const nameDiv = document.createElement('div');
|
|
43
|
+
nameDiv.innerHTML = `
|
|
44
|
+
<div class="action-name">🐾 ${companion.name}</div>
|
|
45
|
+
<div style="font-size: 0.85em; color: var(--text-secondary); font-style: italic;">
|
|
46
|
+
${companion.size} ${companion.type}${companion.alignment ? ', ' + companion.alignment : ''}
|
|
47
|
+
</div>
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
header.appendChild(nameDiv);
|
|
51
|
+
companionCard.appendChild(header);
|
|
52
|
+
|
|
53
|
+
// Stats block
|
|
54
|
+
const statsDiv = document.createElement('div');
|
|
55
|
+
statsDiv.className = 'action-description expanded';
|
|
56
|
+
statsDiv.style.display = 'block';
|
|
57
|
+
statsDiv.style.background = 'var(--bg-secondary)';
|
|
58
|
+
statsDiv.style.padding = '12px';
|
|
59
|
+
statsDiv.style.borderRadius = '4px';
|
|
60
|
+
statsDiv.style.marginTop = '10px';
|
|
61
|
+
|
|
62
|
+
let statsHTML = '<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 10px;">';
|
|
63
|
+
|
|
64
|
+
// AC, HP, Speed
|
|
65
|
+
if (companion.ac) statsHTML += `<div><strong>AC:</strong> ${companion.ac}</div>`;
|
|
66
|
+
if (companion.hp) statsHTML += `<div><strong>HP:</strong> ${companion.hp}</div>`;
|
|
67
|
+
if (companion.speed) statsHTML += `<div style="grid-column: span 3;"><strong>Speed:</strong> ${companion.speed}</div>`;
|
|
68
|
+
|
|
69
|
+
statsHTML += '</div>';
|
|
70
|
+
|
|
71
|
+
// Abilities
|
|
72
|
+
if (Object.keys(companion.abilities).length > 0) {
|
|
73
|
+
statsHTML += '<div style="display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; text-align: center; margin: 10px 0; padding: 8px; background: var(--bg-tertiary); border-radius: 4px;">';
|
|
74
|
+
['str', 'dex', 'con', 'int', 'wis', 'cha'].forEach(ability => {
|
|
75
|
+
if (companion.abilities[ability]) {
|
|
76
|
+
const abil = companion.abilities[ability];
|
|
77
|
+
statsHTML += `
|
|
78
|
+
<div>
|
|
79
|
+
<div style="font-weight: bold; font-size: 0.75em; color: var(--text-secondary);">${ability.toUpperCase()}</div>
|
|
80
|
+
<div style="font-size: 1.1em; color: var(--text-primary);">${abil.score}</div>
|
|
81
|
+
<div style="font-size: 0.9em; color: var(--accent-success);">(${abil.modifier >= 0 ? '+' : ''}${abil.modifier})</div>
|
|
82
|
+
</div>
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
statsHTML += '</div>';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Senses, Languages, PB
|
|
90
|
+
if (companion.senses) statsHTML += `<div style="margin: 5px 0; color: var(--text-primary);"><strong>Senses:</strong> ${companion.senses}</div>`;
|
|
91
|
+
if (companion.languages) statsHTML += `<div style="margin: 5px 0; color: var(--text-primary);"><strong>Languages:</strong> ${companion.languages}</div>`;
|
|
92
|
+
if (companion.proficiencyBonus) statsHTML += `<div style="margin: 5px 0; color: var(--text-primary);"><strong>Proficiency Bonus:</strong> +${companion.proficiencyBonus}</div>`;
|
|
93
|
+
|
|
94
|
+
// Features
|
|
95
|
+
if (companion.features && companion.features.length > 0) {
|
|
96
|
+
statsHTML += '<div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border-color);">';
|
|
97
|
+
companion.features.forEach(feature => {
|
|
98
|
+
statsHTML += `<div style="margin: 8px 0; color: var(--text-primary);"><strong>${feature.name}.</strong> ${feature.description}</div>`;
|
|
99
|
+
});
|
|
100
|
+
statsHTML += '</div>';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Actions with attack buttons
|
|
104
|
+
if (companion.actions && companion.actions.length > 0) {
|
|
105
|
+
statsHTML += '<div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border-color); color: var(--text-primary);"><strong>Actions</strong></div>';
|
|
106
|
+
companion.actions.forEach(action => {
|
|
107
|
+
statsHTML += `
|
|
108
|
+
<div style="margin: 10px 0; padding: 8px; background: var(--bg-action); border: 1px solid var(--accent-danger); border-radius: 4px;">
|
|
109
|
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
110
|
+
<div style="color: var(--text-primary);">
|
|
111
|
+
<strong>${action.name}.</strong> Melee Weapon Attack: +${action.attackBonus} to hit, ${action.reach}. <em>Hit:</em> ${action.damage}
|
|
112
|
+
</div>
|
|
113
|
+
<div style="display: flex; gap: 8px;">
|
|
114
|
+
<button class="attack-btn companion-attack-btn" data-name="${companion.name} - ${action.name}" data-bonus="${action.attackBonus}">⚔️ Attack</button>
|
|
115
|
+
<button class="damage-btn companion-damage-btn" data-name="${companion.name} - ${action.name}" data-damage="${action.damage}">💥 Damage</button>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
`;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
statsDiv.innerHTML = statsHTML;
|
|
124
|
+
companionCard.appendChild(statsDiv);
|
|
125
|
+
|
|
126
|
+
// Add event listeners for attack/damage buttons
|
|
127
|
+
companionCard.querySelectorAll('.companion-attack-btn').forEach(btn => {
|
|
128
|
+
btn.addEventListener('click', (e) => {
|
|
129
|
+
e.stopPropagation();
|
|
130
|
+
const name = btn.dataset.name;
|
|
131
|
+
const bonus = parseInt(btn.dataset.bonus);
|
|
132
|
+
|
|
133
|
+
// Announce companion attack
|
|
134
|
+
const announcement = `&{template:default} {{name=${getColoredBanner(characterData)}${characterData.name}'s ${name} attacks!}} {{Type=Companion Attack}}`;
|
|
135
|
+
const messageData = {
|
|
136
|
+
action: 'announceSpell',
|
|
137
|
+
message: announcement,
|
|
138
|
+
color: characterData.notificationColor
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
sendToRoll20(messageData);
|
|
142
|
+
|
|
143
|
+
roll(`${name} - Attack`, `1d20+${bonus}`);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
companionCard.querySelectorAll('.companion-damage-btn').forEach(btn => {
|
|
148
|
+
btn.addEventListener('click', (e) => {
|
|
149
|
+
e.stopPropagation();
|
|
150
|
+
const name = btn.dataset.name;
|
|
151
|
+
const damage = btn.dataset.damage;
|
|
152
|
+
|
|
153
|
+
// Announce companion damage
|
|
154
|
+
const announcement = `&{template:default} {{name=${getColoredBanner(characterData)}${characterData.name}'s ${name} deals damage!}} {{Type=Companion Damage}}`;
|
|
155
|
+
const messageData = {
|
|
156
|
+
action: 'announceSpell',
|
|
157
|
+
message: announcement,
|
|
158
|
+
color: characterData.notificationColor
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
sendToRoll20(messageData);
|
|
162
|
+
|
|
163
|
+
roll(`${name} - Damage`, damage);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
container.appendChild(companionCard);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Export function to globalThis
|
|
172
|
+
Object.assign(globalThis, {
|
|
173
|
+
buildCompanionsDisplay
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
console.log('✅ Companions Manager module loaded');
|
|
177
|
+
|
|
178
|
+
})();
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concentration Tracker Module
|
|
3
|
+
*
|
|
4
|
+
* Manages spell concentration mechanics:
|
|
5
|
+
* - Tracks which spell the character is concentrating on
|
|
6
|
+
* - Displays concentration indicator UI
|
|
7
|
+
* - Handles dropping concentration
|
|
8
|
+
* - Auto-hides for non-spellcasters
|
|
9
|
+
*
|
|
10
|
+
* Concentration is a core D&D 5e mechanic where certain spells require
|
|
11
|
+
* ongoing focus to maintain their effects. Only one concentration spell
|
|
12
|
+
* can be active at a time.
|
|
13
|
+
*
|
|
14
|
+
* Loaded as a plain script (no ES6 modules) to export to globalThis.
|
|
15
|
+
*
|
|
16
|
+
* Functions exported to globalThis:
|
|
17
|
+
* - updateConcentrationDisplay()
|
|
18
|
+
* - initConcentrationTracker()
|
|
19
|
+
* - setConcentration(spellName)
|
|
20
|
+
* - dropConcentration()
|
|
21
|
+
*
|
|
22
|
+
* State exported to globalThis:
|
|
23
|
+
* - concentratingSpell (via getter/setter)
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
(function() {
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
// Track concentration state
|
|
30
|
+
let concentratingSpell = null;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Update the concentration indicator UI
|
|
34
|
+
* Shows/hides the concentration banner based on current state
|
|
35
|
+
* Auto-hides for characters without spell slots (e.g., rogues, fighters)
|
|
36
|
+
*/
|
|
37
|
+
function updateConcentrationDisplay() {
|
|
38
|
+
const concentrationIndicator = document.getElementById('concentration-indicator');
|
|
39
|
+
const concentrationSpell = document.getElementById('concentration-spell');
|
|
40
|
+
|
|
41
|
+
if (!concentrationIndicator || !concentrationSpell) return;
|
|
42
|
+
|
|
43
|
+
// Hide concentration row if character has no spell slots (e.g., rogues)
|
|
44
|
+
if (characterData && characterData.spellSlots) {
|
|
45
|
+
const spellSlots = characterData.spellSlots;
|
|
46
|
+
let hasSpellSlots = false;
|
|
47
|
+
|
|
48
|
+
// Check for regular spell slots (levels 1-9)
|
|
49
|
+
for (let level = 1; level <= 9; level++) {
|
|
50
|
+
if ((spellSlots[`level${level}SpellSlotsMax`] || 0) > 0) {
|
|
51
|
+
hasSpellSlots = true;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Also check for pact magic (warlocks)
|
|
57
|
+
if ((spellSlots.pactMagicSlotsMax || 0) > 0) {
|
|
58
|
+
hasSpellSlots = true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// If no spell slots, hide the concentration tracker entirely
|
|
62
|
+
if (!hasSpellSlots) {
|
|
63
|
+
concentrationIndicator.style.display = 'none';
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Show/hide based on concentration state
|
|
69
|
+
if (concentratingSpell) {
|
|
70
|
+
concentrationIndicator.style.display = 'flex';
|
|
71
|
+
if (concentrationSpell) {
|
|
72
|
+
concentrationSpell.textContent = concentratingSpell;
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
concentrationIndicator.style.display = 'none';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Initialize concentration tracker button
|
|
81
|
+
* Sets up click handler for the "Drop Concentration" button
|
|
82
|
+
*/
|
|
83
|
+
function initConcentrationTracker() {
|
|
84
|
+
const dropConcentrationBtn = document.getElementById('drop-concentration-btn');
|
|
85
|
+
|
|
86
|
+
if (dropConcentrationBtn) {
|
|
87
|
+
dropConcentrationBtn.addEventListener('click', () => {
|
|
88
|
+
dropConcentration();
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
debug.log('✅ Concentration tracker initialized');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Set concentration on a spell
|
|
97
|
+
* Automatically drops concentration on any previous spell
|
|
98
|
+
* @param {string} spellName - Name of the spell to concentrate on
|
|
99
|
+
*/
|
|
100
|
+
function setConcentration(spellName) {
|
|
101
|
+
debug.log(`🧠 setConcentration called with: "${spellName}"`);
|
|
102
|
+
debug.log(`🧠 Current concentration: "${concentratingSpell}"`);
|
|
103
|
+
|
|
104
|
+
// Track if this is a replacement
|
|
105
|
+
const isReplacement = concentratingSpell && concentratingSpell !== spellName;
|
|
106
|
+
|
|
107
|
+
// If already concentrating on a different spell, notify that it's being dropped
|
|
108
|
+
if (isReplacement) {
|
|
109
|
+
const previousSpell = concentratingSpell;
|
|
110
|
+
showNotification(`⚠️ Dropped concentration on ${previousSpell} to concentrate on ${spellName}`);
|
|
111
|
+
debug.log(`🔄 Replacing concentration: ${previousSpell} → ${spellName}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
concentratingSpell = spellName;
|
|
115
|
+
debug.log(`🧠 Updated concentratingSpell variable to: "${concentratingSpell}"`);
|
|
116
|
+
|
|
117
|
+
if (characterData) {
|
|
118
|
+
characterData.concentrationSpell = spellName;
|
|
119
|
+
debug.log(`🧠 Updated characterData.concentrationSpell to: "${spellName}"`);
|
|
120
|
+
saveCharacterData();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
updateConcentrationDisplay();
|
|
124
|
+
debug.log(`🧠 Called updateConcentrationDisplay()`);
|
|
125
|
+
|
|
126
|
+
// Update status bar if available
|
|
127
|
+
if (typeof sendStatusUpdate === 'function') {
|
|
128
|
+
sendStatusUpdate();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Only show "Concentrating on" notification if this is a new concentration (not replacement)
|
|
132
|
+
if (!isReplacement) {
|
|
133
|
+
showNotification(`🧠 Concentrating on: ${spellName}`);
|
|
134
|
+
}
|
|
135
|
+
debug.log(`🧠 Concentration set complete: ${spellName}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Drop concentration on the current spell
|
|
140
|
+
*/
|
|
141
|
+
function dropConcentration() {
|
|
142
|
+
if (!concentratingSpell) return;
|
|
143
|
+
|
|
144
|
+
const spellName = concentratingSpell;
|
|
145
|
+
concentratingSpell = null;
|
|
146
|
+
if (characterData) {
|
|
147
|
+
characterData.concentrationSpell = null;
|
|
148
|
+
saveCharacterData();
|
|
149
|
+
}
|
|
150
|
+
updateConcentrationDisplay();
|
|
151
|
+
|
|
152
|
+
// Update status bar if available
|
|
153
|
+
if (typeof sendStatusUpdate === 'function') {
|
|
154
|
+
sendStatusUpdate();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
showNotification(`✅ Dropped concentration on ${spellName}`);
|
|
158
|
+
debug.log(`🗑️ Concentration dropped: ${spellName}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ===== EXPORTS =====
|
|
162
|
+
|
|
163
|
+
// Export concentratingSpell state variable to window (for access from content script context)
|
|
164
|
+
Object.defineProperty(window, 'concentratingSpell', {
|
|
165
|
+
get: () => concentratingSpell,
|
|
166
|
+
set: (value) => { concentratingSpell = value; },
|
|
167
|
+
configurable: true
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Export functions to window
|
|
171
|
+
window.updateConcentrationDisplay = updateConcentrationDisplay;
|
|
172
|
+
window.initConcentrationTracker = initConcentrationTracker;
|
|
173
|
+
window.setConcentration = setConcentration;
|
|
174
|
+
window.dropConcentration = dropConcentration;
|
|
175
|
+
|
|
176
|
+
debug.log('✅ Concentration Tracker module loaded');
|
|
177
|
+
|
|
178
|
+
})();
|