megabot/web/index.html
megaproxy a3ed25f8dd Initial commit: Enhanced IRC bot with duck hunt game
- Added hunt/feed duck mechanics (80% hunt, 20% feed)
- Implemented persistent scoring system
- Added channel control commands (\!stopducks/\!startducks)
- Enhanced duck hunt with wrong action penalties
- Organized bot structure with botmain.js as main file
- Added comprehensive documentation (README.md)
- Included 17 plugins with various games and utilities

🦆 Duck Hunt Features:
- Hunt ducks with \!shoot/\!bang (80% of spawns)
- Feed ducks with \!feed (20% of spawns)
- Persistent scores saved to JSON
- Channel-specific controls for #bakedbeans
- Reaction time tracking and special achievements

🎮 Other Games:
- Casino games (slots, coinflip, hi-lo, scratch cards)
- Multiplayer games (pigs, zombie dice, quiplash)
- Text generation (babble, conspiracy, drunk historian)
- Interactive features (story writing, emojify, combos)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-17 19:03:45 +00:00

1288 lines
No EOL
43 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pet Battles World Map</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #333;
overflow: hidden;
height: 100vh;
}
.map-container {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
.world-map {
position: relative;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #87CEEB 0%, #98FB98 30%, #F4A460 60%, #DDA0DD 100%);
overflow: hidden;
}
/* Biome Areas */
.biome {
position: absolute;
border-radius: 20px;
transition: all 0.3s ease;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 2em;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
color: white;
border: 3px solid rgba(255,255,255,0.3);
backdrop-filter: blur(5px);
}
.biome:hover {
transform: scale(1.05);
border-color: rgba(255,255,255,0.8);
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.biome-town {
top: 45%;
left: 45%;
width: 10%;
height: 10%;
background: linear-gradient(135deg, #8B4513, #A0522D);
}
.biome-forest {
top: 20%;
left: 15%;
width: 25%;
height: 30%;
background: linear-gradient(135deg, #228B22, #32CD32);
}
.biome-mountain {
top: 10%;
left: 60%;
width: 30%;
height: 35%;
background: linear-gradient(135deg, #696969, #2F4F4F);
}
.biome-ocean {
top: 65%;
left: 10%;
width: 35%;
height: 25%;
background: linear-gradient(135deg, #4682B4, #1E90FF);
}
.biome-desert {
top: 55%;
left: 65%;
width: 30%;
height: 30%;
background: linear-gradient(135deg, #DAA520, #B8860B);
}
/* Player Icons */
.player {
position: absolute;
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #FF6B6B, #4ECDC4);
border: 3px solid white;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
z-index: 100;
}
.player:hover {
transform: scale(1.2);
box-shadow: 0 6px 25px rgba(0,0,0,0.4);
}
.player.exploring {
background: linear-gradient(135deg, #32CD32, #90EE90);
animation: pulse 2s infinite;
}
.player.encountering {
background: linear-gradient(135deg, #FFD700, #FFA500);
animation: encounter 1s infinite;
}
.player.battling {
background: linear-gradient(135deg, #FF4500, #DC143C);
animation: battle 0.5s infinite alternate;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
@keyframes encounter {
0%, 100% { box-shadow: 0 4px 15px rgba(255,215,0,0.5); }
50% { box-shadow: 0 4px 25px rgba(255,215,0,0.8); }
}
@keyframes battle {
from { transform: rotate(-2deg); }
to { transform: rotate(2deg); }
}
/* Player Info Panel */
.player-info {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
border-radius: 15px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
z-index: 1000;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
display: none;
}
.player-info.show {
display: block;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
.close-btn {
position: absolute;
top: 15px;
right: 20px;
background: #ff4757;
color: white;
border: none;
border-radius: 50%;
width: 30px;
height: 30px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
}
.player-header {
text-align: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px solid #eee;
}
.player-name {
font-size: 1.5em;
font-weight: bold;
color: #2c3e50;
margin-bottom: 5px;
}
.player-location {
color: #7f8c8d;
font-size: 1.1em;
}
.info-section {
margin-bottom: 20px;
}
.section-title {
font-size: 1.2em;
font-weight: bold;
color: #2c3e50;
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 8px;
}
.pet-card {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 10px;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 10px;
}
.pet-card.active {
border-color: #28a745;
background: #d4edda;
}
.pet-emoji {
font-size: 1.5em;
}
.pet-info {
flex: 1;
}
.pet-name {
font-weight: bold;
color: #2c3e50;
}
.pet-stats {
font-size: 0.9em;
color: #6c757d;
}
.hp-bar {
width: 100%;
height: 8px;
background: #dee2e6;
border-radius: 4px;
overflow: hidden;
margin-top: 4px;
}
.hp-fill {
height: 100%;
background: linear-gradient(90deg, #28a745, #20c997);
transition: width 0.3s ease;
}
.item-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 8px;
}
.item-card {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 6px;
padding: 8px;
text-align: center;
font-size: 0.9em;
}
.item-name {
font-weight: bold;
color: #2c3e50;
}
.item-quantity {
color: #6c757d;
font-size: 0.8em;
}
/* UI Controls */
.controls {
position: fixed;
top: 20px;
left: 20px;
z-index: 200;
display: flex;
flex-direction: column;
gap: 10px;
}
.control-panel {
background: rgba(255,255,255,0.9);
backdrop-filter: blur(10px);
border-radius: 10px;
padding: 15px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.refresh-btn {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 8px;
padding: 10px 20px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s ease;
}
.refresh-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.auto-refresh {
display: flex;
align-items: center;
gap: 8px;
margin-top: 10px;
}
.legend {
position: fixed;
top: 20px;
right: 20px;
background: rgba(255,255,255,0.9);
backdrop-filter: blur(10px);
border-radius: 10px;
padding: 15px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
z-index: 200;
}
.legend-title {
font-weight: bold;
margin-bottom: 10px;
color: #2c3e50;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 5px;
font-size: 0.9em;
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
}
.status-exploring { background: linear-gradient(135deg, #32CD32, #90EE90); }
.status-encountering { background: linear-gradient(135deg, #FFD700, #FFA500); }
.status-battling { background: linear-gradient(135deg, #FF4500, #DC143C); }
.status-idle { background: linear-gradient(135deg, #FF6B6B, #4ECDC4); }
/* Stats Display */
.stats-panel {
position: fixed;
bottom: 20px;
left: 20px;
background: rgba(255,255,255,0.9);
backdrop-filter: blur(10px);
border-radius: 10px;
padding: 15px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
z-index: 200;
}
.stat-item {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
font-size: 0.9em;
}
.stat-label {
color: #6c757d;
}
.stat-value {
font-weight: bold;
color: #2c3e50;
}
/* Overlay */
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 999;
display: none;
}
.overlay.show {
display: block;
}
/* Responsive Design */
@media (max-width: 768px) {
.controls, .legend, .stats-panel {
position: relative;
margin: 10px;
}
.player-info {
max-width: 90vw;
max-height: 90vh;
padding: 20px;
}
.biome {
font-size: 1.5em;
}
}
/* Loading Animation */
.loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1001;
display: none;
}
.loading.show {
display: block;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="map-container">
<!-- World Map -->
<div class="world-map" id="worldMap">
<!-- Biomes -->
<div class="biome biome-town" data-biome="town" title="Trainer Town">
🏘️<br><small style="font-size:0.5em;">Town</small>
</div>
<div class="biome biome-forest" data-biome="forest" title="Mystic Forest">
🌲<br><small style="font-size:0.5em;">Forest</small>
</div>
<div class="biome biome-mountain" data-biome="mountain" title="Dragon Peaks">
🏔️<br><small style="font-size:0.5em;">Mountain</small>
</div>
<div class="biome biome-ocean" data-biome="ocean" title="Crystal Shores">
🌊<br><small style="font-size:0.5em;">Ocean</small>
</div>
<div class="biome biome-desert" data-biome="desert" title="Thunder Desert">
🏜️<br><small style="font-size:0.5em;">Desert</small>
</div>
<!-- Players will be dynamically added here -->
</div>
<!-- Controls -->
<div class="controls">
<div class="control-panel">
<button class="refresh-btn" onclick="refreshMap()">🔄 Refresh Map</button>
<div class="auto-refresh">
<input type="checkbox" id="autoRefresh" checked>
<label for="autoRefresh">Auto-refresh (5s)</label>
</div>
</div>
</div>
<!-- Legend -->
<div class="legend">
<div class="legend-title">Player Status</div>
<div class="legend-item">
<div class="status-indicator status-idle"></div>
<span>Idle</span>
</div>
<div class="legend-item">
<div class="status-indicator status-exploring"></div>
<span>Exploring</span>
</div>
<div class="legend-item">
<div class="status-indicator status-encountering"></div>
<span>Wild Encounter</span>
</div>
<div class="legend-item">
<div class="status-indicator status-battling"></div>
<span>In Battle</span>
</div>
</div>
<!-- Stats Panel -->
<div class="stats-panel">
<div class="legend-title">World Stats</div>
<div class="stat-item">
<span class="stat-label">Active Players:</span>
<span class="stat-value" id="playerCount">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Wild Encounters:</span>
<span class="stat-value" id="encounterCount">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Active Battles:</span>
<span class="stat-value" id="battleCount">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Last Update:</span>
<span class="stat-value" id="lastUpdate">Never</span>
</div>
</div>
</div>
<!-- Player Info Modal -->
<div class="overlay" id="overlay" onclick="closePlayerInfo()"></div>
<div class="player-info" id="playerInfo">
<button class="close-btn" onclick="closePlayerInfo()">×</button>
<div class="player-header">
<div class="player-name" id="playerName">Player Name</div>
<div class="player-location" id="playerLocation">Current Location</div>
</div>
<div class="info-section">
<div class="section-title">🎒 Active Party</div>
<div id="playerParty">
<!-- Party pets will be loaded here -->
</div>
</div>
<div class="info-section">
<div class="section-title">📦 Inventory</div>
<div class="item-grid" id="playerInventory">
<!-- Items will be loaded here -->
</div>
</div>
<div class="info-section">
<div class="section-title">📊 Statistics</div>
<div id="playerStats">
<!-- Stats will be loaded here -->
</div>
</div>
</div>
<!-- Loading Indicator -->
<div class="loading" id="loading">
<div class="spinner"></div>
</div>
<script>
// Global variables
let gameData = null;
let autoRefreshInterval = null;
let selectedPlayer = null;
// Biome positioning data
const biomePositions = {
town: { x: 50, y: 50 },
forest: { x: 27.5, y: 35 },
mountain: { x: 75, y: 27.5 },
ocean: { x: 27.5, y: 77.5 },
desert: { x: 80, y: 70 }
};
// Pet type emojis
const petTypeEmojis = {
normal: '🐱',
fire: '🔥',
water: '💧',
grass: '🌱',
electric: '⚡',
fighting: '🥊',
psychic: '🌟',
ghost: '👻',
dragon: '🐉'
};
// Pet species to type mapping
const petSpeciesTypes = {
'Whiskers': 'normal',
'Flame': 'fire',
'Splash': 'water',
'Leafy': 'grass',
'Sparky': 'electric',
'Boxer': 'fighting',
'Mystic': 'psychic',
'Phantom': 'ghost',
'Draco': 'dragon',
'Phoenix': 'fire'
};
// Initialize the map
function initMap() {
console.log('Initializing Pet Battles World Map...');
refreshMap();
startAutoRefresh();
}
// Refresh map data
async function refreshMap() {
showLoading(true);
try {
const response = await fetch('/api/gamestate');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
gameData = await response.json();
updateMap();
updateStats();
} catch (error) {
console.error('Error fetching game data:', error);
// Show mock data for demonstration
gameData = generateMockData();
updateMap();
updateStats();
}
showLoading(false);
}
// Generate mock data for demonstration
function generateMockData() {
const mockPlayers = [
{
nick: 'TrainerAlex',
location: { biome: 'forest', x: 25, y: 30 },
activePet: { nickname: 'Leafy', species_name: 'Leafy', level: 12, hp: 45, max_hp: 60 },
totalPets: 5,
status: 'exploring'
},
{
nick: 'FireMaster99',
location: { biome: 'mountain', x: 70, y: 25 },
activePet: { nickname: 'Blaze', species_name: 'Phoenix', level: 25, hp: 80, max_hp: 95 },
totalPets: 8,
status: 'encountering'
},
{
nick: 'WaterTrainer',
location: { biome: 'ocean', x: 30, y: 80 },
activePet: { nickname: 'Splash', species_name: 'Splash', level: 8, hp: 32, max_hp: 40 },
totalPets: 3,
status: 'idle'
},
{
nick: 'ElectricAce',
location: { biome: 'desert', x: 75, y: 65 },
activePet: { nickname: 'Thunder', species_name: 'Sparky', level: 15, hp: 28, max_hp: 45 },
totalPets: 6,
status: 'battling'
}
];
return {
players: mockPlayers,
encounters: [
{
player: 'FireMaster99',
pet: { name: 'Draco', level: 18, rarity: 'rare' },
location: { biome: 'mountain', x: 70, y: 25 }
}
],
battles: [],
biomes: {
town: { name: 'Trainer Town', emoji: '🏘️' },
forest: { name: 'Mystic Forest', emoji: '🌲' },
mountain: { name: 'Dragon Peaks', emoji: '🏔️' },
ocean: { name: 'Crystal Shores', emoji: '🌊' },
desert: { name: 'Thunder Desert', emoji: '🏜️' }
}
};
}
// Update map with current data
function updateMap() {
const worldMap = document.getElementById('worldMap');
// Remove existing players
const existingPlayers = worldMap.querySelectorAll('.player');
existingPlayers.forEach(player => player.remove());
// Add current players
if (gameData && gameData.players) {
gameData.players.forEach(player => {
createPlayerElement(player);
});
}
}
// Create player element on map
function createPlayerElement(player) {
const worldMap = document.getElementById('worldMap');
const playerElement = document.createElement('div');
playerElement.className = `player ${player.status || 'idle'}`;
playerElement.style.left = `${player.location.x}%`;
playerElement.style.top = `${player.location.y}%`;
playerElement.title = `${player.nick} - ${player.location.biome}`;
playerElement.onclick = () => showPlayerInfo(player);
// Add pet emoji if available
if (player.activePet) {
const petType = petSpeciesTypes[player.activePet.species_name] || 'normal';
playerElement.innerHTML = petTypeEmojis[petType] || '🐾';
} else {
playerElement.innerHTML = '👤';
}
worldMap.appendChild(playerElement);
}
// Show player information modal
async function showPlayerInfo(player) {
selectedPlayer = player;
// Update basic info
document.getElementById('playerName').textContent = player.nick;
document.getElementById('playerLocation').textContent =
`${gameData.biomes[player.location.biome]?.emoji || ''} ${gameData.biomes[player.location.biome]?.name || player.location.biome}`;
// Load detailed player data
try {
showLoading(true);
const detailedData = await fetchPlayerDetails(player.nick);
populatePlayerInfo(detailedData);
} catch (error) {
console.error('Error loading player details:', error);
populatePlayerInfo(generateMockPlayerData(player));
}
showLoading(false);
document.getElementById('overlay').classList.add('show');
document.getElementById('playerInfo').classList.add('show');
}
// Fetch detailed player information
async function fetchPlayerDetails(playerNick) {
const response = await fetch(`/api/player/${playerNick}`);
if (!response.ok) {
throw new Error('Player data not available');
}
return await response.json();
}
// Generate mock player data
function generateMockPlayerData(player) {
const mockParty = [
{
nickname: player.activePet?.nickname || 'Starter',
species_name: player.activePet?.species_name || 'Whiskers',
level: player.activePet?.level || 5,
hp: player.activePet?.hp || 30,
max_hp: player.activePet?.max_hp || 40,
party_slot: 1
},
{
nickname: 'Buddy',
species_name: 'Flame',
level: 8,
hp: 25,
max_hp: 35,
party_slot: 2
}
];
const mockInventory = [
{ item_name: 'Potion', quantity: 5 },
{ item_name: 'Great Ball', quantity: 3 },
{ item_name: 'Rare Candy', quantity: 2 }
];
return {
party: mockParty,
inventory: mockInventory,
stats: {
totalPets: player.totalPets || 3,
wins: Math.floor(Math.random() * 20),
battles: Math.floor(Math.random() * 30),
badges: Math.floor(Math.random() * 5)
}
};
}
// Populate player info modal
function populatePlayerInfo(data) {
// Party
const partyContainer = document.getElementById('playerParty');
partyContainer.innerHTML = '';
if (data.party && data.party.length > 0) {
data.party.forEach(pet => {
const petCard = createPetCard(pet);
partyContainer.appendChild(petCard);
});
} else {
partyContainer.innerHTML = '<p style="color: #6c757d; text-align: center;">No pets in party</p>';
}
// Inventory
const inventoryContainer = document.getElementById('playerInventory');
inventoryContainer.innerHTML = '';
if (data.inventory && data.inventory.length > 0) {
data.inventory.forEach(item => {
const itemCard = createItemCard(item);
inventoryContainer.appendChild(itemCard);
});
} else {
inventoryContainer.innerHTML = '<p style="color: #6c757d; text-align: center;">No items</p>';
}
// Stats
const statsContainer = document.getElementById('playerStats');
statsContainer.innerHTML = '';
if (data.stats) {
const stats = [
{ label: 'Total Pets', value: data.stats.totalPets || 0 },
{ label: 'Battles Won', value: data.stats.wins || 0 },
{ label: 'Total Battles', value: data.stats.battles || 0 },
{ label: 'Gym Badges', value: data.stats.badges || 0 }
];
stats.forEach(stat => {
const statElement = document.createElement('div');
statElement.className = 'stat-item';
statElement.innerHTML = `
<span class="stat-label">${stat.label}:</span>
<span class="stat-value">${stat.value}</span>
`;
statsContainer.appendChild(statElement);
});
}
}
// Create pet card element
function createPetCard(pet) {
const petCard = document.createElement('div');
petCard.className = `pet-card ${pet.party_slot === 1 ? 'active' : ''}`;
const petType = petSpeciesTypes[pet.species_name] || 'normal';
const petEmoji = petTypeEmojis[petType] || '🐾';
const hpPercentage = (pet.hp / pet.max_hp) * 100;
petCard.innerHTML = `
<div class="pet-emoji">${petEmoji}</div>
<div class="pet-info">
<div class="pet-name">${pet.nickname}</div>
<div class="pet-stats">Lv.${pet.level} ${pet.species_name}</div>
<div class="hp-bar">
<div class="hp-fill" style="width: ${hpPercentage}%"></div>
</div>
<div style="font-size: 0.8em; color: #6c757d; margin-top: 2px;">
${pet.hp}/${pet.max_hp} HP
</div>
</div>
`;
return petCard;
}
// Create item card element
function createItemCard(item) {
const itemCard = document.createElement('div');
itemCard.className = 'item-card';
// Get item emoji based on type
const itemEmojis = {
'Potion': '🧪',
'Super Potion': '🧪',
'Hyper Potion': '🧪',
'Rare Candy': '🍬',
'Lucky Egg': '🥚',
'Great Ball': '🥎',
'Ultra Ball': '⚾',
'Master Ball': '🔮',
'Power Bracer': '💪',
'Quick Claw': '⚡'
};
const emoji = itemEmojis[item.item_name] || '📦';
itemCard.innerHTML = `
<div style="font-size: 1.2em; margin-bottom: 4px;">${emoji}</div>
<div class="item-name">${item.item_name}</div>
<div class="item-quantity">x${item.quantity}</div>
`;
return itemCard;
}
// Close player info modal
function closePlayerInfo() {
document.getElementById('overlay').classList.remove('show');
document.getElementById('playerInfo').classList.remove('show');
selectedPlayer = null;
}
// Update world statistics
function updateStats() {
if (!gameData) return;
const playerCount = gameData.players ? gameData.players.length : 0;
const encounterCount = gameData.encounters ? gameData.encounters.length : 0;
const battleCount = gameData.battles ? gameData.battles.length : 0;
document.getElementById('playerCount').textContent = playerCount;
document.getElementById('encounterCount').textContent = encounterCount;
document.getElementById('battleCount').textContent = battleCount;
document.getElementById('lastUpdate').textContent = new Date().toLocaleTimeString();
}
// Show/hide loading indicator
function showLoading(show) {
const loading = document.getElementById('loading');
if (show) {
loading.classList.add('show');
} else {
loading.classList.remove('show');
}
}
// Auto-refresh functionality
function startAutoRefresh() {
const checkbox = document.getElementById('autoRefresh');
function updateAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
}
if (checkbox.checked) {
autoRefreshInterval = setInterval(refreshMap, 5000); // 5 seconds
}
}
checkbox.addEventListener('change', updateAutoRefresh);
updateAutoRefresh(); // Start immediately if checked
}
// Biome click handlers
function setupBiomeHandlers() {
const biomes = document.querySelectorAll('.biome');
biomes.forEach(biome => {
biome.addEventListener('click', (e) => {
const biomeName = e.target.dataset.biome;
showBiomeInfo(biomeName);
});
});
}
// Show biome information
function showBiomeInfo(biomeName) {
const biomeData = {
town: {
name: 'Trainer Town',
description: 'The starting area for new trainers. A peaceful place with basic amenities.',
commonPets: ['Whiskers', 'Boxer'],
gymLeader: null
},
forest: {
name: 'Mystic Forest',
description: 'A dense woodland filled with grass and ghost type pets.',
commonPets: ['Whiskers', 'Leafy', 'Phantom'],
rarePets: ['Mystic'],
gymLeader: 'Ranger Oak'
},
mountain: {
name: 'Dragon Peaks',
description: 'Towering mountains where fire and dragon types thrive.',
commonPets: ['Flame', 'Boxer'],
rarePets: ['Draco', 'Phoenix'],
gymLeader: 'Flame Master Ash'
},
ocean: {
name: 'Crystal Shores',
description: 'Pristine waters home to water and psychic type pets.',
commonPets: ['Splash'],
rarePets: ['Mystic'],
gymLeader: 'Captain Marina'
},
desert: {
name: 'Thunder Desert',
description: 'An arid landscape crackling with electric energy.',
commonPets: ['Sparky', 'Flame'],
rarePets: ['Phoenix'],
gymLeader: 'Thunder Sage Volt'
}
};
const biome = biomeData[biomeName];
if (!biome) return;
const playersInBiome = gameData.players ?
gameData.players.filter(p => p.location.biome === biomeName) : [];
alert(`🏞️ ${biome.name}\n\n${biome.description}\n\nCommon Pets: ${biome.commonPets.join(', ')}\n${biome.rarePets ? `Rare Pets: ${biome.rarePets.join(', ')}\n` : ''}${biome.gymLeader ? `Gym Leader: ${biome.gymLeader}\n` : ''}Players Here: ${playersInBiome.length}`);
}
// Keyboard shortcuts
function setupKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
switch(e.key) {
case 'r':
case 'R':
if (!e.ctrlKey && !e.metaKey) {
refreshMap();
}
break;
case 'Escape':
closePlayerInfo();
break;
case ' ':
e.preventDefault();
const checkbox = document.getElementById('autoRefresh');
checkbox.checked = !checkbox.checked;
checkbox.dispatchEvent(new Event('change'));
break;
}
});
}
// Tooltip system
function setupTooltips() {
// Add tooltips to various elements
const tooltips = {
'.refresh-btn': 'Refresh map data (R)',
'.biome': 'Click to view biome details',
'.player': 'Click to view trainer details',
'#autoRefresh': 'Toggle auto-refresh (Space)'
};
Object.entries(tooltips).forEach(([selector, text]) => {
const elements = document.querySelectorAll(selector);
elements.forEach(el => {
if (!el.title) {
el.title = text;
}
});
});
}
// Enhanced player status detection
function getPlayerStatusClass(player) {
if (!gameData) return 'idle';
// Check for encounters
const hasEncounter = gameData.encounters &&
gameData.encounters.some(enc => enc.player === player.nick);
if (hasEncounter) return 'encountering';
// Check for battles
const inBattle = gameData.battles &&
gameData.battles.some(battle =>
battle.player1 === player.nick || battle.player2 === player.nick);
if (inBattle) return 'battling';
// Check exploration status (recently moved)
const now = Date.now();
const lastMoved = player.location.lastMoved || 0;
const timeSinceMove = now - lastMoved;
if (timeSinceMove < 30000) { // 30 seconds
return 'exploring';
}
return 'idle';
}
// Add encounter indicators
function addEncounterIndicators() {
if (!gameData.encounters) return;
gameData.encounters.forEach(encounter => {
const encounterElement = document.createElement('div');
encounterElement.className = 'encounter-indicator';
encounterElement.style.cssText = `
position: absolute;
left: ${encounter.location.x + 2}%;
top: ${encounter.location.y - 2}%;
width: 20px;
height: 20px;
background: radial-gradient(circle, #FFD700, #FFA500);
border-radius: 50%;
animation: pulse 1s infinite;
z-index: 99;
pointer-events: none;
`;
encounterElement.title = `Wild ${encounter.pet.name} (Lv.${encounter.pet.level})`;
document.getElementById('worldMap').appendChild(encounterElement);
});
}
// Weather effects (cosmetic)
function addWeatherEffects() {
const weather = ['sunny', 'rainy', 'cloudy'][Math.floor(Math.random() * 3)];
const worldMap = document.getElementById('worldMap');
// Remove existing weather
const existingWeather = worldMap.querySelector('.weather-overlay');
if (existingWeather) {
existingWeather.remove();
}
if (weather === 'rainy') {
const rainOverlay = document.createElement('div');
rainOverlay.className = 'weather-overlay';
rainOverlay.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(transparent, rgba(173, 216, 230, 0.3));
pointer-events: none;
z-index: 1;
`;
worldMap.appendChild(rainOverlay);
// Add rain animation
for (let i = 0; i < 50; i++) {
const raindrop = document.createElement('div');
raindrop.style.cssText = `
position: absolute;
width: 2px;
height: 10px;
background: linear-gradient(transparent, rgba(173, 216, 230, 0.8));
left: ${Math.random() * 100}%;
top: -10px;
animation: rain ${Math.random() * 2 + 1}s linear infinite;
animation-delay: ${Math.random() * 2}s;
`;
rainOverlay.appendChild(raindrop);
}
// Add rain animation keyframes
if (!document.querySelector('#rain-style')) {
const style = document.createElement('style');
style.id = 'rain-style';
style.textContent = `
@keyframes rain {
to { transform: translateY(100vh); }
}
`;
document.head.appendChild(style);
}
}
}
// Performance monitoring
function monitorPerformance() {
let lastFrameTime = performance.now();
let frameCount = 0;
let fps = 0;
function updateFPS() {
const now = performance.now();
frameCount++;
if (now - lastFrameTime >= 1000) {
fps = Math.round((frameCount * 1000) / (now - lastFrameTime));
frameCount = 0;
lastFrameTime = now;
// Update FPS display if needed
console.log(`Map FPS: ${fps}`);
}
requestAnimationFrame(updateFPS);
}
requestAnimationFrame(updateFPS);
}
// Initialize everything when page loads
document.addEventListener('DOMContentLoaded', function() {
console.log('🗺️ Pet Battles World Map Loading...');
initMap();
setupBiomeHandlers();
setupKeyboardShortcuts();
setupTooltips();
monitorPerformance();
// Add weather effects periodically
setInterval(addWeatherEffects, 60000); // Every minute
addWeatherEffects(); // Initial weather
console.log('✅ Pet Battles World Map Ready!');
});
// Handle window resize
window.addEventListener('resize', function() {
// Ensure player positions remain accurate on resize
setTimeout(updateMap, 100);
});
// Handle visibility change (pause auto-refresh when tab is not visible)
document.addEventListener('visibilitychange', function() {
const checkbox = document.getElementById('autoRefresh');
if (document.hidden) {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
}
} else {
if (checkbox.checked) {
autoRefreshInterval = setInterval(refreshMap, 5000);
}
}
});
// Add right-click context menu for players
document.addEventListener('contextmenu', function(e) {
if (e.target.classList.contains('player')) {
e.preventDefault();
const contextMenu = document.createElement('div');
contextMenu.style.cssText = `
position: fixed;
left: ${e.clientX}px;
top: ${e.clientY}px;
background: white;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
z-index: 1002;
`;
contextMenu.innerHTML = `
<div style="cursor: pointer; padding: 5px;" onclick="alert('Feature coming soon!')">Send Message</div>
<div style="cursor: pointer; padding: 5px;" onclick="alert('Feature coming soon!')">Challenge to Battle</div>
<div style="cursor: pointer; padding: 5px;" onclick="alert('Feature coming soon!')">View Full Profile</div>
`;
document.body.appendChild(contextMenu);
// Remove context menu when clicking elsewhere
setTimeout(() => {
document.addEventListener('click', function removeMenu() {
contextMenu.remove();
document.removeEventListener('click', removeMenu);
});
}, 100);
}
});
</script>
</body>
</html>