import { Network, ServerMessage } from './network.js'; import { ClientMessage } from './protocol.js'; import { startBattle } from './battle.js'; import { unlockAudio } from './audio.js'; import { getLang } from '../i18n.js'; const container = document.getElementById('lobby'); // Статичные подписи UI лобби переводим; данные из сервера (ники, room id, // текст ошибок) остаются как есть — они генерируются на сервере только на русском. const STR = { ru: { connecting: 'подключение...', join: 'войти', createCoop: 'создать комнату (coop)', createPvp: 'создать комнату (pvp)', refresh: 'обновить список', empty: 'пока пусто — создай комнату', ready: 'готов', readySuffix: ' — готов', waitingForSecond: 'жду второго игрока (1/2) — открой комнату во второй вкладке/другим человеком', soloHint: 'можно начать соло, второй игрок сможет подключиться позже', room: 'комната', mode: 'режим:', leave: 'выйти в лобби', }, en: { connecting: 'connecting...', join: 'join', createCoop: 'create room (coop)', createPvp: 'create room (pvp)', refresh: 'refresh list', empty: 'nothing here yet — create a room', ready: 'ready', readySuffix: ' — ready', waitingForSecond: 'waiting for a second player (1/2) — open the room in another tab or have someone else join', soloHint: 'you can start solo, a second player can join later', room: 'room', mode: 'mode:', leave: 'leave to lobby', }, }; function t() { return STR[getLang()]; } let net; let myPlayerId = null; let rooms = []; let currentRoom = null; let errorMessage = ''; let connected = false; let nickname = 'cacto-' + Math.floor(Math.random() * 1000); // Ники приходят от других игроков (сервер их только режет по длине, не // экранирует) и попадают сюда через innerHTML — без экранирования это XSS. function escapeHtml(str) { return String(str).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', }[c])); } function roomRow(room) { const full = room.players >= room.max_players; return `
  • ${room.name} ${room.mode} ${room.players}/${room.max_players} ${room.state}
  • `; } function errorBanner() { return errorMessage ? `

    ! ${errorMessage}

    ` : ''; } function renderLobbyView() { const dis = connected ? '' : 'disabled'; container.innerHTML = `
    ${errorBanner()} ${connected ? '' : `

    ${t().connecting}

    `}
    `; } function renderRoomView() { const full = currentRoom.players.length >= 2; let statusLine = ''; let readyBtn = ``; if (currentRoom.mode === 'pvp' && !full) { statusLine = `

    ${t().waitingForSecond}

    `; readyBtn = ``; } else if (currentRoom.mode === 'coop' && !full) { statusLine = `

    ${t().soloHint}

    `; } container.innerHTML = `
    ${errorBanner()}

    ${t().room} ${currentRoom.name}

    ${t().mode} ${currentRoom.mode}

    ${statusLine} ${readyBtn}
    `; } function render() { if (currentRoom) { renderRoomView(); } else { renderLobbyView(); } } // Вызывается из battle.js по кнопкам экрана победы/поражения. export function returnToLobbyContainer() { document.getElementById('tanks-canvas-container').style.display = 'none'; container.style.display = ''; render(); } export function leaveRoomAndReturn() { currentRoom = null; net.leaveRoom(); returnToLobbyContainer(); } container?.addEventListener('click', (e) => { const btn = e.target.closest('[data-action]'); if (!btn) return; errorMessage = ''; switch (btn.dataset.action) { case 'create': net.createRoom(btn.dataset.mode); break; case 'join': net.joinRoom(btn.dataset.roomId); break; case 'ready': unlockAudio(); net.send(ClientMessage.GAME_READY); break; case 'leave': currentRoom = null; net.leaveRoom(); render(); break; case 'refresh': net.listRooms(); break; } }); container?.addEventListener('change', (e) => { if (e.target.id === 'nickname-input') { nickname = e.target.value.trim() || nickname; } }); export function initLobby() { net = new Network(); net.on('_open', () => net.hello(nickname)); net.on(ServerMessage.HELLO_ACK, (payload) => { connected = true; myPlayerId = payload.player_id; net.listRooms(); }); net.on('_close', () => { connected = false; render(); }); net.on(ServerMessage.LOBBY_ROOMS, (payload) => { rooms = payload.rooms; render(); }); net.on(ServerMessage.LOBBY_ROOM_JOINED, (payload) => { currentRoom = payload; render(); }); net.on(ServerMessage.LOBBY_ROOM_UPDATED, (payload) => { if (currentRoom) { currentRoom.players = payload.players; render(); } }); net.on(ServerMessage.GAME_START, (payload) => { startBattle(net, payload, myPlayerId); }); net.on(ServerMessage.ERROR, (payload) => { console.error('[gameserver] error:', payload); errorMessage = payload.message; render(); }); window.addEventListener('cacto:langchange', render); render(); return net; }