mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 06:12:19 +00:00
feat(nukewars): Implement Nuke Wars game mode
This commit introduces the Nuke Wars game mode, a team-based nuclear warfare scenario on the Baikal map. Key features and rule implementations: - Movement Restrictions: Implemented midpoint crossing restrictions for units in UnitImpl.ts. Only nuclear missiles, warships, and tradeships can cross the center line. - Preparation Phase: - Added a 3-minute preparation phase at the beginning of the game. - Restricted building to a teams own territory during this phase in PlayerImpl.ts. - Disabled nuke launches during the preparation phase in PlayerImpl.ts. - UI Enhancements: - Added a countdown timer for the preparation phase in GameRightSidebar.ts. - Added a visual line indicator on the Baikal map to separate team territories in TerrainLayer.ts. - Team-based Logic: Ensured Nuke Wars is consistently treated as a team-based game mode, fixing an issue in GameImpl.addPlayers. The implementation aligns with the detailed Nuke Wars game mode specification, including rules on allowed units, win conditions, and map/team setup.
This commit is contained in:
@@ -7,7 +7,7 @@ import replayRegularIcon from "../../../../resources/images/ReplayRegularIconWhi
|
||||
import replaySolidIcon from "../../../../resources/images/ReplaySolidIconWhite.svg";
|
||||
import settingsIcon from "../../../../resources/images/SettingIconWhite.svg";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { GameType } from "../../../core/game/Game";
|
||||
import { GameMode, GameType } from "../../../core/game/Game";
|
||||
import { GameUpdateType } from "../../../core/game/GameUpdates";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { PauseGameEvent } from "../../Transport";
|
||||
@@ -52,23 +52,42 @@ export class GameRightSidebar extends LitElement implements Layer {
|
||||
}
|
||||
|
||||
tick() {
|
||||
// Timer logic
|
||||
const updates = this.game.updatesSinceLastTick();
|
||||
if (updates) {
|
||||
this.hasWinner = this.hasWinner || updates[GameUpdateType.Win].length > 0;
|
||||
}
|
||||
const maxTimerValue = this.game.config().gameConfig().maxTimerValue;
|
||||
if (maxTimerValue !== undefined) {
|
||||
if (this.game.inSpawnPhase()) {
|
||||
|
||||
if (this.hasWinner) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isNukeWars =
|
||||
this.game.config().gameConfig().gameMode === GameMode.NukeWars;
|
||||
const spawnTurns = this.game.config().numSpawnPhaseTurns();
|
||||
const prepTurns = this.game.config().numPreparationPhaseTurns();
|
||||
const ticks = this.game.ticks();
|
||||
|
||||
if (ticks <= spawnTurns) {
|
||||
// Spawn phase
|
||||
const maxTimerValue = this.game.config().gameConfig().maxTimerValue;
|
||||
if (maxTimerValue !== undefined) {
|
||||
this.timer = maxTimerValue * 60;
|
||||
} else if (!this.hasWinner && this.game.ticks() % 10 === 0) {
|
||||
this.timer = Math.max(0, this.timer - 1);
|
||||
}
|
||||
} else {
|
||||
if (this.game.inSpawnPhase()) {
|
||||
} else {
|
||||
this.timer = 0;
|
||||
} else if (!this.hasWinner && this.game.ticks() % 10 === 0) {
|
||||
this.timer++;
|
||||
}
|
||||
} else if (isNukeWars && ticks <= spawnTurns + prepTurns) {
|
||||
// Nuke Wars Prep phase
|
||||
const elapsedInPrep = ticks - spawnTurns;
|
||||
this.timer = Math.max(0, (prepTurns - elapsedInPrep) / 10);
|
||||
} else {
|
||||
// Main game phase
|
||||
if (this.game.ticks() % 10 === 0) {
|
||||
const maxTimerValue = this.game.config().gameConfig().maxTimerValue;
|
||||
if (maxTimerValue !== undefined) {
|
||||
this.timer = Math.max(0, this.timer - 1);
|
||||
} else {
|
||||
this.timer++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Theme } from "../../../core/configuration/Config";
|
||||
import { GameMapType, GameMode } from "../../../core/game/Game";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import { Layer } from "./Layer";
|
||||
@@ -73,5 +74,20 @@ export class TerrainLayer implements Layer {
|
||||
this.game.width(),
|
||||
this.game.height(),
|
||||
);
|
||||
|
||||
if (
|
||||
this.game.config().gameConfig().gameMode === GameMode.NukeWars &&
|
||||
this.game.config().gameConfig().gameMap === GameMapType.Baikal
|
||||
) {
|
||||
const height = this.game.height();
|
||||
const midpoint = 0; // The map is centered, so midpoint is at x=0
|
||||
|
||||
context.beginPath();
|
||||
context.moveTo(midpoint, -height / 2);
|
||||
context.lineTo(midpoint, height / 2);
|
||||
context.strokeStyle = "white";
|
||||
context.lineWidth = 2 / this.transformHandler.scale; // Make the line width independent of zoom
|
||||
context.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ export class GameImpl implements Game {
|
||||
}
|
||||
|
||||
private addPlayers() {
|
||||
if (this.config().gameConfig().gameMode !== GameMode.Team) {
|
||||
if (!this.isTeamBasedGame()) {
|
||||
this._humans.forEach((p) => this.addPlayer(p));
|
||||
this._nations.forEach((n) => this.addPlayer(n.playerInfo));
|
||||
return;
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
ColoredTeams,
|
||||
Embargo,
|
||||
EmojiMessage,
|
||||
GameMapType,
|
||||
GameMode,
|
||||
Gold,
|
||||
MessageType,
|
||||
MutableAlliance,
|
||||
@@ -920,6 +922,23 @@ export class PlayerImpl implements Player {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
this.mg.config().gameConfig().gameMode === GameMode.NukeWars &&
|
||||
this.mg.config().gameConfig().gameMap === GameMapType.Baikal &&
|
||||
this.mg.inPreparationPhase()
|
||||
) {
|
||||
const midpoint = this.mg.width() / 2;
|
||||
const targetX = this.mg.x(targetTile);
|
||||
const isTeam1 = this.smallID() % 2 === 1; // Team 1 is on the left
|
||||
|
||||
if (isTeam1 && targetX >= midpoint) {
|
||||
return false;
|
||||
}
|
||||
if (!isTeam1 && targetX < midpoint) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const cost = this.mg.unitInfo(unitType).cost(this);
|
||||
if (!this.isAlive() || this.gold() < cost) {
|
||||
return false;
|
||||
@@ -961,6 +980,9 @@ export class PlayerImpl implements Player {
|
||||
}
|
||||
|
||||
nukeSpawn(tile: TileRef): TileRef | false {
|
||||
if (this.mg.inPreparationPhase()) {
|
||||
return false;
|
||||
}
|
||||
const owner = this.mg.owner(tile);
|
||||
if (owner.isPlayer()) {
|
||||
if (this.isOnSameTeam(owner)) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { simpleHash, toInt, withinInt } from "../Util";
|
||||
import {
|
||||
AllUnitParams,
|
||||
GameMapType,
|
||||
GameMode,
|
||||
MessageType,
|
||||
Player,
|
||||
Tick,
|
||||
@@ -151,6 +153,33 @@ export class UnitImpl implements Unit {
|
||||
}
|
||||
|
||||
move(tile: TileRef): void {
|
||||
if (
|
||||
this.mg.config().gameConfig().gameMode === GameMode.NukeWars &&
|
||||
this.mg.config().gameConfig().gameMap === GameMapType.Baikal
|
||||
) {
|
||||
const midpoint = this.mg.width() / 2;
|
||||
const currentX = this.mg.x(this._tile);
|
||||
const nextX = this.mg.x(tile);
|
||||
const crossesMidpoint =
|
||||
(currentX < midpoint && nextX >= midpoint) ||
|
||||
(currentX >= midpoint && nextX < midpoint);
|
||||
|
||||
if (crossesMidpoint) {
|
||||
const allowedTypes = [
|
||||
UnitType.Warship,
|
||||
UnitType.TradeShip,
|
||||
UnitType.AtomBomb,
|
||||
UnitType.HydrogenBomb,
|
||||
UnitType.MIRV,
|
||||
UnitType.MIRVWarhead,
|
||||
UnitType.Shell,
|
||||
];
|
||||
if (!allowedTypes.includes(this._type)) {
|
||||
return; // Block movement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tile === null) {
|
||||
throw new Error("tile cannot be null");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user