class WorldMap { constructor(canvasId) { this.canvas = document.getElementById(canvasId); this.ctx = this.canvas.getContext('2d'); this.hoveredLocation = null; this.availableLocations = []; this.mapImage = null; this.mapLoaded = false; this.initCanvas(); this.setupEventListeners(); this.loadWorldMapImage(); } initCanvas() { this.canvas.width = this.canvas.offsetWidth; this.canvas.height = this.canvas.offsetHeight; } loadWorldMapImage() { // Загружаем реальную карту мира this.mapImage = new Image(); this.mapImage.crossOrigin = "anonymous"; // Используем бесплатную карту мира с Wikimedia Commons this.mapImage.src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Equirectangular_projection_SW.jpg/1280px-Equirectangular_projection_SW.jpg'; this.mapImage.onload = () => { this.mapLoaded = true; this.draw(); }; this.mapImage.onerror = () => { console.warn('Не удалось загрузить карту, рисуем базовую версию'); this.mapLoaded = false; this.draw(); }; } setupEventListeners() { this.canvas.addEventListener('mousemove', (e) => this.onMouseMove(e)); this.canvas.addEventListener('click', (e) => this.onClick(e)); window.addEventListener('resize', () => { this.initCanvas(); this.draw(); }); } onMouseMove(e) { const rect = this.canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; this.hoveredLocation = this.getLocationAt(x, y); this.canvas.style.cursor = this.hoveredLocation ? 'pointer' : 'default'; this.draw(); } onClick(e) { const rect = this.canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const location = this.getLocationAt(x, y); if (location) { game.onLocationClick(location); } } getLocationAt(x, y) { for (const loc of allPoints) { const coords = this.getLocationCoords(loc); const dx = x - coords.x; const dy = y - coords.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < 20) { return loc; } } return null; } drawWorldMap() { const ctx = this.ctx; if (this.mapLoaded && this.mapImage) { // Рисуем загруженную карту мира ctx.drawImage(this.mapImage, 0, 0, this.canvas.width, this.canvas.height); // Добавляем легкое затемнение для лучшей видимости маркеров ctx.fillStyle = 'rgba(255, 255, 255, 0.1)'; ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); } else { // Запасной вариант - простая карта ctx.fillStyle = '#a8dadc'; ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); // Рисуем простые континенты this.drawSimpleContinents(); } } drawSimpleContinents() { const ctx = this.ctx; ctx.fillStyle = '#90be6d'; // Упрощенные континенты // Европа ctx.fillRect(480, 170, 100, 120); // Азия ctx.fillRect(650, 150, 350, 280); // Африка ctx.fillRect(480, 280, 120, 260); // Северная Америка ctx.fillRect(100, 180, 180, 180); // Южная Америка ctx.fillRect(240, 360, 90, 200); // Австралия ctx.fillRect(900, 480, 120, 100); } draw() { this.drawWorldMap(); this.drawRoutes(); this.drawLocations(); this.drawPlayers(); } drawRoutes() { const ctx = this.ctx; ctx.strokeStyle = 'rgba(0, 0, 0, 0.15)'; ctx.lineWidth = 1.5; ctx.setLineDash([3, 3]); routes.forEach(route => { const from = allPoints.find(l => l.id === route.from); const to = allPoints.find(l => l.id === route.to); if (from && to) { const fromCoords = this.getLocationCoords(from); const toCoords = this.getLocationCoords(to); ctx.beginPath(); ctx.moveTo(fromCoords.x, fromCoords.y); ctx.lineTo(toCoords.x, toCoords.y); ctx.stroke(); } }); ctx.setLineDash([]); } getLocationCoords(loc) { // Пересчитываем координаты на основе текущего размера canvas const x = (loc.lon + 180) * (this.canvas.width / 360); const y = (90 - loc.lat) * (this.canvas.height / 180); return { x, y }; } drawLocations() { const ctx = this.ctx; // Получаем активные задания текущего игрока const activeMissionDestinations = []; if (game && game.getCurrentPlayer() && game.getCurrentPlayer().missions) { game.getCurrentPlayer().missions.forEach(mission => { activeMissionDestinations.push(mission.to); }); } allPoints.forEach(loc => { const coords = this.getLocationCoords(loc); const isHovered = this.hoveredLocation && this.hoveredLocation.id === loc.id; const isAvailable = this.availableLocations.includes(loc.id); const isWaypoint = !loc.type; // waypoints не имеют type const isMissionDestination = activeMissionDestinations.includes(loc.id); // Подсветка точек назначения активных заданий if (isMissionDestination) { ctx.shadowColor = 'rgba(46, 204, 113, 0.8)'; ctx.shadowBlur = 25; ctx.beginPath(); ctx.arc(coords.x, coords.y, 30, 0, Math.PI * 2); ctx.fillStyle = 'rgba(46, 204, 113, 0.2)'; ctx.fill(); ctx.shadowBlur = 0; // Анимированный круг вокруг точки назначения ctx.strokeStyle = 'rgba(46, 204, 113, 0.6)'; ctx.lineWidth = 3; ctx.stroke(); } // Подсветка доступных локаций (желтая) if (isAvailable) { ctx.shadowColor = 'rgba(255, 215, 0, 0.8)'; ctx.shadowBlur = 20; ctx.beginPath(); ctx.arc(coords.x, coords.y, 25, 0, Math.PI * 2); ctx.fillStyle = 'rgba(255, 215, 0, 0.3)'; ctx.fill(); ctx.shadowBlur = 0; } // Размер точки const radius = isWaypoint ? (isHovered ? 8 : 5) : (isHovered ? 12 : 10); // Круг локации ctx.beginPath(); ctx.arc(coords.x, coords.y, radius, 0, Math.PI * 2); if (isWaypoint) { // Путевые точки - маленькие белые точки ctx.fillStyle = isAvailable ? '#ffd700' : '#ffffff'; ctx.fill(); ctx.strokeStyle = isAvailable ? '#ff8c00' : 'rgba(0, 0, 0, 0.3)'; ctx.lineWidth = 2; ctx.stroke(); } else { // Города и порты - большие цветные if (isMissionDestination) { // Точки назначения - зеленые ctx.fillStyle = '#27ae60'; } else if (loc.type === 'port') { ctx.fillStyle = '#457b9d'; } else if (loc.type === 'city') { ctx.fillStyle = '#e63946'; } else { ctx.fillStyle = '#f77f00'; } ctx.fill(); ctx.strokeStyle = 'white'; ctx.lineWidth = 3; ctx.stroke(); } // Название при наведении if (isHovered) { ctx.font = isWaypoint ? '12px Arial' : 'bold 14px Arial'; ctx.fillStyle = 'white'; ctx.strokeStyle = 'black'; ctx.lineWidth = 3; const textWidth = ctx.measureText(loc.name).width; const yOffset = isWaypoint ? -15 : -20; ctx.strokeText(loc.name, coords.x - textWidth / 2, coords.y + yOffset); ctx.fillText(loc.name, coords.x - textWidth / 2, coords.y + yOffset); } }); } drawPlayers() { if (!game || !game.players) return; const ctx = this.ctx; game.players.forEach((player, index) => { let coords; let offset = index * 25 - (game.players.length - 1) * 12; // Если есть временная позиция анимации, используем её if (player._tempAnimationPos) { coords = player._tempAnimationPos; } else { const loc = allPoints.find(l => l.id === player.location); if (!loc) return; coords = this.getLocationCoords(loc); } // Рисуем кораблик игрока ctx.font = 'bold 28px Arial'; // Тень для корабля ctx.shadowColor = 'rgba(0, 0, 0, 0.3)'; ctx.shadowBlur = 5; ctx.fillText(playerShips[index], coords.x + offset - 14, coords.y + 38); ctx.shadowBlur = 0; // Рамка вокруг текущего игрока if (game.currentPlayerIndex === index) { ctx.strokeStyle = playerColors[index]; ctx.lineWidth = 3; ctx.strokeRect(coords.x + offset - 18, coords.y + 15, 36, 36); } // Индикатор активных заданий (не показываем во время анимации) if (!player._tempAnimationPos && player.missions && player.missions.length > 0) { ctx.fillStyle = '#f39c12'; ctx.beginPath(); ctx.arc(coords.x + offset + 12, coords.y + 20, 6, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = 'white'; ctx.lineWidth = 2; ctx.stroke(); // Количество грузов ctx.fillStyle = 'white'; ctx.font = 'bold 10px Arial'; ctx.fillText(player.missions.length, coords.x + offset + 9, coords.y + 24); } }); } setAvailableLocations(locationIds) { this.availableLocations = locationIds; this.draw(); } clearAvailableLocations() { this.availableLocations = []; this.draw(); } }