added inventory, updated DB

This commit is contained in:
erik 2025-06-10 19:21:21 +00:00
parent f218350959
commit 10c51f6825
16528 changed files with 147743 additions and 79 deletions

View file

@ -460,11 +460,182 @@ function showInventoryWindow(name) {
header.appendChild(title);
header.appendChild(closeBtn);
win.appendChild(header);
// Loading message
const loading = document.createElement('div');
loading.className = 'inventory-loading';
loading.textContent = 'Loading inventory...';
win.appendChild(loading);
// Content container
const content = document.createElement('div');
content.className = 'inventory-content';
content.innerHTML = '<div class="inventory-placeholder">Inventory feature coming soon...</div>';
content.style.display = 'none';
win.appendChild(content);
// Fetch inventory data from main app (which will proxy to inventory service)
fetch(`${API_BASE}/inventory/${encodeURIComponent(name)}?limit=1000`)
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
.then(data => {
loading.style.display = 'none';
content.style.display = 'block';
// Create inventory grid
const grid = document.createElement('div');
grid.className = 'inventory-grid';
// Render each item
data.items.forEach(item => {
const slot = document.createElement('div');
slot.className = 'inventory-slot';
// Create layered icon container
const iconContainer = document.createElement('div');
iconContainer.className = 'item-icon-composite';
// Get base icon ID with portal.dat offset
const baseIconId = (item.icon + 0x06000000).toString(16).toUpperCase().padStart(8, '0');
// Check for overlay and underlay from enhanced format or legacy format
let overlayIconId = null;
let underlayIconId = null;
// Enhanced format (inventory service) - check for proper icon overlay/underlay properties
if (item.icon_overlay_id && item.icon_overlay_id > 0) {
overlayIconId = (item.icon_overlay_id + 0x06000000).toString(16).toUpperCase().padStart(8, '0');
}
if (item.icon_underlay_id && item.icon_underlay_id > 0) {
underlayIconId = (item.icon_underlay_id + 0x06000000).toString(16).toUpperCase().padStart(8, '0');
}
// Fallback: Enhanced format (inventory service) - check spells object for decal info
if (!overlayIconId && !underlayIconId && item.spells && typeof item.spells === 'object') {
// Icon overlay (using the actual property names from the data)
// Only use valid icon IDs (must be > 100 to avoid invalid small IDs)
if (item.spells.spell_decal_218103838 && item.spells.spell_decal_218103838 > 100) {
overlayIconId = (item.spells.spell_decal_218103838 + 0x06000000).toString(16).toUpperCase().padStart(8, '0');
}
// Icon underlay
if (item.spells.spell_decal_218103848 && item.spells.spell_decal_218103848 > 100) {
underlayIconId = (item.spells.spell_decal_218103848 + 0x06000000).toString(16).toUpperCase().padStart(8, '0');
}
} else if (item.item_data) {
// Legacy format - parse item_data
try {
const itemData = typeof item.item_data === 'string' ? JSON.parse(item.item_data) : item.item_data;
if (itemData.IntValues) {
// Icon overlay (ID 218103849) - only use valid icon IDs
if (itemData.IntValues['218103849'] && itemData.IntValues['218103849'] > 100) {
overlayIconId = (itemData.IntValues['218103849'] + 0x06000000).toString(16).toUpperCase().padStart(8, '0');
}
// Icon underlay (ID 218103850) - only use valid icon IDs
if (itemData.IntValues['218103850'] && itemData.IntValues['218103850'] > 100) {
underlayIconId = (itemData.IntValues['218103850'] + 0x06000000).toString(16).toUpperCase().padStart(8, '0');
}
}
} catch (e) {
console.warn('Failed to parse item data for', item.name);
}
}
// Create underlay (bottom layer)
if (underlayIconId) {
const underlayImg = document.createElement('img');
underlayImg.className = 'icon-underlay';
underlayImg.src = `/icons/${underlayIconId}.png`;
underlayImg.alt = 'underlay';
underlayImg.onerror = function() { this.style.display = 'none'; };
iconContainer.appendChild(underlayImg);
}
// Create base icon (middle layer)
const baseImg = document.createElement('img');
baseImg.className = 'icon-base';
baseImg.src = `/icons/${baseIconId}.png`;
baseImg.alt = item.name || 'Unknown Item';
baseImg.onerror = function() {
// Final fallback
this.src = '/icons/06000133.png';
};
iconContainer.appendChild(baseImg);
// Create overlay (top layer)
if (overlayIconId) {
const overlayImg = document.createElement('img');
overlayImg.className = 'icon-overlay';
overlayImg.src = `/icons/${overlayIconId}.png`;
overlayImg.alt = 'overlay';
overlayImg.onerror = function() { this.style.display = 'none'; };
iconContainer.appendChild(overlayImg);
}
// Create tooltip data
slot.dataset.name = item.name || 'Unknown Item';
slot.dataset.value = item.value || 0;
slot.dataset.burden = item.burden || 0;
// Store enhanced data for tooltips
// All data now comes from inventory service (no more local fallback)
if (item.max_damage !== undefined || item.object_class_name !== undefined || item.spells !== undefined) {
// Inventory service provides clean, structured data with translations
// Only include properties that actually exist on the item
const enhancedData = {};
// Check all possible enhanced properties from inventory service
const possibleProps = [
'max_damage', 'armor_level', 'damage_bonus', 'attack_bonus',
'wield_level', 'skill_level', 'lore_requirement', 'equip_skill', 'equip_skill_name',
'material', 'material_name', 'material_id', 'imbue', 'item_set', 'tinks',
'workmanship', 'workmanship_text', 'damage_rating', 'crit_rating',
'heal_boost_rating', 'has_id_data', 'object_class_name', 'spells',
'enhanced_properties', 'damage_range', 'damage_type', 'min_damage',
'speed_text', 'speed_value', 'mana_display', 'spellcraft', 'current_mana', 'max_mana',
'melee_defense_bonus', 'magic_defense_bonus', 'missile_defense_bonus',
'elemental_damage_vs_monsters', 'mana_conversion_bonus', 'icon_overlay_id', 'icon_underlay_id'
];
// Only add properties that exist and have meaningful values
possibleProps.forEach(prop => {
if (item.hasOwnProperty(prop) && item[prop] !== undefined && item[prop] !== null) {
enhancedData[prop] = item[prop];
}
});
slot.dataset.enhancedData = JSON.stringify(enhancedData);
} else {
// No enhanced data available
slot.dataset.enhancedData = JSON.stringify({});
}
// Add tooltip on hover
slot.addEventListener('mouseenter', e => showInventoryTooltip(e, slot));
slot.addEventListener('mousemove', e => showInventoryTooltip(e, slot));
slot.addEventListener('mouseleave', hideInventoryTooltip);
slot.appendChild(iconContainer);
grid.appendChild(slot);
});
content.appendChild(grid);
// Add item count
const count = document.createElement('div');
count.className = 'inventory-count';
count.textContent = `${data.item_count} items`;
content.appendChild(count);
})
.catch(err => {
loading.textContent = `Failed to load inventory: ${err.message}`;
console.error('Inventory fetch failed:', err);
});
document.body.appendChild(win);
inventoryWindows[name] = win;
@ -472,6 +643,238 @@ function showInventoryWindow(name) {
makeDraggable(win, header);
}
// Inventory tooltip functions
let inventoryTooltip = null;
function showInventoryTooltip(e, slot) {
if (!inventoryTooltip) {
inventoryTooltip = document.createElement('div');
inventoryTooltip.className = 'inventory-tooltip';
document.body.appendChild(inventoryTooltip);
}
const name = slot.dataset.name;
const value = parseInt(slot.dataset.value) || 0;
const burden = parseInt(slot.dataset.burden) || 0;
// Build enhanced tooltip
let tooltipHTML = `<div class="tooltip-name">${name}</div>`;
// Basic stats
tooltipHTML += `<div class="tooltip-section">`;
tooltipHTML += `<div class="tooltip-value">Value: ${value.toLocaleString()}</div>`;
tooltipHTML += `<div class="tooltip-burden">Burden: ${burden}</div>`;
// Add workmanship right after basic stats for weapons/items
if (slot.dataset.enhancedData) {
try {
const enhanced = JSON.parse(slot.dataset.enhancedData);
if (enhanced.workmanship_text) {
tooltipHTML += `<div class="tooltip-workmanship">Workmanship: ${enhanced.workmanship_text}</div>`;
}
} catch (e) {
// Ignore parsing errors for this section
}
}
tooltipHTML += `</div>`;
// Enhanced data from inventory service
if (slot.dataset.enhancedData) {
try {
const enhanced = JSON.parse(slot.dataset.enhancedData);
// Only show enhanced sections if we have enhanced data
if (Object.keys(enhanced).length > 0) {
// Helper function to check for valid values
const isValid = (val) => val !== undefined && val !== null && val !== -1 && val !== -1.0;
// Weapon-specific stats section (for weapons)
if (enhanced.damage_range || enhanced.speed_text || enhanced.equip_skill_name) {
tooltipHTML += `<div class="tooltip-section">`;
// Skill requirement
if (enhanced.equip_skill_name) {
tooltipHTML += `<div class="tooltip-property">Skill: ${enhanced.equip_skill_name}</div>`;
}
// Damage with type
if (enhanced.damage_range && enhanced.damage_type) {
const damageText = enhanced.damage_type !== 'Physical' ?
`${enhanced.damage_range}, ${enhanced.damage_type}` :
enhanced.damage_range;
tooltipHTML += `<div class="tooltip-property">Damage: ${damageText}</div>`;
}
// Speed
if (enhanced.speed_text) {
tooltipHTML += `<div class="tooltip-property">Speed: ${enhanced.speed_text}</div>`;
}
// Attack and defense bonuses (as percentages)
if (isValid(enhanced.attack_bonus)) {
const attackPercent = ((enhanced.attack_bonus - 1) * 100).toFixed(1);
if (attackPercent !== "0.0") {
tooltipHTML += `<div class="tooltip-property">Bonus to Attack Skill: ${attackPercent > 0 ? '+' : ''}${attackPercent}%</div>`;
}
}
// Defense bonuses
if (enhanced.melee_defense_bonus && isValid(enhanced.melee_defense_bonus)) {
const defensePercent = ((enhanced.melee_defense_bonus - 1) * 100).toFixed(1);
if (defensePercent !== "0.0") {
tooltipHTML += `<div class="tooltip-property">Bonus to Melee Defense: ${defensePercent > 0 ? '+' : ''}${defensePercent}%</div>`;
}
}
// Magic defense bonus
if (enhanced.magic_defense_bonus && isValid(enhanced.magic_defense_bonus)) {
const magicDefensePercent = ((enhanced.magic_defense_bonus - 1) * 100).toFixed(1);
if (magicDefensePercent !== "0.0") {
tooltipHTML += `<div class="tooltip-property">Bonus to Magic Defense: ${magicDefensePercent > 0 ? '+' : ''}${magicDefensePercent}%</div>`;
}
}
// Elemental damage vs monsters
if (enhanced.elemental_damage_vs_monsters && isValid(enhanced.elemental_damage_vs_monsters)) {
const elementalPercent = ((enhanced.elemental_damage_vs_monsters - 1) * 100).toFixed(1);
if (elementalPercent !== "0.0") {
tooltipHTML += `<div class="tooltip-property">Elemental Damage vs Monsters: ${elementalPercent > 0 ? '+' : ''}${elementalPercent}%</div>`;
}
}
tooltipHTML += `</div>`;
}
// Traditional combat stats section (for non-weapons or additional stats)
const combatProps = [];
if (isValid(enhanced.armor_level)) combatProps.push(`Armor Level: ${enhanced.armor_level}`);
if (!enhanced.damage_range && isValid(enhanced.max_damage)) combatProps.push(`Max Damage: ${enhanced.max_damage}`);
if (!enhanced.attack_bonus && isValid(enhanced.damage_bonus)) combatProps.push(`Damage Bonus: ${enhanced.damage_bonus.toFixed(1)}`);
if (combatProps.length > 0) {
tooltipHTML += `<div class="tooltip-section"><div class="tooltip-section-title">Combat Stats</div>`;
combatProps.forEach(prop => {
tooltipHTML += `<div class="tooltip-property">${prop}</div>`;
});
tooltipHTML += `</div>`;
}
// Requirements section
const reqProps = [];
if (isValid(enhanced.wield_level)) reqProps.push(`Level Required: ${enhanced.wield_level}`);
if (isValid(enhanced.skill_level)) reqProps.push(`Skill Level: ${enhanced.skill_level}`);
if (enhanced.equip_skill_name) reqProps.push(`Skill: ${enhanced.equip_skill_name}`);
if (isValid(enhanced.lore_requirement)) reqProps.push(`Lore: ${enhanced.lore_requirement}`);
if (reqProps.length > 0) {
tooltipHTML += `<div class="tooltip-section"><div class="tooltip-section-title">Requirements</div>`;
reqProps.forEach(prop => {
tooltipHTML += `<div class="tooltip-property">${prop}</div>`;
});
tooltipHTML += `</div>`;
}
// Enhancement section
const enhanceProps = [];
if (enhanced.material_name) enhanceProps.push(`Material: ${enhanced.material_name}`);
if (enhanced.imbue) enhanceProps.push(`Imbue: ${enhanced.imbue}`);
if (enhanced.item_set) enhanceProps.push(`Set: ${enhanced.item_set}`);
if (isValid(enhanced.tinks)) enhanceProps.push(`Tinks: ${enhanced.tinks}`);
// Use workmanship_text if available, otherwise numeric value
if (enhanced.workmanship_text) {
enhanceProps.push(`Workmanship: ${enhanced.workmanship_text}`);
} else if (isValid(enhanced.workmanship)) {
enhanceProps.push(`Workmanship: ${enhanced.workmanship.toFixed(1)}`);
}
if (enhanceProps.length > 0) {
tooltipHTML += `<div class="tooltip-section"><div class="tooltip-section-title">Enhancements</div>`;
enhanceProps.forEach(prop => {
tooltipHTML += `<div class="tooltip-property">${prop}</div>`;
});
tooltipHTML += `</div>`;
}
// Ratings section
const ratingProps = [];
if (isValid(enhanced.damage_rating)) ratingProps.push(`Damage Rating: ${enhanced.damage_rating}`);
if (isValid(enhanced.crit_rating)) ratingProps.push(`Crit Rating: ${enhanced.crit_rating}`);
if (isValid(enhanced.heal_boost_rating)) ratingProps.push(`Heal Boost: ${enhanced.heal_boost_rating}`);
if (ratingProps.length > 0) {
tooltipHTML += `<div class="tooltip-section"><div class="tooltip-section-title">Ratings</div>`;
ratingProps.forEach(prop => {
tooltipHTML += `<div class="tooltip-property">${prop}</div>`;
});
tooltipHTML += `</div>`;
}
// Spells section (condensed list)
if (enhanced.spells && enhanced.spells.spells && enhanced.spells.spells.length > 0) {
const spellNames = enhanced.spells.spells.map(spell => spell.name).join(', ');
tooltipHTML += `<div class="tooltip-section">`;
tooltipHTML += `<div class="tooltip-property">Spells: ${spellNames}</div>`;
tooltipHTML += `</div>`;
}
// Mana and Spellcraft section
if (enhanced.mana_display || enhanced.spellcraft) {
tooltipHTML += `<div class="tooltip-section">`;
if (enhanced.spellcraft) {
tooltipHTML += `<div class="tooltip-property">Spellcraft: ${enhanced.spellcraft}</div>`;
}
if (enhanced.mana_display) {
tooltipHTML += `<div class="tooltip-property">Mana: ${enhanced.mana_display}</div>`;
}
tooltipHTML += `</div>`;
}
// Detailed Spell Descriptions section
if (enhanced.spells && enhanced.spells.spells && enhanced.spells.spells.length > 0) {
tooltipHTML += `<div class="tooltip-section"><div class="tooltip-section-title">Spell Descriptions</div>`;
enhanced.spells.spells.forEach(spell => {
tooltipHTML += `<div class="tooltip-spell">`;
tooltipHTML += `<div class="spell-name">${spell.name}</div>`;
if (spell.description) {
tooltipHTML += `<div class="spell-description">${spell.description}</div>`;
}
tooltipHTML += `</div>`;
});
tooltipHTML += `</div>`;
}
// Object class info
if (enhanced.object_class_name) {
tooltipHTML += `<div class="tooltip-section">`;
tooltipHTML += `<div class="tooltip-info">Type: ${enhanced.object_class_name}</div>`;
tooltipHTML += `</div>`;
}
} // End of enhanced data check
} catch (e) {
console.warn('Failed to parse enhanced tooltip data', e);
}
}
inventoryTooltip.innerHTML = tooltipHTML;
// Position tooltip near cursor
const x = e.clientX + 10;
const y = e.clientY + 10;
inventoryTooltip.style.left = `${x}px`;
inventoryTooltip.style.top = `${y}px`;
inventoryTooltip.style.display = 'block';
}
function hideInventoryTooltip() {
if (inventoryTooltip) {
inventoryTooltip.style.display = 'none';
}
}
const applyTransform = () =>
group.style.transform = `translate(${offX}px,${offY}px) scale(${scale})`;