// Resource management for Cursebreaker map let resourceLayerGroups = {}; // Map: resource name -> L.layerGroup let resourceIcons = {}; // Map: resource name -> L.icon let resourceData = {}; // Map: resource name -> resource metadata (skill, level, etc.) let filterState = {}; // Map: resource name -> boolean (visible) // Load resources from API async function loadResources() { try { console.log('Loading resources from API...'); const response = await fetch('/api/resources'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log(`Received ${data.resources.length} resource types`); // Create icons and layer groups for each resource for (const group of data.resources) { createResourceGroup(group); } // Initialize filter UI initializeFilterUI(); // Restore filter state from localStorage restoreFilterState(); console.log(`Loaded ${data.resources.length} resource types successfully`); } catch (error) { console.error('Error loading resources:', error); const container = document.getElementById('resource-filters'); if (container) { container.innerHTML = '

Failed to load resources. Check console for details.

'; } } } // Create resource group with icon and markers function createResourceGroup(group) { const config = window.MapConfig; // Create icon definition (cached per resource type) const iconUrl = `data:image/webp;base64,${group.icon_base64}`; const icon = L.icon({ iconUrl: iconUrl, iconSize: [config.resourceIconSize, config.resourceIconSize], iconAnchor: [config.resourceIconSize / 2, config.resourceIconSize / 2], popupAnchor: [0, -(config.resourceIconSize / 2)], }); resourceIcons[group.name] = icon; // Store metadata resourceData[group.name] = { item_id: group.item_id, skill: group.skill, level: group.level, }; // Create layer group for this resource type const layerGroup = L.layerGroup(); // Add markers for all positions for (const pos of group.positions) { const marker = L.marker([pos.y, pos.x], { icon: icon, title: group.name, }); // Add popup with resource details marker.bindPopup( `${group.name}
Position: (${pos.x.toFixed(1)}, ${pos.y.toFixed(1)})` ); marker.addTo(layerGroup); } // Add to map (initially visible) layerGroup.addTo(map); resourceLayerGroups[group.name] = layerGroup; filterState[group.name] = true; // Initially visible } // Initialize filter UI with skill grouping function initializeFilterUI() { const container = document.getElementById('resource-filters'); if (!container) { console.error('resource-filters container not found'); return; } container.innerHTML = ''; // Clear loading text // Group resources by skill const skillGroups = {}; for (const name in resourceLayerGroups) { const metadata = resourceData[name]; if (!skillGroups[metadata.skill]) { skillGroups[metadata.skill] = []; } skillGroups[metadata.skill].push({ name: name, level: metadata.level, }); } // Sort skills alphabetically const sortedSkills = Object.keys(skillGroups).sort(); // Create UI for each skill group for (const skill of sortedSkills) { const skillDiv = document.createElement('div'); skillDiv.className = 'skill-group'; const header = document.createElement('div'); header.className = 'skill-header'; // Capitalize first letter of skill header.textContent = skill.charAt(0).toUpperCase() + skill.slice(1); skillDiv.appendChild(header); // Resources are already sorted by level in backend, but sort again to be sure skillGroups[skill].sort((a, b) => a.level - b.level); // Create checkbox for each resource for (const resource of skillGroups[skill]) { const label = createFilterLabel(resource.name); skillDiv.appendChild(label); } container.appendChild(skillDiv); } // Attach bulk filter handlers const selectAllBtn = document.getElementById('select-all-resources'); const deselectAllBtn = document.getElementById('deselect-all-resources'); if (selectAllBtn) { selectAllBtn.addEventListener('click', () => { setAllFilters(true); }); } if (deselectAllBtn) { deselectAllBtn.addEventListener('click', () => { setAllFilters(false); }); } } // Create filter label with checkbox and icon function createFilterLabel(resourceName) { const label = document.createElement('label'); label.className = 'filter-label'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.checked = filterState[resourceName]; checkbox.dataset.resource = resourceName; checkbox.addEventListener('change', handleFilterChange); const icon = document.createElement('img'); icon.src = resourceIcons[resourceName].options.iconUrl; icon.className = 'filter-icon'; icon.alt = resourceName; const text = document.createElement('span'); text.textContent = resourceName; label.appendChild(checkbox); label.appendChild(icon); label.appendChild(text); return label; } // Handle filter checkbox change function handleFilterChange(event) { const resourceName = event.target.dataset.resource; const isVisible = event.target.checked; filterState[resourceName] = isVisible; // Show/hide layer group const layerGroup = resourceLayerGroups[resourceName]; if (isVisible) { layerGroup.addTo(map); } else { map.removeLayer(layerGroup); } // Persist state saveFilterState(); } // Set all filters to visible or hidden function setAllFilters(visible) { for (const name in filterState) { filterState[name] = visible; const layerGroup = resourceLayerGroups[name]; if (visible) { layerGroup.addTo(map); } else { map.removeLayer(layerGroup); } } // Update checkboxes document.querySelectorAll('#resource-filters input[type="checkbox"]').forEach((cb) => { cb.checked = visible; }); saveFilterState(); } // Save filter state to localStorage function saveFilterState() { try { localStorage.setItem('cursebreaker_resource_filters', JSON.stringify(filterState)); } catch (error) { console.warn('Failed to save filter state to localStorage:', error); } } // Restore filter state from localStorage function restoreFilterState() { const saved = localStorage.getItem('cursebreaker_resource_filters'); if (!saved) return; try { const savedState = JSON.parse(saved); for (const name in savedState) { if (resourceLayerGroups[name]) { filterState[name] = savedState[name]; const layerGroup = resourceLayerGroups[name]; if (!savedState[name]) { map.removeLayer(layerGroup); } } } // Update checkboxes after UI is created setTimeout(() => { document.querySelectorAll('#resource-filters input[type="checkbox"]').forEach((cb) => { const name = cb.dataset.resource; if (filterState[name] !== undefined) { cb.checked = filterState[name]; } }); }, 100); console.log('Restored filter state from localStorage'); } catch (error) { console.warn('Failed to restore filter state:', error); } }