fix: crash on back button, disconnected player must die and get replaced by AI

This commit is contained in:
catarrh committed 2026-08-01 03:19:57 +03:00
1 parent 0995578092
commit 6fad11469d
2 files changed
+62 -2

No files matched your search

+1
View File
@@ -135,6 +135,7 @@ export const PANEL_TITLE_FONT_SIZE = 16;
export const PANEL_BG_COLOR = '#666'; export const PANEL_BG_COLOR = '#666';
export const PANEL_BORDER_COLOR = '#333'; export const PANEL_BORDER_COLOR = '#333';
export const NETWORK_DISCONNECT_TIMEOUT = 15000;
export const STARTING_SCORE = 100; export const STARTING_SCORE = 100;
export const SCORE_PER_KILL = 20; export const SCORE_PER_KILL = 20;
export const SCORE_FOR_WIN = 100; export const SCORE_FOR_WIN = 100;
+61 -2
View File
@@ -1,5 +1,5 @@
import {AI_TYPES} from './ai.js?v=26'; import {AI_TYPES} from './ai.js?v=26';
import {DEATH_SPECS, EXPLOSION_SHAKE_REDUCTION_FACTOR, H, MAX_EXPLOSION_SHAKE_FACTOR, MAX_WIND, PARTICLE_AMOUNT, PARTICLE_FADE_AMOUNT, PARTICLE_MAX_POWER_FACTOR, PARTICLE_MIN_LIFETIME, PARTICLE_MIN_POWER_FACTOR, PARTICLE_POWER_REDUCTION_FACTOR, PARTICLE_TIME_FACTOR, PARTICLE_WIND_REDUCTION_FACTOR, PLAYER_ANGLE_FAST_INCREMENT, PLAYER_ANGLE_INCREMENT, PLAYER_ANGLE_TICK_SOUND_INTERVAL, PLAYER_COLORS, PLAYER_ENERGY_POWER_MULTIPLIER, PLAYER_EXPLOSION_PARTICLE_POWER, PLAYER_FALL_DAMAGE_FACTOR, PLAYER_FALL_DAMAGE_HEIGHT, PLAYER_INITIAL_POWER, PLAYER_MAX_ENERGY, PLAYER_POWER_FAST_INCREMENT, PLAYER_POWER_INCREMENT, PLAYER_POWER_TICK_SOUND_INTERVAL, PLAYER_STARTING_TOOLS, PLAYER_STARTING_WEAPONS, PLAYER_TANK_BOUNDING_RADIUS, PLAYER_TANK_Y_FOOTPRINT, PROJECTILE_POWER_REDUCTION_FACTOR, PROJECTILE_WIND_REDUCTION_FACTOR, SHIELD_TYPES, TRAJECTORY_FADE_SPEED, TRAJECTORY_FLOAT_SPEED, W, WEAPON_TYPES, Z, STARTING_SCORE, SCORE_PER_KILL, SCORE_FOR_WIN, MARKET_ITEMS, NAPALM_SPAWN_RATE, FIRE_DURATION, FIRE_DAMAGE, MAX_PLAYERS} from './constants.js?v=26'; import {DEATH_SPECS, EXPLOSION_SHAKE_REDUCTION_FACTOR, H, MAX_EXPLOSION_SHAKE_FACTOR, MAX_WIND, NETWORK_DISCONNECT_TIMEOUT, PARTICLE_AMOUNT, PARTICLE_FADE_AMOUNT, PARTICLE_MAX_POWER_FACTOR, PARTICLE_MIN_LIFETIME, PARTICLE_MIN_POWER_FACTOR, PARTICLE_POWER_REDUCTION_FACTOR, PARTICLE_TIME_FACTOR, PARTICLE_WIND_REDUCTION_FACTOR, PLAYER_ANGLE_FAST_INCREMENT, PLAYER_ANGLE_INCREMENT, PLAYER_ANGLE_TICK_SOUND_INTERVAL, PLAYER_COLORS, PLAYER_ENERGY_POWER_MULTIPLIER, PLAYER_EXPLOSION_PARTICLE_POWER, PLAYER_FALL_DAMAGE_FACTOR, PLAYER_FALL_DAMAGE_HEIGHT, PLAYER_INITIAL_POWER, PLAYER_MAX_ENERGY, PLAYER_POWER_FAST_INCREMENT, PLAYER_POWER_INCREMENT, PLAYER_POWER_TICK_SOUND_INTERVAL, PLAYER_STARTING_TOOLS, PLAYER_STARTING_WEAPONS, PLAYER_TANK_BOUNDING_RADIUS, PLAYER_TANK_Y_FOOTPRINT, PROJECTILE_POWER_REDUCTION_FACTOR, PROJECTILE_WIND_REDUCTION_FACTOR, SHIELD_TYPES, TRAJECTORY_FADE_SPEED, TRAJECTORY_FLOAT_SPEED, W, WEAPON_TYPES, Z, STARTING_SCORE, SCORE_PER_KILL, SCORE_FOR_WIN, MARKET_ITEMS, NAPALM_SPAWN_RATE, FIRE_DURATION, FIRE_DAMAGE, MAX_PLAYERS} from './constants.js?v=26';
import {createCanvas, drawLine, drawRect, drawSemiCircle, drawText, loop, plot, strokeCircle} from './gfx.js?v=26'; import {createCanvas, drawLine, drawRect, drawSemiCircle, drawText, loop, plot, strokeCircle} from './gfx.js?v=26';
import {afterKeyDelay, key, initClickCanvas, popClick, getPointer, clearKeys} from './input.js?v=26'; import {afterKeyDelay, key, initClickCanvas, popClick, getPointer, clearKeys} from './input.js?v=26';
import {clamp, deg2rad, distance, parable, random, randomInt, vec, wrap} from './math.js?v=26'; import {clamp, deg2rad, distance, parable, random, randomInt, vec, wrap} from './math.js?v=26';
@@ -647,6 +647,9 @@ const NOOP_OSC = {frequency: {setValueAtTime() {}}, stop() {}};
let lastBroadcastState = null; let lastBroadcastState = null;
let lastRemoteCommandAt = 0; let lastRemoteCommandAt = 0;
let pendingRoster = null; let pendingRoster = null;
let connectedClientIds = new Set();
let disconnectedPlayerTimers = {};
let disconnectedPlayerIds = new Set();
function applyRoster(msg) { function applyRoster(msg) {
const current = new Map(netLobby.players.map(p => [p.id, p])); const current = new Map(netLobby.players.map(p => [p.id, p]));
@@ -658,12 +661,40 @@ function applyRoster(msg) {
broadcastLobby(); broadcastLobby();
} }
function handlePlayerDisconnect(playerId) {
const player = players.find(p => p.id === playerId && !p.dead);
if (!player) return;
const {x, y, c} = player;
const explosionSpec = sample(DEATH_SPECS);
const explosionType = EXPLOSION_TYPES[explosionSpec.type];
explosions.push(explosionType.create(explosionSpec, x, y));
createParticles(x, y, PLAYER_EXPLOSION_PARTICLE_POWER, c);
player.dead = true;
player.deaths++;
player.deathOrder = typeof deathOrderCounter !== 'undefined' ? deathOrderCounter++ : 0;
disconnectedPlayerIds.add(playerId);
if (state === 'aim' && players[currentPlayer].id === playerId) {
state = 'explosions';
}
broadcastWorld();
delete disconnectedPlayerTimers[playerId];
}
function myPlayer() { function myPlayer() {
if (!networkMode) return players.find(p => !p.ai); if (!networkMode) return players.find(p => !p.ai);
return players.find(p => p.id === netPlayerId); return players.find(p => p.id === netPlayerId);
} }
function endNetworkSession() { function endNetworkSession() {
for (const id of Object.keys(disconnectedPlayerTimers)) {
clearTimeout(disconnectedPlayerTimers[id]);
}
disconnectedPlayerTimers = {};
connectedClientIds.clear();
disconnectedPlayerIds.clear();
netDisconnect(); netDisconnect();
networkMode = false; networkMode = false;
netPlayerId = null; netPlayerId = null;
@@ -676,6 +707,28 @@ function endNetworkSession() {
netOnMessage((msg) => { netOnMessage((msg) => {
if (msg.type === MSG.ROSTER) { if (msg.type === MSG.ROSTER) {
if (netIsHost()) { if (netIsHost()) {
const newIds = new Set(msg.players.map(p => p.id));
for (const id of connectedClientIds) {
if (!newIds.has(id) && !disconnectedPlayerTimers[id]) {
const player = players.find(p => p.id === id && !p.ai && !p.dead);
if (player && state !== 'net-lobby' && state !== 'market') {
disconnectedPlayerTimers[id] = setTimeout(() => {
handlePlayerDisconnect(id);
}, NETWORK_DISCONNECT_TIMEOUT);
}
}
}
for (const id of Object.keys(disconnectedPlayerTimers)) {
if (newIds.has(id)) {
clearTimeout(disconnectedPlayerTimers[id]);
delete disconnectedPlayerTimers[id];
}
}
connectedClientIds = newIds;
if (netLobby) applyRoster(msg); if (netLobby) applyRoster(msg);
else pendingRoster = msg; else pendingRoster = msg;
} }
@@ -899,6 +952,7 @@ function initNetworkPlayers() {
for (let i = 0; i < total; i++) { for (let i = 0; i < total; i++) {
const [color, borderColor] = PLAYER_COLORS[i]; const [color, borderColor] = PLAYER_COLORS[i];
const human = i < roster.length ? roster[i] : null; const human = i < roster.length ? roster[i] : null;
const wasDisconnected = human && disconnectedPlayerIds.has(human.id);
players.push({ players.push({
name: human ? human.name : `AI ${i+1}`, name: human ? human.name : `AI ${i+1}`,
dead: false, dead: false,
@@ -911,7 +965,7 @@ function initNetworkPlayers() {
currentWeapon: 0, currentWeapon: 0,
energy: PLAYER_MAX_ENERGY, energy: PLAYER_MAX_ENERGY,
shield: null, shield: null,
ai: human ? undefined : sample(Object.keys(AI_TYPES)), ai: (human && !wasDisconnected) ? undefined : sample(Object.keys(AI_TYPES)),
parachute: null, parachute: null,
fallHeight: 0, fallHeight: 0,
score: STARTING_SCORE, score: STARTING_SCORE,
@@ -937,6 +991,7 @@ function startNetworkGame() {
totalRounds = netLobby.config.rounds; totalRounds = netLobby.config.rounds;
selectedTerrain = netLobby.config.terrain; selectedTerrain = netLobby.config.terrain;
menuState = null; menuState = null;
disconnectedPlayerIds.clear();
initNetworkPlayers(); initNetworkPlayers();
broadcastLobby(); broadcastLobby();
broadcastWorld(); broadcastWorld();
@@ -1013,6 +1068,10 @@ function updateNetMenu() {
state = 'start-menu'; state = 'start-menu';
netMenuState = null; netMenuState = null;
netError = null; netError = null;
menuState = {
selected: 0,
values: [Math.max(0, selectedPlayers - 3), Math.max(0, totalRounds - 1), selectedTerrain === null ? 0 : selectedTerrain === 'mountain' ? 1 : selectedTerrain === 'sand' ? 2 : 0, tracerMode ? 0 : 1, 0],
};
}; };
const enterBrowse = () => { const enterBrowse = () => {