378 lines
12 KiB
JavaScript
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
|