init commit

This commit is contained in:
K. Krivoruchenko 2025-11-02 16:42:25 +03:00
commit f716b05e18
7 changed files with 2766 additions and 0 deletions

320
map.js Normal file
View file

@ -0,0 +1,320 @@
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();
}
}