import { Network, ServerMessage } from './network.js';
import { ClientMessage } from './protocol.js';
import { startBattle } from './battle.js';
import { unlockAudio } from './audio.js';
const container = document.getElementById('lobby');
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 ? '' : '
подключение...
'}
nickname
создать комнату (coop)
создать комнату (pvp)
обновить список
${rooms.length === 0 ? 'пока пусто — создай комнату ' : rooms.map(roomRow).join('')}
`;
}
function renderRoomView() {
const full = currentRoom.players.length >= 2;
let statusLine = '';
let readyBtn = 'готов ';
if (currentRoom.mode === 'pvp' && !full) {
statusLine = 'жду второго игрока (1/2) — открой комнату во второй вкладке/другим человеком
';
readyBtn = 'готов ';
} else if (currentRoom.mode === 'coop' && !full) {
statusLine = 'можно начать соло, второй игрок сможет подключиться позже
';
}
container.innerHTML = `
${errorBanner()}
room ${currentRoom.name}
режим: ${currentRoom.mode}
${currentRoom.players.map((p) => `${escapeHtml(p.nickname)}${p.ready ? ' — готов' : ''} `).join('')}
${statusLine}
${readyBtn}
выйти в лобби
`;
}
function render() {
if (currentRoom) {
renderRoomView();
} else {
renderLobbyView();
}
}
// Вызывается из battle.js по кнопкам экрана победы/поражения.
export function returnToLobbyContainer() {
document.getElementById('battlecity-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();
});
render();
return net;
}