fix: improve mana tracker state matching
This commit is contained in:
parent
84da2a8752
commit
b204ba8e75
3 changed files with 1212 additions and 603 deletions
135
static/script.js
135
static/script.js
|
|
@ -1494,9 +1494,25 @@ function getManaTrackedItems(state) {
|
||||||
const snapshotMs = Date.now();
|
const snapshotMs = Date.now();
|
||||||
return state.items
|
return state.items
|
||||||
.filter(item => (item.current_wielded_location || 0) > 0)
|
.filter(item => (item.current_wielded_location || 0) > 0)
|
||||||
.filter(item => item.is_mana_tracked || item.current_mana !== undefined || item.max_mana !== undefined || item.spellcraft !== undefined)
|
.filter(item => {
|
||||||
|
const spellInfo = item.spells;
|
||||||
|
const hasSpellData =
|
||||||
|
(Array.isArray(spellInfo?.spells) && spellInfo.spells.length > 0) ||
|
||||||
|
(Array.isArray(spellInfo?.active_spells) && spellInfo.active_spells.length > 0) ||
|
||||||
|
Number(spellInfo?.spell_count || 0) > 0 ||
|
||||||
|
Number(spellInfo?.active_spell_count || 0) > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
item.is_mana_tracked ||
|
||||||
|
item.current_mana !== undefined ||
|
||||||
|
item.max_mana !== undefined ||
|
||||||
|
item.spellcraft !== undefined ||
|
||||||
|
hasSpellData
|
||||||
|
);
|
||||||
|
})
|
||||||
.map(item => {
|
.map(item => {
|
||||||
const result = { ...item };
|
const result = { ...item };
|
||||||
|
result.mana_state = deriveManaStateFromCharacterStats(result, state.characterName);
|
||||||
if (result.mana_time_remaining_seconds !== undefined && result.mana_time_remaining_seconds !== null) {
|
if (result.mana_time_remaining_seconds !== undefined && result.mana_time_remaining_seconds !== null) {
|
||||||
const snapshotUtc = result.mana_snapshot_utc ? Date.parse(result.mana_snapshot_utc) : NaN;
|
const snapshotUtc = result.mana_snapshot_utc ? Date.parse(result.mana_snapshot_utc) : NaN;
|
||||||
if (!Number.isNaN(snapshotUtc)) {
|
if (!Number.isNaN(snapshotUtc)) {
|
||||||
|
|
@ -1521,6 +1537,87 @@ function getManaTrackedItems(state) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isActionableManaSpell(spell) {
|
||||||
|
if (!spell) return false;
|
||||||
|
const spellName = (spell.name || '').toLowerCase();
|
||||||
|
if (!spellName || spellName.startsWith('unknown_spell_')) return false;
|
||||||
|
if (spellName.startsWith('cantrip portal send') || spellName.startsWith('cantrip portal recall')) return false;
|
||||||
|
if (spellName.startsWith('incantation of ') || spellName.startsWith('aura of incantation ')) return true;
|
||||||
|
if (
|
||||||
|
spellName.startsWith('feeble ') ||
|
||||||
|
spellName.startsWith('minor ') ||
|
||||||
|
spellName.startsWith('lesser ') ||
|
||||||
|
spellName.startsWith('moderate ') ||
|
||||||
|
spellName.startsWith('inner ') ||
|
||||||
|
spellName.startsWith('major ') ||
|
||||||
|
spellName.startsWith('epic ') ||
|
||||||
|
spellName.startsWith('legendary ') ||
|
||||||
|
spellName.startsWith('prodigal ')
|
||||||
|
) return true;
|
||||||
|
|
||||||
|
const duration = spell.duration;
|
||||||
|
return duration !== undefined && duration !== null && Number(duration) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function doesSpellMatch(activeSpell, spell) {
|
||||||
|
if (!activeSpell || !spell) return false;
|
||||||
|
if (activeSpell.id === spell.id) return true;
|
||||||
|
if (activeSpell.family == null || spell.family == null) return false;
|
||||||
|
if (Number(activeSpell.family) !== Number(spell.family)) return false;
|
||||||
|
if (activeSpell.difficulty == null || spell.difficulty == null) return true;
|
||||||
|
return Number(activeSpell.difficulty) >= Number(spell.difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isHandEquippedItem(item) {
|
||||||
|
const mask = Number(item?.current_wielded_location || 0);
|
||||||
|
return (
|
||||||
|
mask === 1048576 ||
|
||||||
|
mask === 2097152 ||
|
||||||
|
mask === 4194304 ||
|
||||||
|
mask === 16777216 ||
|
||||||
|
mask === 33554432
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deriveManaStateFromCharacterStats(item, characterName) {
|
||||||
|
const stats = characterStats[characterName];
|
||||||
|
const activeEnchantments = stats?.active_item_enchantments;
|
||||||
|
const itemActiveSpells = Array.isArray(item.spells?.active_spells) ? item.spells.active_spells : [];
|
||||||
|
|
||||||
|
if ((!Array.isArray(activeEnchantments) || activeEnchantments.length === 0) && itemActiveSpells.length === 0) {
|
||||||
|
return item.mana_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.current_mana === undefined || item.current_mana === null) return item.mana_state;
|
||||||
|
if (item.current_mana <= 0) return 'not_active';
|
||||||
|
|
||||||
|
const translatedSpells = Array.isArray(item.spells?.spells) ? item.spells.spells : [];
|
||||||
|
const actionableSpells = translatedSpells.filter(isActionableManaSpell);
|
||||||
|
if (actionableSpells.length === 0) return item.mana_state;
|
||||||
|
|
||||||
|
const allMatchedOnItem = actionableSpells.every(spell => itemActiveSpells.some(activeSpell => doesSpellMatch(activeSpell, spell)));
|
||||||
|
if (allMatchedOnItem) {
|
||||||
|
return 'active';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHandEquippedItem(item)) {
|
||||||
|
return 'not_active';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(activeEnchantments) || activeEnchantments.length === 0) {
|
||||||
|
return allMatchedOnItem ? 'active' : item.mana_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allMatched = actionableSpells.every(spell => {
|
||||||
|
if (!spell) return false;
|
||||||
|
return activeEnchantments.some(activeSpell => doesSpellMatch(activeSpell, spell));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (allMatched) return 'active';
|
||||||
|
if (item.mana_state === 'active') return 'not_active';
|
||||||
|
return item.mana_state || 'not_active';
|
||||||
|
}
|
||||||
|
|
||||||
function formatManaRemaining(totalSeconds) {
|
function formatManaRemaining(totalSeconds) {
|
||||||
if (totalSeconds === null || totalSeconds === undefined) return '--';
|
if (totalSeconds === null || totalSeconds === undefined) return '--';
|
||||||
const safeSeconds = Math.max(0, Math.floor(totalSeconds));
|
const safeSeconds = Math.max(0, Math.floor(totalSeconds));
|
||||||
|
|
@ -1533,7 +1630,6 @@ function renderInventoryManaPanel(state) {
|
||||||
if (!state || !state.manaListBody || !state.manaSummary) return;
|
if (!state || !state.manaListBody || !state.manaSummary) return;
|
||||||
|
|
||||||
const items = getManaTrackedItems(state);
|
const items = getManaTrackedItems(state);
|
||||||
adjustInventoryLayoutForMana(state, items.length);
|
|
||||||
state.manaListBody.innerHTML = '';
|
state.manaListBody.innerHTML = '';
|
||||||
|
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
|
|
@ -1589,23 +1685,7 @@ function renderInventoryManaPanel(state) {
|
||||||
row.appendChild(timeEl);
|
row.appendChild(timeEl);
|
||||||
state.manaListBody.appendChild(row);
|
state.manaListBody.appendChild(row);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
function adjustInventoryLayoutForMana(state, itemCount) {
|
|
||||||
if (!state || !state.windowEl || !state.topSection || !state.manaPanel) return;
|
|
||||||
|
|
||||||
const baseWindowHeight = 520;
|
|
||||||
const baseTopHeight = 264;
|
|
||||||
const basePanelHeight = 260;
|
|
||||||
const visibleRowsAtBase = 9;
|
|
||||||
const rowHeight = 22;
|
|
||||||
|
|
||||||
const extraRows = Math.max(0, itemCount - visibleRowsAtBase);
|
|
||||||
const extraHeight = extraRows * rowHeight;
|
|
||||||
|
|
||||||
state.topSection.style.height = `${baseTopHeight + extraHeight}px`;
|
|
||||||
state.manaPanel.style.height = `${basePanelHeight + extraHeight}px`;
|
|
||||||
state.windowEl.style.height = `${baseWindowHeight + extraHeight}px`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showInventoryWindow(name) {
|
function showInventoryWindow(name) {
|
||||||
|
|
@ -1630,7 +1710,7 @@ function showInventoryWindow(name) {
|
||||||
content.appendChild(loading);
|
content.appendChild(loading);
|
||||||
|
|
||||||
win.style.width = '572px';
|
win.style.width = '572px';
|
||||||
win.style.height = '520px';
|
win.style.height = '720px';
|
||||||
|
|
||||||
const invContent = document.createElement('div');
|
const invContent = document.createElement('div');
|
||||||
invContent.className = 'inventory-content';
|
invContent.className = 'inventory-content';
|
||||||
|
|
@ -1784,6 +1864,20 @@ function showInventoryWindow(name) {
|
||||||
loading.textContent = `Failed to load inventory: ${err.message}`;
|
loading.textContent = `Failed to load inventory: ${err.message}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!characterStats[name]) {
|
||||||
|
fetch(`${API_BASE}/character-stats/${encodeURIComponent(name)}`)
|
||||||
|
.then(r => r.ok ? r.json() : null)
|
||||||
|
.then(data => {
|
||||||
|
if (data && !data.error) {
|
||||||
|
characterStats[name] = data;
|
||||||
|
if (win._inventoryState) {
|
||||||
|
renderInventoryState(win._inventoryState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
debugLog('Inventory window created for:', name);
|
debugLog('Inventory window created for:', name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3000,6 +3094,9 @@ function initWebSocket() {
|
||||||
} else if (msg.type === 'character_stats') {
|
} else if (msg.type === 'character_stats') {
|
||||||
characterStats[msg.character_name] = msg;
|
characterStats[msg.character_name] = msg;
|
||||||
updateCharacterWindow(msg.character_name, msg);
|
updateCharacterWindow(msg.character_name, msg);
|
||||||
|
if (inventoryWindows[msg.character_name] && inventoryWindows[msg.character_name]._inventoryState) {
|
||||||
|
renderInventoryState(inventoryWindows[msg.character_name]._inventoryState);
|
||||||
|
}
|
||||||
} else if (msg.type === 'inventory_delta') {
|
} else if (msg.type === 'inventory_delta') {
|
||||||
updateInventoryLive(msg);
|
updateInventoryLive(msg);
|
||||||
} else if (msg.type === 'server_status') {
|
} else if (msg.type === 'server_status') {
|
||||||
|
|
|
||||||
|
|
@ -736,7 +736,7 @@ body.noselect, body.noselect * {
|
||||||
top: 100px;
|
top: 100px;
|
||||||
left: 400px;
|
left: 400px;
|
||||||
width: 572px;
|
width: 572px;
|
||||||
height: 520px;
|
height: 720px;
|
||||||
background: rgba(20, 20, 20, 0.92);
|
background: rgba(20, 20, 20, 0.92);
|
||||||
backdrop-filter: blur(2px);
|
backdrop-filter: blur(2px);
|
||||||
border: 2px solid var(--ac-gold);
|
border: 2px solid var(--ac-gold);
|
||||||
|
|
@ -761,7 +761,7 @@ body.noselect, body.noselect * {
|
||||||
.inv-top-section {
|
.inv-top-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
min-height: 264px;
|
min-height: 464px;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -959,7 +959,8 @@ body.noselect, body.noselect * {
|
||||||
border: 1px solid var(--ac-border-light);
|
border: 1px solid var(--ac-border-light);
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
min-height: 260px;
|
min-height: 460px;
|
||||||
|
height: 460px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
@ -2433,7 +2434,7 @@ table.ts-allegiance td:first-child {
|
||||||
background: #0e0c08 !important;
|
background: #0e0c08 !important;
|
||||||
resize: none !important;
|
resize: none !important;
|
||||||
width: 572px !important;
|
width: 572px !important;
|
||||||
min-height: 520px !important;
|
min-height: 720px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inv-top-section {
|
.inv-top-section {
|
||||||
|
|
@ -2449,7 +2450,8 @@ table.ts-allegiance td:first-child {
|
||||||
.inv-mana-panel {
|
.inv-mana-panel {
|
||||||
width: 162px !important;
|
width: 162px !important;
|
||||||
min-width: 162px !important;
|
min-width: 162px !important;
|
||||||
min-height: 260px !important;
|
min-height: 460px !important;
|
||||||
|
height: 460px !important;
|
||||||
background: #111014 !important;
|
background: #111014 !important;
|
||||||
border: 1px solid #5a4a24 !important;
|
border: 1px solid #5a4a24 !important;
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue