Release 0.9.0

This commit is contained in:
2025-11-21 07:26:02 +01:00
committed by ecv
commit 472f0812e7
240 changed files with 20033 additions and 0 deletions

View File

@@ -0,0 +1,168 @@
// ========================================
// Player Popup Actions
// ========================================
// Kick player from map popup
window.kickPlayerFromMap = function(steamid, playerName) {
const reason = prompt('Kick reason for ' + playerName + ':', 'Admin action');
if (reason === null) {
return; // User cancelled
}
window.socket.emit(
'widget_event',
['players',
['kick_player', {
'action': 'kick_player',
'steamid': steamid,
'reason': reason || 'No reason provided',
'confirmed': 'True'
}]]
);
console.log('[MAP] Kicking player:', steamid, 'Reason:', reason);
};
// Send message to player from map popup
window.messagePlayerFromMap = function(steamid, playerName) {
const message = prompt('Message to ' + playerName + ':', '');
if (!message) {
return; // User cancelled or empty message
}
window.socket.emit(
'widget_event',
['players',
['say_to_player', {
'steamid': steamid,
'message': message
}]]
);
console.log('[MAP] Sending message to player:', steamid, 'Message:', message);
};
// Toggle player mute status from map popup
window.togglePlayerMuteFromMap = function(steamid, dataset, isMuted) {
window.socket.emit(
'widget_event',
['players',
['toggle_player_mute', {
'steamid': steamid,
'dataset': dataset,
'mute_status': isMuted
}]]
);
console.log('[MAP] Toggling player mute status:', steamid, 'to', isMuted);
};
// Toggle player authentication status from map popup
window.togglePlayerAuthFromMap = function(steamid, dataset, isAuth) {
window.socket.emit(
'widget_event',
['players',
['toggle_player_authentication', {
'steamid': steamid,
'dataset': dataset,
'auth_status': isAuth
}]]
);
console.log('[MAP] Toggling player auth status:', steamid, 'to', isAuth);
};
// Teleport player from map popup - click to select destination
window.teleportPlayerFromMap = function(steamid, playerName) {
// Close any open popups
map.closePopup();
// Create info message
const infoDiv = L.DomUtil.create('div', 'coordinates-display');
infoDiv.style.bottom = '50px';
infoDiv.style.background = 'rgba(255, 204, 0, 0.95)';
infoDiv.style.borderColor = 'var(--lcars-golden-tanoi)';
infoDiv.style.color = '#000';
infoDiv.style.fontWeight = 'bold';
infoDiv.innerHTML = '🎯 Click destination for ' + playerName + ' (Locations will use their TP entry)';
document.getElementById('map').appendChild(infoDiv);
// Change cursor
map.getContainer().style.cursor = 'crosshair';
// Wait for click
map.once('click', function(e) {
const clickCoords = e.latlng;
let targetX = Math.round(clickCoords.lat);
let targetY = 0; // Default Y, will be adjusted
let targetZ = Math.round(clickCoords.lng);
// Check if click is inside any location with teleport_entry
let foundLocationTeleport = false;
for (const locationId in locations) {
const loc = locations[locationId];
const teleportEntry = loc.teleport_entry || {};
const hasTeleport = teleportEntry.x !== undefined &&
teleportEntry.y !== undefined &&
teleportEntry.z !== undefined;
if (hasTeleport) {
// Check if click is inside this location's bounds
if (isInsideLocation(clickCoords.lat, clickCoords.lng, loc)) {
// Use location's teleport entry
targetX = Math.round(parseFloat(teleportEntry.x));
targetY = Math.round(parseFloat(teleportEntry.y));
targetZ = Math.round(parseFloat(teleportEntry.z));
foundLocationTeleport = true;
console.log('[MAP] Using location teleport entry:', loc.name, 'at', targetX, targetY, targetZ);
break;
}
}
}
// If no location teleport found, use default Y (ground level)
if (!foundLocationTeleport) {
targetY = -1; // Standard ground level
}
// Call teleport_player action
window.socket.emit(
'widget_event',
['players',
['teleport_player', {
'steamid': steamid,
'coordinates': {
'x': targetX.toString(),
'y': targetY.toString(),
'z': targetZ.toString()
}
}]]
);
console.log('[MAP] Teleporting player:', steamid, 'to', targetX, targetY, targetZ);
// Cleanup
map.getContainer().style.cursor = '';
document.getElementById('map').removeChild(infoDiv);
});
};
// Helper function to check if coordinates are inside a location
function isInsideLocation(x, z, loc) {
const coords = loc.coordinates;
const dims = loc.dimensions;
const shape = loc.shape;
if (shape === 'circle' || shape === 'spherical') {
const radius = parseFloat(dims.radius || 0);
const distance = Math.sqrt(
Math.pow(x - coords.x, 2) +
Math.pow(z - coords.z, 2)
);
return distance <= radius;
} else if (shape === 'rectangle' || shape === 'box') {
const width = parseFloat(dims.width || 0);
const length = parseFloat(dims.length || 0);
return (
x >= coords.x - width && x <= coords.x + width &&
z >= coords.z - length && z <= coords.z + length
);
}
return false;
}

View File

@@ -0,0 +1,3 @@
// Player markers are now loaded dynamically via Socket.IO
// Initial loading is handled by player_position_update events
// See player_update_handler.html for marker creation logic

View File

@@ -0,0 +1,64 @@
// Helper function to create player popup content
function createPlayerPopup(steamid, player) {
const healthMax = 150;
const healthPercent = Math.round((player.health / healthMax) * 100);
// Status badge
let statusBadge = '🟢 Online';
let statusColor = '#66ff66';
if (player.in_limbo) {
statusBadge = '💀 Dead';
statusColor = '#ff6666';
} else if (!player.is_initialized) {
statusBadge = '🔄 Spawning';
statusColor = '#ffaa66';
}
// Permission badge
let permissionBadge = '';
if (player.permission_level === 0) {
permissionBadge = '<span style="background: #ff0000; color: white; padding: 2px 6px; border-radius: 3px; font-size: 0.85em; font-weight: bold;">🛡️ ADMIN</span>';
}
// Use template literal for clean HTML
return `
<div style="min-width: 280px; font-family: monospace;">
<b style="font-size: 1.1em;">${player.name}</b>
${permissionBadge ? '<br>' + permissionBadge : ''}
<br><span style="font-size: 0.9em; color: ${statusColor};">${statusBadge}</span>
<br><hr style="margin: 5px 0; border-color: #333;">
<b>❤️ Health:</b> ${player.health}/${healthMax} (${healthPercent}%)
<br><b>⭐ Level:</b> ${player.level} | <b>🎯 Score:</b> ${player.score}
<br><b>🧟 Zombies:</b> ${player.zombies} | <b>💀 Deaths:</b> ${player.deaths}
<br><b>👥 Players:</b> ${player.players} | <b>📡 Ping:</b> ${player.ping}ms
<br><hr style="margin: 8px 0; border-color: #333;">
<div style="display: flex; gap: 5px; margin-bottom: 5px;">
<label style="display: flex; align-items: center; gap: 5px; cursor: pointer; flex: 1;">
<input type="checkbox"
${player.is_muted ? 'checked' : ''}
onchange="togglePlayerMuteFromMap('${steamid}', this.checked)"
style="cursor: pointer; width: 16px; height: 16px;" />
<span style="font-weight: bold; font-size: 0.9em;">🔇 Muted</span>
</label>
<label style="display: flex; align-items: center; gap: 5px; cursor: pointer; flex: 1;">
<input type="checkbox"
${player.is_authenticated ? 'checked' : ''}
onchange="togglePlayerAuthFromMap('${steamid}', this.checked)"
style="cursor: pointer; width: 16px; height: 16px;" />
<span style="font-weight: bold; font-size: 0.9em;">✅ Auth</span>
</label>
</div>
<div style="display: flex; gap: 5px; margin-bottom: 5px;">
<button onclick="messagePlayerFromMap('${steamid}', '${player.name.replace(/'/g, "\\'")}', event)"
style="flex: 1; padding: 6px 10px; background: var(--lcars-anakiwa); color: #000; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 0.9em;">
💬 Message</button>
<button onclick="kickPlayerFromMap('${steamid}', '${player.name.replace(/'/g, "\\'")}', event)"
style="flex: 1; padding: 6px 10px; background: var(--lcars-hopbush); color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 0.9em;">
👢 Kick</button>
</div>
<button onclick="teleportPlayerFromMap('${steamid}', '${player.name.replace(/'/g, "\\'")}', event)"
style="width: 100%; padding: 8px 10px; background: var(--lcars-golden-tanoi); color: #000; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 0.95em;">
🎯 Teleport Player - Click Map</button>
</div>
`;
}

View File

@@ -0,0 +1,58 @@
// Handle player position updates
if (data.data_type === 'player_position_update' && data.payload) {
const playerData = data.payload;
const steamid = playerData.steamid;
const pos = playerData.position;
console.log('[MAP] Processing player update for:', steamid, pos);
if (!steamid || !pos) {
console.warn('[MAP] Invalid player update data:', playerData);
return;
}
// Update or create player data in players object
if (!players[steamid]) {
players[steamid] = {};
}
// Update all player data
players[steamid].pos = pos;
players[steamid].name = playerData.name || players[steamid].name || 'Player';
players[steamid].level = playerData.level !== undefined ? playerData.level : (players[steamid].level || 0);
players[steamid].health = playerData.health !== undefined ? playerData.health : (players[steamid].health || 0);
players[steamid].zombies = playerData.zombies !== undefined ? playerData.zombies : (players[steamid].zombies || 0);
players[steamid].deaths = playerData.deaths !== undefined ? playerData.deaths : (players[steamid].deaths || 0);
players[steamid].players = playerData.players !== undefined ? playerData.players : (players[steamid].players || 0);
players[steamid].score = playerData.score !== undefined ? playerData.score : (players[steamid].score || 0);
players[steamid].ping = playerData.ping !== undefined ? playerData.ping : (players[steamid].ping || 0);
players[steamid].is_muted = playerData.is_muted !== undefined ? playerData.is_muted : (players[steamid].is_muted || false);
players[steamid].is_authenticated = playerData.is_authenticated !== undefined ? playerData.is_authenticated : (players[steamid].is_authenticated || false);
players[steamid].in_limbo = playerData.in_limbo !== undefined ? playerData.in_limbo : (players[steamid].in_limbo || false);
players[steamid].is_initialized = playerData.is_initialized !== undefined ? playerData.is_initialized : (players[steamid].is_initialized || false);
players[steamid].permission_level = playerData.permission_level || players[steamid].permission_level || null;
players[steamid].dataset = playerData.dataset || players[steamid].dataset || '';
// Update or create marker
if (playerMarkers[steamid]) {
// Update existing marker position and popup
playerMarkers[steamid].setLatLng([pos.x, pos.z]);
playerMarkers[steamid].setPopupContent(createPlayerPopup(steamid, players[steamid]));
console.log('[MAP] Updated player marker:', steamid);
} else {
// Create new marker
const marker = L.circleMarker([pos.x, pos.z], {
radius: 5,
fillColor: '#66ccff',
color: '#0099ff',
weight: 2,
opacity: 1,
fillOpacity: 0.9,
className: 'player-marker'
}).addTo(map);
marker.bindPopup(createPlayerPopup(steamid, players[steamid]));
playerMarkers[steamid] = marker;
console.log('[MAP] Created new player marker:', steamid);
}
}