Supprime les mini-jeux (survival/teams), corrige tous les bugs identifiés
- Supprime DeathZone.cs, ZoneCapture.cs, ArenaZoneBuilder.cs - ArenaRoom.js : mode race uniquement, fix _checkRoundEndCondition, fix _getActiveCount - GameState.js : supprime team (Player) et deathZoneY/teamScoreRed/teamScoreBlue (GameState) - NetworkSchema.cs : aligne sur le nouveau schéma serveur (supprime team, indices corrigés) - NetworkManager.cs : supprime OnDeathZoneYChanged/SendDeathZoneHit/SendInZone, OnRoundStart passe totalRounds - GameManager.cs : subscriptions OnEnable→Start/OnDestroy, fix Lobby (player visible si connecté), HandleRoundStart(totalRounds) - GameHUD.cs : supprime blocs survival/teams, ajoute SetTotalRounds, supprime dead code - PlayerController.cs : cache Rigidbody, fix OnCollisionStay gel (supprime else), SetSpawnPosition - CheckpointSystem.cs : flash le prochain checkpoint actif, supprime FinishFlash vide - LobbyUI.cs : CancelInvoke sur connexion, appelle SetSpawnPosition Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,11 @@
|
||||
const { Room } = require("@colyseus/core");
|
||||
const { GameState, Player } = require("../schema/GameState");
|
||||
|
||||
const ROUND_MODES = ["race", "survival", "teams"];
|
||||
const LOBBY_TIMEOUT = 30; // seconds before auto-start
|
||||
const LOBBY_TIMEOUT = 30;
|
||||
const COUNTDOWN_DURATION = 3;
|
||||
const ROUND_END_DURATION = 5;
|
||||
const RACE_TIMEOUT = 180; // 3 min
|
||||
const SURVIVAL_START_DELAY = 20; // seconds before deathzone rises
|
||||
const SURVIVAL_RISE_RATE = 0.3; // units/sec
|
||||
const SURVIVAL_MAX_Y = 15;
|
||||
const TEAMS_DURATION = 90;
|
||||
const QUALIFY_RATIO = 0.6; // top 60% qualify in race
|
||||
const RACE_TIMEOUT = 180;
|
||||
const QUALIFY_RATIO = 0.6;
|
||||
|
||||
class ArenaRoom extends Room {
|
||||
maxClients = 20;
|
||||
@@ -20,10 +15,7 @@ class ArenaRoom extends Room {
|
||||
this.setPatchRate(16); // ~62.5 Hz
|
||||
|
||||
this._phaseTimer = null;
|
||||
this._survivalInterval = null;
|
||||
this._teamInterval = null;
|
||||
this._lobbyTimer = null;
|
||||
this._inZonePlayers = new Set(); // sessionIds currently in zone
|
||||
|
||||
console.log(`[ArenaRoom] Room ${this.roomId} created`);
|
||||
|
||||
@@ -55,35 +47,17 @@ class ArenaRoom extends Room {
|
||||
});
|
||||
|
||||
this.onMessage("checkpointReached", (client, data) => {
|
||||
if (this.state.phase !== "playing" || this.state.gameMode !== "race") return;
|
||||
if (this.state.phase !== "playing") return;
|
||||
const player = this.state.players.get(client.sessionId);
|
||||
if (!player || player.isEliminated || player.isQualified) return;
|
||||
const expected = player.checkpointIndex;
|
||||
if (data.index !== expected) return; // must hit in order
|
||||
if (data.index !== expected) return;
|
||||
player.checkpointIndex = data.index + 1;
|
||||
// The last checkpoint (index 4 = finish) qualifies the player
|
||||
// CheckpointSystem sends index after increment, so finish = totalCheckpoints
|
||||
const TOTAL_CHECKPOINTS = 5;
|
||||
if (player.checkpointIndex >= TOTAL_CHECKPOINTS) {
|
||||
this._qualifyPlayer(client.sessionId, "finish");
|
||||
}
|
||||
});
|
||||
|
||||
this.onMessage("deathZoneHit", (client) => {
|
||||
if (this.state.phase !== "playing" || this.state.gameMode !== "survival") return;
|
||||
this._eliminatePlayer(client.sessionId, "deathzone");
|
||||
});
|
||||
|
||||
this.onMessage("inZone", (client, data) => {
|
||||
if (this.state.phase !== "playing" || this.state.gameMode !== "teams") return;
|
||||
const player = this.state.players.get(client.sessionId);
|
||||
if (!player || player.isEliminated) return;
|
||||
if (data.inZone) {
|
||||
this._inZonePlayers.add(client.sessionId);
|
||||
} else {
|
||||
this._inZonePlayers.delete(client.sessionId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onJoin(client, options) {
|
||||
@@ -101,7 +75,6 @@ class ArenaRoom extends Room {
|
||||
this.state.players.set(client.sessionId, player);
|
||||
this._updatePlayersAlive();
|
||||
|
||||
// Auto-start lobby timer on first player
|
||||
if (this.state.players.size === 1 && this.state.phase === "lobby") {
|
||||
this._startLobbyTimer();
|
||||
}
|
||||
@@ -109,7 +82,6 @@ class ArenaRoom extends Room {
|
||||
|
||||
onLeave(client, consented) {
|
||||
console.log(`[ArenaRoom] ${client.sessionId} left`);
|
||||
this._inZonePlayers.delete(client.sessionId);
|
||||
this.state.players.delete(client.sessionId);
|
||||
this._updatePlayersAlive();
|
||||
if (this.state.phase === "playing") {
|
||||
@@ -159,29 +131,17 @@ class ArenaRoom extends Room {
|
||||
}
|
||||
|
||||
_startPlaying() {
|
||||
const modeIndex = (this.state.roundNumber - 1) % ROUND_MODES.length;
|
||||
this.state.gameMode = ROUND_MODES[modeIndex];
|
||||
this.state.gameMode = "race";
|
||||
this.state.phase = "playing";
|
||||
this.state.countdown = 0;
|
||||
|
||||
// Reset player state for new round
|
||||
let teamToggle = 0;
|
||||
this.state.players.forEach((p, id) => {
|
||||
this.state.players.forEach((p) => {
|
||||
p.isEliminated = false;
|
||||
p.isQualified = false;
|
||||
p.isReady = false;
|
||||
p.checkpointIndex = 0;
|
||||
if (this.state.gameMode === "teams") {
|
||||
p.team = (teamToggle++ % 2 === 0) ? 1 : 2;
|
||||
} else {
|
||||
p.team = 0;
|
||||
}
|
||||
});
|
||||
|
||||
this.state.deathZoneY = -50;
|
||||
this.state.teamScoreRed = 0;
|
||||
this.state.teamScoreBlue = 0;
|
||||
this._inZonePlayers.clear();
|
||||
this._updatePlayersAlive();
|
||||
|
||||
this.broadcast("roundStart", {
|
||||
@@ -190,16 +150,9 @@ class ArenaRoom extends Room {
|
||||
totalRounds: this.state.totalRounds,
|
||||
});
|
||||
|
||||
console.log(`[ArenaRoom] Round ${this.state.roundNumber} started (mode: ${this.state.gameMode})`);
|
||||
console.log(`[ArenaRoom] Round ${this.state.roundNumber} started (race)`);
|
||||
|
||||
if (this.state.gameMode === "race") {
|
||||
this._phaseTimer = setTimeout(() => this._endRaceTimeout(), RACE_TIMEOUT * 1000);
|
||||
} else if (this.state.gameMode === "survival") {
|
||||
this._phaseTimer = setTimeout(() => this._startSurvivalRise(), SURVIVAL_START_DELAY * 1000);
|
||||
} else if (this.state.gameMode === "teams") {
|
||||
this._startTeamsScoring();
|
||||
this._phaseTimer = setTimeout(() => this._endTeamsRound(), TEAMS_DURATION * 1000);
|
||||
}
|
||||
this._phaseTimer = setTimeout(() => this._endRaceTimeout(), RACE_TIMEOUT * 1000);
|
||||
}
|
||||
|
||||
_endRound() {
|
||||
@@ -209,7 +162,6 @@ class ArenaRoom extends Room {
|
||||
this.broadcast("roundEnd", { round: this.state.roundNumber });
|
||||
console.log(`[ArenaRoom] Round ${this.state.roundNumber} ended`);
|
||||
|
||||
// Check if all rounds done
|
||||
if (this.state.roundNumber >= this.state.totalRounds) {
|
||||
this._phaseTimer = setTimeout(() => this._endGame(), ROUND_END_DURATION * 1000);
|
||||
} else {
|
||||
@@ -220,13 +172,10 @@ class ArenaRoom extends Room {
|
||||
_nextRound() {
|
||||
this.state.roundNumber += 1;
|
||||
this.state.phase = "lobby";
|
||||
this.state.playersAlive = 0;
|
||||
this.state.players.forEach((p) => {
|
||||
if (!p.isEliminated) {
|
||||
p.isReady = false;
|
||||
const spawn = this._findSpawnPosition();
|
||||
p.x = spawn.x; p.y = spawn.y; p.z = spawn.z;
|
||||
}
|
||||
p.isReady = false;
|
||||
const spawn = this._findSpawnPosition();
|
||||
p.x = spawn.x; p.y = spawn.y; p.z = spawn.z;
|
||||
});
|
||||
this._updatePlayersAlive();
|
||||
this._lobbyTimer = null;
|
||||
@@ -236,7 +185,6 @@ class ArenaRoom extends Room {
|
||||
|
||||
_endGame() {
|
||||
this.state.phase = "gameEnd";
|
||||
// Find winner: last qualified player, or player with most checkpoints
|
||||
let winner = "";
|
||||
let best = -1;
|
||||
this.state.players.forEach((p) => {
|
||||
@@ -251,7 +199,6 @@ class ArenaRoom extends Room {
|
||||
// ─── Race mode ──────────────────────────────────────────────────────
|
||||
|
||||
_endRaceTimeout() {
|
||||
// Eliminate anyone who hasn't qualified
|
||||
this.state.players.forEach((p, id) => {
|
||||
if (!p.isQualified && !p.isEliminated) {
|
||||
this._eliminatePlayer(id, "timeout");
|
||||
@@ -260,49 +207,6 @@ class ArenaRoom extends Room {
|
||||
this._endRound();
|
||||
}
|
||||
|
||||
// ─── Survival mode ──────────────────────────────────────────────────
|
||||
|
||||
_startSurvivalRise() {
|
||||
console.log(`[ArenaRoom] DeathZone starts rising`);
|
||||
this._survivalInterval = setInterval(() => {
|
||||
this.state.deathZoneY += SURVIVAL_RISE_RATE * (16 / 1000);
|
||||
if (this.state.deathZoneY > SURVIVAL_MAX_Y) {
|
||||
this.state.deathZoneY = SURVIVAL_MAX_Y;
|
||||
}
|
||||
}, 16);
|
||||
}
|
||||
|
||||
// ─── Teams mode ─────────────────────────────────────────────────────
|
||||
|
||||
_startTeamsScoring() {
|
||||
this._teamInterval = setInterval(() => {
|
||||
let redInZone = 0;
|
||||
let blueInZone = 0;
|
||||
this._inZonePlayers.forEach((id) => {
|
||||
const p = this.state.players.get(id);
|
||||
if (!p || p.isEliminated) return;
|
||||
if (p.team === 1) redInZone++;
|
||||
else if (p.team === 2) blueInZone++;
|
||||
});
|
||||
if (redInZone > blueInZone) this.state.teamScoreRed = Math.min(this.state.teamScoreRed + 1, 32767);
|
||||
else if (blueInZone > redInZone) this.state.teamScoreBlue = Math.min(this.state.teamScoreBlue + 1, 32767);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
_endTeamsRound() {
|
||||
// Eliminate losing team
|
||||
const redWins = this.state.teamScoreRed >= this.state.teamScoreBlue;
|
||||
const losingTeam = redWins ? 2 : 1;
|
||||
this.state.players.forEach((p, id) => {
|
||||
if (p.team === losingTeam && !p.isEliminated) {
|
||||
this._eliminatePlayer(id, "teams_lost");
|
||||
} else if (!p.isEliminated) {
|
||||
this._qualifyPlayer(id, "teams_won");
|
||||
}
|
||||
});
|
||||
this._endRound();
|
||||
}
|
||||
|
||||
// ─── Elimination helpers ─────────────────────────────────────────────
|
||||
|
||||
_eliminatePlayer(sessionId, reason) {
|
||||
@@ -323,45 +227,23 @@ class ArenaRoom extends Room {
|
||||
this.broadcast("qualified", { sessionId, name: player.name });
|
||||
console.log(`[ArenaRoom] ${player.name} (${sessionId}) qualified: ${reason}`);
|
||||
|
||||
if (this.state.gameMode === "race") {
|
||||
const aliveCount = this._getAliveCount();
|
||||
const totalActive = this._getActiveCount();
|
||||
const qualifiedCount = this._getQualifiedCount();
|
||||
// Eliminate once qualify_ratio reached
|
||||
const toQualify = Math.ceil(totalActive * QUALIFY_RATIO);
|
||||
if (qualifiedCount >= toQualify) {
|
||||
this.state.players.forEach((p, id) => {
|
||||
if (!p.isQualified && !p.isEliminated) {
|
||||
this._eliminatePlayer(id, "too_slow");
|
||||
}
|
||||
});
|
||||
this._endRound();
|
||||
}
|
||||
} else if (this.state.gameMode === "survival") {
|
||||
// In survival: only 1 qualifies (last one), rest get eliminated by zone
|
||||
this._checkRoundEndCondition();
|
||||
const totalActive = this._getActiveCount();
|
||||
const qualifiedCount = this._getQualifiedCount();
|
||||
const toQualify = Math.ceil(totalActive * QUALIFY_RATIO);
|
||||
if (qualifiedCount >= toQualify) {
|
||||
this.state.players.forEach((p, id) => {
|
||||
if (!p.isQualified && !p.isEliminated) {
|
||||
this._eliminatePlayer(id, "too_slow");
|
||||
}
|
||||
});
|
||||
this._endRound();
|
||||
}
|
||||
}
|
||||
|
||||
_checkRoundEndCondition() {
|
||||
if (this.state.phase !== "playing") return;
|
||||
const alive = this._getAliveCount();
|
||||
const qualified = this._getQualifiedCount();
|
||||
const total = this._getActiveCount();
|
||||
|
||||
if (this.state.gameMode === "survival") {
|
||||
if (alive <= 1) {
|
||||
// Qualify the last survivor
|
||||
this.state.players.forEach((p, id) => {
|
||||
if (!p.isEliminated && !p.isQualified) {
|
||||
this._qualifyPlayer(id, "last_survivor");
|
||||
}
|
||||
});
|
||||
this._endRound();
|
||||
}
|
||||
} else if (alive === 0 || alive + qualified >= total) {
|
||||
this._endRound();
|
||||
}
|
||||
if (alive === 0) this._endRound();
|
||||
}
|
||||
|
||||
_getAliveCount() {
|
||||
@@ -377,7 +259,9 @@ class ArenaRoom extends Room {
|
||||
}
|
||||
|
||||
_getActiveCount() {
|
||||
return this.state.players.size;
|
||||
let n = 0;
|
||||
this.state.players.forEach((p) => { if (!p.isEliminated) n++; });
|
||||
return n;
|
||||
}
|
||||
|
||||
_updatePlayersAlive() {
|
||||
@@ -387,8 +271,6 @@ class ArenaRoom extends Room {
|
||||
_clearAllTimers() {
|
||||
if (this._phaseTimer) { clearTimeout(this._phaseTimer); this._phaseTimer = null; }
|
||||
if (this._lobbyTimer) { clearTimeout(this._lobbyTimer); this._lobbyTimer = null; }
|
||||
if (this._survivalInterval) { clearInterval(this._survivalInterval); this._survivalInterval = null; }
|
||||
if (this._teamInterval) { clearInterval(this._teamInterval); this._teamInterval = null; }
|
||||
}
|
||||
|
||||
// ─── Spawn helper ────────────────────────────────────────────────────
|
||||
|
||||
@@ -3,88 +3,58 @@ const { Schema, MapSchema, defineTypes } = require("@colyseus/schema");
|
||||
class Player extends Schema {
|
||||
constructor() {
|
||||
super();
|
||||
this.x = 0;
|
||||
this.y = 5;
|
||||
this.z = 0;
|
||||
this.vx = 0;
|
||||
this.vy = 0;
|
||||
this.vz = 0;
|
||||
this.rx = 0;
|
||||
this.ry = 0;
|
||||
this.rz = 0;
|
||||
this.rw = 1;
|
||||
this.x = 0; this.y = 5; this.z = 0;
|
||||
this.vx = 0; this.vy = 0; this.vz = 0;
|
||||
this.rx = 0; this.ry = 0; this.rz = 0; this.rw = 1;
|
||||
this.t = 0;
|
||||
this.name = "";
|
||||
this.colorR = 1;
|
||||
this.colorG = 1;
|
||||
this.colorB = 1;
|
||||
this.avx = 0;
|
||||
this.avy = 0;
|
||||
this.avz = 0;
|
||||
// Game state
|
||||
this.colorR = 1; this.colorG = 1; this.colorB = 1;
|
||||
this.avx = 0; this.avy = 0; this.avz = 0;
|
||||
this.isEliminated = false;
|
||||
this.isQualified = false;
|
||||
this.isReady = false;
|
||||
this.team = 0;
|
||||
this.isQualified = false;
|
||||
this.isReady = false;
|
||||
this.checkpointIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Field order must match NetworkSchema.cs [Type(N)] indices exactly
|
||||
defineTypes(Player, {
|
||||
x: "float32",
|
||||
y: "float32",
|
||||
z: "float32",
|
||||
vx: "float32",
|
||||
vy: "float32",
|
||||
vz: "float32",
|
||||
rx: "float32",
|
||||
ry: "float32",
|
||||
rz: "float32",
|
||||
rw: "float32",
|
||||
t: "float64",
|
||||
name: "string",
|
||||
colorR: "float32",
|
||||
colorG: "float32",
|
||||
colorB: "float32",
|
||||
avx: "float32",
|
||||
avy: "float32",
|
||||
avz: "float32",
|
||||
isEliminated: "boolean",
|
||||
isQualified: "boolean",
|
||||
isReady: "boolean",
|
||||
team: "int8",
|
||||
checkpointIndex: "int8",
|
||||
x: "float32", y: "float32", z: "float32", // 0-2
|
||||
vx: "float32", vy: "float32", vz: "float32", // 3-5
|
||||
rx: "float32", ry: "float32", rz: "float32", rw: "float32", // 6-9
|
||||
t: "float64", // 10
|
||||
name: "string", // 11
|
||||
colorR: "float32", colorG: "float32", colorB: "float32", // 12-14
|
||||
avx: "float32", avy: "float32", avz: "float32", // 15-17
|
||||
isEliminated: "boolean", // 18
|
||||
isQualified: "boolean", // 19
|
||||
isReady: "boolean", // 20
|
||||
checkpointIndex: "int8", // 21
|
||||
});
|
||||
|
||||
class GameState extends Schema {
|
||||
constructor() {
|
||||
super();
|
||||
this.players = new MapSchema();
|
||||
this.phase = "lobby";
|
||||
this.countdown = 0;
|
||||
this.roundNumber = 1;
|
||||
this.totalRounds = 3;
|
||||
this.players = new MapSchema();
|
||||
this.phase = "lobby";
|
||||
this.countdown = 0;
|
||||
this.roundNumber = 1;
|
||||
this.totalRounds = 3;
|
||||
this.playersAlive = 0;
|
||||
this.gameMode = "race";
|
||||
this.deathZoneY = -50;
|
||||
this.teamScoreRed = 0;
|
||||
this.teamScoreBlue = 0;
|
||||
this.winnerName = "";
|
||||
this.gameMode = "race";
|
||||
this.winnerName = "";
|
||||
}
|
||||
}
|
||||
|
||||
defineTypes(GameState, {
|
||||
players: { map: Player },
|
||||
phase: "string",
|
||||
countdown: "float32",
|
||||
roundNumber: "int8",
|
||||
totalRounds: "int8",
|
||||
playersAlive: "int8",
|
||||
gameMode: "string",
|
||||
deathZoneY: "float32",
|
||||
teamScoreRed: "int16",
|
||||
teamScoreBlue: "int16",
|
||||
winnerName: "string",
|
||||
players: { map: Player }, // 0
|
||||
phase: "string", // 1
|
||||
countdown: "float32", // 2
|
||||
roundNumber: "int8", // 3
|
||||
totalRounds: "int8", // 4
|
||||
playersAlive: "int8", // 5
|
||||
gameMode: "string", // 6
|
||||
winnerName: "string", // 7
|
||||
});
|
||||
|
||||
module.exports = { GameState, Player };
|
||||
|
||||
Reference in New Issue
Block a user