Files
2026-01-26 13:05:57 +00:00

378 lines
12 KiB
JavaScript

// Markers management for Cursebreaker map (Labels, Entrances, Ground Items, Houses)
// Layer groups for each marker type
let labelsLayerGroup = null;
let entrancesLayerGroup = null;
let groundItemsLayerGroup = null;
let housesLayerGroup = null;
// Store active teleport lines for entrances
let activeTeleportLine = null;
// Initialize all markers when map is ready
function initMarkers() {
// Load all marker types in parallel
Promise.all([
loadLabels(),
loadEntrances(),
loadGroundItems(),
loadHouses(),
]).catch(error => {
console.error('Error loading markers:', error);
});
// Set up toggle handlers
setupMarkerToggles();
}
// Set up toggle event handlers
function setupMarkerToggles() {
const labelsToggle = document.getElementById('labels-toggle');
const entrancesToggle = document.getElementById('entrances-toggle');
const groundItemsToggle = document.getElementById('ground-items-toggle');
const housesToggle = document.getElementById('houses-toggle');
if (labelsToggle) {
labelsToggle.addEventListener('change', (e) => {
toggleLayer(labelsLayerGroup, e.target.checked);
saveMarkerState('labels', e.target.checked);
});
}
if (entrancesToggle) {
entrancesToggle.addEventListener('change', (e) => {
toggleLayer(entrancesLayerGroup, e.target.checked);
saveMarkerState('entrances', e.target.checked);
// Remove active teleport line when hiding entrances
if (!e.target.checked && activeTeleportLine) {
map.removeLayer(activeTeleportLine);
activeTeleportLine = null;
}
});
}
if (groundItemsToggle) {
groundItemsToggle.addEventListener('change', (e) => {
toggleLayer(groundItemsLayerGroup, e.target.checked);
saveMarkerState('groundItems', e.target.checked);
});
}
if (housesToggle) {
housesToggle.addEventListener('change', (e) => {
toggleLayer(housesLayerGroup, e.target.checked);
saveMarkerState('houses', e.target.checked);
});
}
// Restore saved state
restoreMarkerState();
}
// Toggle layer visibility
function toggleLayer(layerGroup, visible) {
if (!layerGroup) return;
if (visible) {
layerGroup.addTo(map);
} else {
map.removeLayer(layerGroup);
}
}
// Save marker visibility state
function saveMarkerState(type, visible) {
try {
const state = JSON.parse(localStorage.getItem('cursebreaker_marker_state') || '{}');
state[type] = visible;
localStorage.setItem('cursebreaker_marker_state', JSON.stringify(state));
} catch (error) {
console.warn('Failed to save marker state:', error);
}
}
// Restore marker visibility state
function restoreMarkerState() {
try {
const state = JSON.parse(localStorage.getItem('cursebreaker_marker_state') || '{}');
// Update checkboxes and layers based on saved state
setTimeout(() => {
if (state.labels === false) {
const toggle = document.getElementById('labels-toggle');
if (toggle) {
toggle.checked = false;
toggleLayer(labelsLayerGroup, false);
}
}
if (state.entrances === false) {
const toggle = document.getElementById('entrances-toggle');
if (toggle) {
toggle.checked = false;
toggleLayer(entrancesLayerGroup, false);
}
}
if (state.groundItems === false) {
const toggle = document.getElementById('ground-items-toggle');
if (toggle) {
toggle.checked = false;
toggleLayer(groundItemsLayerGroup, false);
}
}
if (state.houses === false) {
const toggle = document.getElementById('houses-toggle');
if (toggle) {
toggle.checked = false;
toggleLayer(housesLayerGroup, false);
}
}
}, 200);
} catch (error) {
console.warn('Failed to restore marker state:', error);
}
}
// Load labels (text markers on the map)
async function loadLabels() {
try {
console.log('Loading labels...');
const response = await fetch('/api/labels');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(`Received ${data.labels.length} labels`);
labelsLayerGroup = L.layerGroup();
for (const label of data.labels) {
// Create a divIcon with the label text
const labelIcon = L.divIcon({
className: 'map-label',
html: `<div class="label-text" style="font-size: ${label.font_size}px;">${label.text}</div>`,
iconSize: null, // Let CSS handle sizing
iconAnchor: [0, 0],
});
const marker = L.marker([label.y, label.x], {
icon: labelIcon,
interactive: false, // Labels shouldn't be clickable
});
marker.addTo(labelsLayerGroup);
}
labelsLayerGroup.addTo(map);
console.log('Labels loaded successfully');
} catch (error) {
console.error('Error loading labels:', error);
}
}
// Load entrances (teleporters with lines)
async function loadEntrances() {
try {
console.log('Loading entrances...');
const response = await fetch('/api/entrances');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(`Received ${data.entrances.length} entrances`);
entrancesLayerGroup = L.layerGroup();
// Create icon from base64
const iconUrl = `data:image/webp;base64,${data.icon_base64}`;
const entranceIcon = L.icon({
iconUrl: iconUrl,
iconSize: [32, 32],
iconAnchor: [16, 16],
popupAnchor: [0, -16],
});
for (const entrance of data.entrances) {
const marker = L.marker([entrance.pos_y, entrance.pos_x], {
icon: entranceIcon,
title: 'Entrance',
});
// Store teleport destination on the marker
marker.teleportDest = {
x: entrance.tp_x,
y: entrance.tp_y,
};
// Handle click to show teleport line
marker.on('click', function(e) {
// Remove existing line if any
if (activeTeleportLine) {
map.removeLayer(activeTeleportLine);
activeTeleportLine = null;
}
const dest = this.teleportDest;
if (dest.x !== null && dest.y !== null) {
// Create a line from entrance to destination
activeTeleportLine = L.polyline(
[
[entrance.pos_y, entrance.pos_x],
[dest.y, dest.x]
],
{
color: '#00ffff',
weight: 3,
opacity: 0.8,
dashArray: '10, 10',
}
).addTo(map);
// Add a destination marker
const destMarker = L.circleMarker([dest.y, dest.x], {
radius: 8,
color: '#00ffff',
fillColor: '#00ffff',
fillOpacity: 0.5,
}).addTo(map);
// Remove line and destination marker after 5 seconds
setTimeout(() => {
if (activeTeleportLine) {
map.removeLayer(activeTeleportLine);
activeTeleportLine = null;
}
map.removeLayer(destMarker);
}, 5000);
}
});
marker.addTo(entrancesLayerGroup);
}
entrancesLayerGroup.addTo(map);
console.log('Entrances loaded successfully');
} catch (error) {
console.error('Error loading entrances:', error);
}
}
// Format respawn time as "XXM XXS"
function formatRespawnTime(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = seconds % 60;
if (minutes > 0 && secs > 0) {
return `${minutes}M ${secs}S`;
} else if (minutes > 0) {
return `${minutes}M`;
} else {
return `${secs}S`;
}
}
// Load ground items
async function loadGroundItems() {
try {
console.log('Loading ground items...');
const response = await fetch('/api/ground-items');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(`Received ${data.items.length} ground items`);
groundItemsLayerGroup = L.layerGroup();
// Create icon from base64
const iconUrl = `data:image/webp;base64,${data.icon_base64}`;
const itemIcon = L.icon({
iconUrl: iconUrl,
iconSize: [24, 24],
iconAnchor: [12, 12],
popupAnchor: [0, -12],
});
for (const item of data.items) {
const marker = L.marker([item.y, item.x], {
icon: itemIcon,
title: item.name,
});
// Build popup content
let popupContent = `<strong>${item.name}</strong>`;
if (item.amount > 1) {
popupContent += `<br/>Amount: ${item.amount}`;
}
popupContent += `<br/>Respawn: ${formatRespawnTime(item.respawn_time)}`;
marker.bindPopup(popupContent);
marker.addTo(groundItemsLayerGroup);
}
groundItemsLayerGroup.addTo(map);
console.log('Ground items loaded successfully');
} catch (error) {
console.error('Error loading ground items:', error);
}
}
// Load player houses
async function loadHouses() {
try {
console.log('Loading houses...');
const response = await fetch('/api/houses');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(`Received ${data.houses.length} houses`);
housesLayerGroup = L.layerGroup();
// Create icon from base64
const iconUrl = `data:image/webp;base64,${data.icon_base64}`;
const houseIcon = L.icon({
iconUrl: iconUrl,
iconSize: [64, 64],
iconAnchor: [32, 32],
popupAnchor: [0, -32],
});
for (const house of data.houses) {
const marker = L.marker([house.y, house.x], {
icon: houseIcon,
title: house.name,
});
// Format price with commas
const formattedPrice = house.price.toLocaleString();
// Build popup content
const popupContent = `
<strong>${house.name}</strong><br/>
<em>${house.description}</em><br/>
<span class="house-price">Price: ${formattedPrice} gold</span>
`;
marker.bindPopup(popupContent);
marker.addTo(housesLayerGroup);
}
housesLayerGroup.addTo(map);
console.log('Houses loaded successfully');
} catch (error) {
console.error('Error loading houses:', error);
}
}
// Call initMarkers after map is loaded
// This is called from map.js after resources are loaded