Release 0.9.0
This commit is contained in:
168
bot/modules/players/templates/webmap/player_actions.html
Normal file
168
bot/modules/players/templates/webmap/player_actions.html
Normal 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;
|
||||
}
|
||||
3
bot/modules/players/templates/webmap/player_markers.html
Normal file
3
bot/modules/players/templates/webmap/player_markers.html
Normal 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
|
||||
64
bot/modules/players/templates/webmap/player_popup.html
Normal file
64
bot/modules/players/templates/webmap/player_popup.html
Normal 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>
|
||||
`;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user