mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 07:44:17 +00:00
asd
This commit is contained in:
+1
-1
@@ -5,4 +5,4 @@
|
||||
export PATH="/usr/local/bin:$HOME/.npm-global/bin:$HOME/.nvm/versions/node/$(node -v)/bin:$PATH"
|
||||
|
||||
# Then run lint-staged if tests pass
|
||||
npx lint-staged
|
||||
cmd lint-staged
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
} from "./Transport";
|
||||
import { createCanvas } from "./Utils";
|
||||
import { createRenderer, GameRenderer } from "./graphics/GameRenderer";
|
||||
import { GoToPositionEvent } from "./graphics/layers/Leaderboard";
|
||||
import SoundManager from "./sound/SoundManager";
|
||||
|
||||
export interface LobbyConfig {
|
||||
@@ -194,6 +195,7 @@ async function createClientGame(
|
||||
export class ClientGameRunner {
|
||||
private myPlayer: PlayerView | null = null;
|
||||
private isActive = false;
|
||||
private hasZoomedToSpawn = false;
|
||||
|
||||
private turnsSeen = 0;
|
||||
private hasJoined = false;
|
||||
@@ -292,6 +294,16 @@ export class ClientGameRunner {
|
||||
this.gameView.update(gu);
|
||||
this.renderer.tick();
|
||||
|
||||
const myPlayer = this.gameView.myPlayer();
|
||||
if (!this.hasZoomedToSpawn && myPlayer && myPlayer.numTilesOwned() > 0) {
|
||||
const initialSpawnTile = myPlayer.initialSpawnTile();
|
||||
if (initialSpawnTile) {
|
||||
const cell = this.gameView.cell(initialSpawnTile);
|
||||
this.eventBus.emit(new GoToPositionEvent(cell.x, cell.y));
|
||||
this.hasZoomedToSpawn = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (gu.updates[GameUpdateType.Win].length > 0) {
|
||||
this.saveGame(gu.updates[GameUpdateType.Win][0]);
|
||||
}
|
||||
|
||||
@@ -53,6 +53,13 @@ export class HostLobbyModal extends LitElement {
|
||||
@state() private clients: ClientInfo[] = [];
|
||||
@state() private useRandomMap: boolean = false;
|
||||
@state() private disabledUnits: UnitType[] = [];
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
|
||||
private readonly nukeWarsDisabledUnits = [
|
||||
UnitType.MIRV,
|
||||
];
|
||||
>>>>>>> Stashed changes
|
||||
@state() private lobbyCreatorClientID: string = "";
|
||||
@state() private lobbyIdVisible: boolean = true;
|
||||
|
||||
|
||||
@@ -49,6 +49,13 @@ export class SinglePlayerModal extends LitElement {
|
||||
|
||||
@state() private disabledUnits: UnitType[] = [];
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
private readonly nukeWarsDisabledUnits = [
|
||||
UnitType.MIRV,
|
||||
];
|
||||
|
||||
>>>>>>> Stashed changes
|
||||
private userSettings: UserSettings = new UserSettings();
|
||||
|
||||
connectedCallback() {
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
PlayerInfo,
|
||||
PlayerProfile,
|
||||
PlayerType,
|
||||
UnitType,
|
||||
} from "./game/Game";
|
||||
import { createGame } from "./game/GameImpl";
|
||||
import { TileRef } from "./game/GameMap";
|
||||
@@ -186,7 +187,7 @@ export class GameRunner {
|
||||
const tile =
|
||||
x !== undefined && y !== undefined ? this.game.ref(x, y) : null;
|
||||
const actions = {
|
||||
canAttack: tile !== null && player.canAttack(tile),
|
||||
canAttack: tile !== null && player.canAttack(tile, UnitType.City),
|
||||
buildableUnits: player.buildableUnits(tile),
|
||||
canSendEmojiAllPlayers: player.canSendEmoji(AllPlayers),
|
||||
} as PlayerActions;
|
||||
|
||||
@@ -323,6 +323,12 @@ export class DefaultConfig implements Config {
|
||||
}
|
||||
|
||||
isUnitDisabled(unitType: UnitType): boolean {
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
if (this._gameConfig.gameMode === GameMode.NukeWars) {
|
||||
return unitType === UnitType.MIRV;
|
||||
}
|
||||
>>>>>>> Stashed changes
|
||||
return this._gameConfig.disabledUnits?.includes(unitType) ?? false;
|
||||
}
|
||||
|
||||
@@ -616,6 +622,16 @@ export class DefaultConfig implements Config {
|
||||
numSpawnPhaseTurns(): number {
|
||||
return this._gameConfig.gameType === GameType.Singleplayer ? 100 : 300;
|
||||
}
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
|
||||
numPreparationPhaseTurns(): number {
|
||||
if (this._gameConfig.gameMode === GameMode.NukeWars) {
|
||||
return 180 * 10; // 180 seconds * 10 ticks/sec
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
>>>>>>> Stashed changes
|
||||
numBots(): number {
|
||||
return this.bots();
|
||||
}
|
||||
|
||||
@@ -37,6 +37,13 @@ export class SpawnExecution implements Execution {
|
||||
player = this.mg.addPlayer(this.playerInfo);
|
||||
}
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
const spawnTile = this.isNukeWarsAndBaikal(player)
|
||||
? this.findBestNukeWarsSpawn(player)
|
||||
: this.tile;
|
||||
|
||||
>>>>>>> Stashed changes
|
||||
player.tiles().forEach((t) => player.relinquish(t));
|
||||
getSpawnTiles(this.mg, this.tile).forEach((t) => {
|
||||
player.conquer(t);
|
||||
@@ -58,4 +65,92 @@ export class SpawnExecution implements Execution {
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
private isNukeWarsAndBaikal(player: Player): boolean {
|
||||
const gc = this.mg.config().gameConfig();
|
||||
return (
|
||||
gc.gameMode === GameMode.NukeWars && gc.gameMap === GameMapType.Baikal
|
||||
);
|
||||
}
|
||||
|
||||
private findBestNukeWarsSpawn(player: Player): TileRef {
|
||||
const mapWidth = this.mg.width();
|
||||
const midpoint = Math.floor(mapWidth / 2);
|
||||
const wantLeft = player.smallID() % 2 === 1;
|
||||
|
||||
let bestTile: TileRef | null = null;
|
||||
let bestScore = Infinity;
|
||||
|
||||
this.mg.forEachTile((t) => {
|
||||
const xt = this.mg.x(t);
|
||||
const onCorrectHalf = wantLeft ? xt < midpoint : xt >= midpoint;
|
||||
|
||||
if (onCorrectHalf && !this.mg.hasOwner(t) && this.mg.isLand(t)) {
|
||||
const distToOriginal = this.mg.manhattanDist(this.tile, t);
|
||||
const distToMidpoint = Math.abs(xt - midpoint);
|
||||
const distToTeam = this.minDistToTeam(player, t);
|
||||
|
||||
// Score combines distance from original tile, distance from midpoint, and distance from team members.
|
||||
// We want to be close to the original spawn, but also spread out from teammates.
|
||||
const score =
|
||||
distToOriginal +
|
||||
distToMidpoint * -0.5 + // Bias towards the center
|
||||
(isFinite(distToTeam) ? -distToTeam * 0.9 : 0) + // Bias away from teammates
|
||||
this.bandScore(player, t); // Bias towards a vertical band to spread out spawns
|
||||
|
||||
if (score < bestScore) {
|
||||
bestScore = score;
|
||||
bestTile = t;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return bestTile ?? this.tile;
|
||||
}
|
||||
|
||||
private minDistToTeam(player: Player, tile: TileRef): number {
|
||||
let minDist = Infinity;
|
||||
const team = player.team();
|
||||
if (!team) {
|
||||
return minDist;
|
||||
}
|
||||
|
||||
for (const p of this.mg.players()) {
|
||||
if (p.team() !== team || p === player) {
|
||||
continue;
|
||||
}
|
||||
for (const owned of p.tiles()) {
|
||||
const d = this.mg.manhattanDist(owned, tile);
|
||||
if (d < minDist) {
|
||||
minDist = d;
|
||||
}
|
||||
if (minDist === 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return minDist;
|
||||
}
|
||||
|
||||
private bandScore(player: Player, tile: TileRef): number {
|
||||
const team = player.team();
|
||||
if (!team) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const teamPlayers = this.mg
|
||||
.players()
|
||||
.filter((pp) => pp.team() === team)
|
||||
.sort((a, b) => a.smallID() - b.smallID());
|
||||
const teamIndex = teamPlayers.findIndex((pp) => pp === player);
|
||||
const teamCount = Math.max(1, teamPlayers.length);
|
||||
const numBands = Math.max(1, Math.round(Math.sqrt(teamCount)));
|
||||
const desiredBand = Math.floor((teamIndex / teamCount) * numBands);
|
||||
const y = this.mg.y(tile);
|
||||
const bandIndex = Math.floor((y / this.mg.height()) * numBands);
|
||||
const bandPenalty = 24; // tunes vertical spread strength
|
||||
|
||||
return Math.abs(bandIndex - desiredBand) * bandPenalty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,11 @@ export class WinCheckExecution implements Execution {
|
||||
|
||||
if (this.mg.config().gameConfig().gameMode === GameMode.FFA) {
|
||||
this.checkWinnerFFA();
|
||||
<<<<<<< Updated upstream
|
||||
} else {
|
||||
=======
|
||||
} else if (gameMode === GameMode.NukeWars || gameMode === GameMode.Team) {
|
||||
>>>>>>> Stashed changes
|
||||
this.checkWinnerTeam();
|
||||
}
|
||||
}
|
||||
@@ -63,32 +67,76 @@ export class WinCheckExecution implements Execution {
|
||||
|
||||
checkWinnerTeam(): void {
|
||||
if (this.mg === null) throw new Error("Not initialized");
|
||||
|
||||
const teamToTiles = new Map<Team, number>();
|
||||
for (const player of this.mg.players()) {
|
||||
const team = player.team();
|
||||
// Sanity check, team should not be null here
|
||||
if (team === null) continue;
|
||||
teamToTiles.set(
|
||||
team,
|
||||
(teamToTiles.get(team) ?? 0) + player.numTilesOwned(),
|
||||
);
|
||||
}
|
||||
const sorted = Array.from(teamToTiles.entries()).sort(
|
||||
(a, b) => b[1] - a[1],
|
||||
);
|
||||
|
||||
const sorted = Array.from(teamToTiles.entries()).sort((a, b) => b[1] - a[1]);
|
||||
if (sorted.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gameMode = this.mg.config().gameConfig().gameMode;
|
||||
if (gameMode === GameMode.NukeWars) {
|
||||
this.checkNukeWarsWinCondition(sorted);
|
||||
} else {
|
||||
this.checkTeamWinCondition(sorted);
|
||||
}
|
||||
}
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
private checkNukeWarsWinCondition(sorted: [Team, number][]): void {
|
||||
if (this.mg === null) throw new Error("Not initialized");
|
||||
const numTilesWithoutFallout =
|
||||
this.mg.numLandTiles() - this.mg.numTilesWithFallout();
|
||||
|
||||
for (const [team, tiles] of sorted) {
|
||||
const percentage = (tiles / numTilesWithoutFallout) * 100;
|
||||
if (percentage < 5 && team !== ColoredTeams.Bot) {
|
||||
const otherTeam = sorted.find(
|
||||
([t, _]) => t !== team && t !== ColoredTeams.Bot,
|
||||
);
|
||||
if (otherTeam) {
|
||||
this.mg.setWinner(otherTeam[0], this.mg.stats().stats());
|
||||
console.log(
|
||||
`${otherTeam[0]} has won the game by reducing ${team} territory below 5%`,
|
||||
);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isTimeElapsed()) {
|
||||
const winner = sorted.find(([t, _]) => t !== ColoredTeams.Bot);
|
||||
if (winner) {
|
||||
this.mg.setWinner(winner[0], this.mg.stats().stats());
|
||||
console.log(
|
||||
`${winner[0]} has won the game by having most territory when time elapsed`,
|
||||
);
|
||||
this.active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private checkTeamWinCondition(sorted: [Team, number][]): void {
|
||||
if (this.mg === null) throw new Error("Not initialized");
|
||||
const max = sorted[0];
|
||||
const timeElapsed =
|
||||
(this.mg.ticks() - this.mg.config().numSpawnPhaseTurns()) / 10;
|
||||
const numTilesWithoutFallout =
|
||||
this.mg.numLandTiles() - this.mg.numTilesWithFallout();
|
||||
const percentage = (max[1] / numTilesWithoutFallout) * 100;
|
||||
|
||||
if (
|
||||
percentage > this.mg.config().percentageTilesOwnedToWin() ||
|
||||
(this.mg.config().gameConfig().maxTimerValue !== undefined &&
|
||||
timeElapsed - this.mg.config().gameConfig().maxTimerValue! * 60 >= 0)
|
||||
this.isTimeElapsed()
|
||||
) {
|
||||
if (max[0] === ColoredTeams.Bot) return;
|
||||
this.mg.setWinner(max[0], this.mg.stats().stats());
|
||||
@@ -97,6 +145,15 @@ export class WinCheckExecution implements Execution {
|
||||
}
|
||||
}
|
||||
|
||||
private isTimeElapsed(): boolean {
|
||||
if (this.mg === null) throw new Error("Not initialized");
|
||||
const timeElapsed =
|
||||
(this.mg.ticks() - this.mg.config().numSpawnPhaseTurns()) / 10;
|
||||
const maxTimerValue = this.mg.config().gameConfig().maxTimerValue;
|
||||
return maxTimerValue !== undefined && timeElapsed >= maxTimerValue * 60;
|
||||
}
|
||||
|
||||
>>>>>>> Stashed changes
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
@@ -635,7 +635,7 @@ export interface Player {
|
||||
canTrade(other: Player): boolean;
|
||||
|
||||
// Attacking.
|
||||
canAttack(tile: TileRef): boolean;
|
||||
canAttack(tile: TileRef, unitType: UnitType): boolean;
|
||||
|
||||
createAttack(
|
||||
target: Player | TerraNullius,
|
||||
|
||||
@@ -96,15 +96,30 @@ export class GameImpl implements Game {
|
||||
this._width = _map.width();
|
||||
this._height = _map.height();
|
||||
this.unitGrid = new UnitGrid(this._map);
|
||||
<<<<<<< Updated upstream
|
||||
|
||||
if (_config.gameConfig().gameMode === GameMode.Team) {
|
||||
=======
|
||||
if (this.isTeamBasedGame()) {
|
||||
>>>>>>> Stashed changes
|
||||
this.populateTeams();
|
||||
}
|
||||
this.addPlayers();
|
||||
}
|
||||
|
||||
private isTeamBasedGame(): boolean {
|
||||
const gameMode = this._config.gameConfig().gameMode;
|
||||
return gameMode === GameMode.Team || gameMode === GameMode.NukeWars;
|
||||
}
|
||||
|
||||
private populateTeams() {
|
||||
let numPlayerTeams = this._config.playerTeams();
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
if (this._config.gameConfig().gameMode === GameMode.NukeWars) {
|
||||
numPlayerTeams = 2;
|
||||
}
|
||||
>>>>>>> Stashed changes
|
||||
if (typeof numPlayerTeams !== "number") {
|
||||
const players = this._humans.length + this._nations.length;
|
||||
switch (numPlayerTeams) {
|
||||
@@ -323,6 +338,15 @@ export class GameImpl implements Game {
|
||||
return this._ticks <= this.config().numSpawnPhaseTurns();
|
||||
}
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
inPreparationPhase(): boolean {
|
||||
const spawn = this.config().numSpawnPhaseTurns();
|
||||
const prep = this.config().numPreparationPhaseTurns();
|
||||
return this._ticks > spawn && this._ticks <= spawn + prep;
|
||||
}
|
||||
|
||||
>>>>>>> Stashed changes
|
||||
ticks(): number {
|
||||
return this._ticks;
|
||||
}
|
||||
@@ -340,7 +364,7 @@ export class GameImpl implements Game {
|
||||
const inited: Execution[] = [];
|
||||
const unInited: Execution[] = [];
|
||||
this.unInitExecs.forEach((e) => {
|
||||
if (!this.inSpawnPhase() || e.activeDuringSpawnPhase()) {
|
||||
if (!this.inSpawnPhase() || (e.activeDuringSpawnPhase && e.activeDuringSpawnPhase())) {
|
||||
e.init(this, this._ticks);
|
||||
inited.push(e);
|
||||
} else {
|
||||
@@ -665,7 +689,11 @@ export class GameImpl implements Game {
|
||||
}
|
||||
|
||||
teams(): Team[] {
|
||||
<<<<<<< Updated upstream
|
||||
if (this._config.gameConfig().gameMode !== GameMode.Team) {
|
||||
=======
|
||||
if (!this.isTeamBasedGame()) {
|
||||
>>>>>>> Stashed changes
|
||||
return [];
|
||||
}
|
||||
return [this.botTeam, ...this.playerTeams];
|
||||
|
||||
@@ -171,6 +171,7 @@ export interface PlayerUpdate {
|
||||
hasSpawned: boolean;
|
||||
betrayals?: bigint;
|
||||
lastDeleteUnitTick: Tick;
|
||||
initialSpawnTile?: TileRef;
|
||||
}
|
||||
|
||||
export interface AllianceView {
|
||||
|
||||
@@ -181,6 +181,7 @@ export class UnitView {
|
||||
export class PlayerView {
|
||||
public anonymousName: string | null = null;
|
||||
private decoder?: PatternDecoder;
|
||||
private _initialSpawnTile: TileRef | null = null;
|
||||
|
||||
private _territoryColor: Colord;
|
||||
private _borderColor: Colord;
|
||||
@@ -430,6 +431,14 @@ export class PlayerView {
|
||||
return this.data.isDisconnected;
|
||||
}
|
||||
|
||||
initialSpawnTile(): TileRef | null {
|
||||
return this._initialSpawnTile;
|
||||
}
|
||||
|
||||
setInitialSpawnTile(tile: TileRef) {
|
||||
this._initialSpawnTile = tile;
|
||||
}
|
||||
|
||||
lastDeleteUnitTick(): Tick {
|
||||
return this.data.lastDeleteUnitTick;
|
||||
}
|
||||
@@ -513,17 +522,21 @@ export class GameView implements GameMap {
|
||||
player.data = pu;
|
||||
player.nameData = gu.playerNameViewData[pu.id];
|
||||
} else {
|
||||
const newPlayerView = new PlayerView(
|
||||
this,
|
||||
pu,
|
||||
gu.playerNameViewData[pu.id],
|
||||
// First check human by clientID, then check nation by name.
|
||||
this._cosmetics.get(pu.clientID ?? "") ??
|
||||
this._cosmetics.get(pu.name) ??
|
||||
{},
|
||||
);
|
||||
if (pu.initialSpawnTile !== undefined) {
|
||||
newPlayerView.setInitialSpawnTile(pu.initialSpawnTile);
|
||||
}
|
||||
this._players.set(
|
||||
pu.id,
|
||||
new PlayerView(
|
||||
this,
|
||||
pu,
|
||||
gu.playerNameViewData[pu.id],
|
||||
// First check human by clientID, then check nation by name.
|
||||
this._cosmetics.get(pu.clientID ?? "") ??
|
||||
this._cosmetics.get(pu.name) ??
|
||||
{},
|
||||
),
|
||||
newPlayerView,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
+147
-1
@@ -66,6 +66,7 @@ class Donation {
|
||||
export class PlayerImpl implements Player {
|
||||
public _lastTileChange: number = 0;
|
||||
public _pseudo_random: PseudoRandom;
|
||||
public _initialSpawnTile: TileRef | null = null;
|
||||
|
||||
private _gold: bigint;
|
||||
private _troops: bigint;
|
||||
@@ -175,6 +176,7 @@ export class PlayerImpl implements Player {
|
||||
hasSpawned: this.hasSpawned(),
|
||||
betrayals: stats?.betrayals,
|
||||
lastDeleteUnitTick: this.lastDeleteUnitTick,
|
||||
initialSpawnTile: this._initialSpawnTile ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -307,6 +309,9 @@ export class PlayerImpl implements Player {
|
||||
this._troops = toInt(troops);
|
||||
}
|
||||
conquer(tile: TileRef) {
|
||||
if (this._initialSpawnTile === null && this._tiles.size === 0) {
|
||||
this._initialSpawnTile = tile;
|
||||
}
|
||||
this.mg.conquer(this, tile);
|
||||
}
|
||||
orderRetreat(id: string) {
|
||||
@@ -911,6 +916,79 @@ export class PlayerImpl implements Player {
|
||||
});
|
||||
}
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
private isInTeamSpawnZone(tile: TileRef): boolean {
|
||||
const gameMode = this.mg.config().gameConfig().gameMode;
|
||||
if (gameMode !== GameMode.NukeWars) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const team = this.team();
|
||||
if (!team) return false;
|
||||
|
||||
// Simple geometric split:
|
||||
// Team 1 (first team) gets left half (x < width/2)
|
||||
// Team 2 (second team) gets right half (x >= width/2)
|
||||
const x = this.mg.x(tile);
|
||||
const mapWidth = this.mg.width();
|
||||
const midpoint = Math.floor(mapWidth / 2);
|
||||
|
||||
// Team 1 gets left half, Team 2 gets right half
|
||||
const isTeam1 = team === this.mg.teams()[0];
|
||||
return isTeam1 ? x < midpoint : x >= midpoint;
|
||||
}
|
||||
|
||||
private isNukeWars(): boolean {
|
||||
return this.mg.config().gameConfig().gameMode === GameMode.NukeWars;
|
||||
}
|
||||
|
||||
private isNukeWarsAndBaikal(): boolean {
|
||||
const gc = this.mg.config().gameConfig();
|
||||
return (
|
||||
gc.gameMode === GameMode.NukeWars && gc.gameMap === GameMapType.Baikal
|
||||
);
|
||||
}
|
||||
|
||||
private canBuildShipNukeWars(
|
||||
unitType: UnitType,
|
||||
targetTile: TileRef,
|
||||
): boolean {
|
||||
// Transport ships cannot enter enemy team territory
|
||||
if (unitType === UnitType.TransportShip) {
|
||||
const targetOwner = this.mg.owner(targetTile);
|
||||
if (
|
||||
targetOwner.isPlayer() &&
|
||||
!this.isOnSameTeam(targetOwner as Player)
|
||||
) {
|
||||
this.mg.displayMessage(
|
||||
"Transport ships cannot enter enemy team territory in Nuke Wars",
|
||||
MessageType.ATTACK_FAILED,
|
||||
this.id(),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Warships and TradeShips are allowed to go over to the enemy's spawn
|
||||
return true;
|
||||
}
|
||||
|
||||
private canBuildNukeNukeWars(unitType: UnitType): boolean {
|
||||
if (
|
||||
(unitType === UnitType.AtomBomb || unitType === UnitType.HydrogenBomb) &&
|
||||
this.mg.inPreparationPhase()
|
||||
) {
|
||||
this.mg.displayMessage(
|
||||
"Nuclear weapons cannot be launched during the preparation phase",
|
||||
MessageType.ATTACK_FAILED,
|
||||
this.id(),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
>>>>>>> Stashed changes
|
||||
canBuild(
|
||||
unitType: UnitType,
|
||||
targetTile: TileRef,
|
||||
@@ -920,10 +998,31 @@ export class PlayerImpl implements Player {
|
||||
return false;
|
||||
}
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
if (this.isNukeWarsAndBaikal()) {
|
||||
if (!this.canBuildShipNukeWars(unitType, targetTile)) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
this.mg.inPreparationPhase() &&
|
||||
!this.isInTeamSpawnZone(targetTile)
|
||||
) {
|
||||
this.mg.displayMessage(
|
||||
"During preparation phase, you can only build in your own territory",
|
||||
MessageType.ATTACK_FAILED,
|
||||
this.id(),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
>>>>>>> Stashed changes
|
||||
const cost = this.mg.unitInfo(unitType).cost(this);
|
||||
if (!this.isAlive() || this.gold() < cost) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (unitType) {
|
||||
case UnitType.MIRV:
|
||||
if (!this.mg.hasOwner(targetTile)) {
|
||||
@@ -932,6 +1031,12 @@ export class PlayerImpl implements Player {
|
||||
return this.nukeSpawn(targetTile);
|
||||
case UnitType.AtomBomb:
|
||||
case UnitType.HydrogenBomb:
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
if (this.isNukeWars() && !this.canBuildNukeNukeWars(unitType)) {
|
||||
return false;
|
||||
}
|
||||
>>>>>>> Stashed changes
|
||||
return this.nukeSpawn(targetTile);
|
||||
case UnitType.MIRVWarhead:
|
||||
return targetTile;
|
||||
@@ -1033,7 +1138,16 @@ export class PlayerImpl implements Player {
|
||||
}
|
||||
|
||||
private validStructureSpawnTiles(tile: TileRef): TileRef[] {
|
||||
<<<<<<< Updated upstream
|
||||
if (this.mg.owner(tile) !== this) {
|
||||
=======
|
||||
const owner = this.mg.owner(tile);
|
||||
if (this.isNukeWars() && this.mg.inPreparationPhase()) {
|
||||
if (!owner.isPlayer() || !this.isOnSameTeam(owner as Player)) {
|
||||
return [];
|
||||
}
|
||||
} else if (owner !== this) {
|
||||
>>>>>>> Stashed changes
|
||||
return [];
|
||||
}
|
||||
const searchRadius = 15;
|
||||
@@ -1145,7 +1259,7 @@ export class PlayerImpl implements Player {
|
||||
return this._incomingAttacks;
|
||||
}
|
||||
|
||||
public canAttack(tile: TileRef): boolean {
|
||||
public canAttack(tile: TileRef, unitType: UnitType): boolean {
|
||||
if (
|
||||
this.mg.hasOwner(tile) &&
|
||||
this.mg.config().numSpawnPhaseTurns() +
|
||||
@@ -1168,6 +1282,38 @@ export class PlayerImpl implements Player {
|
||||
if (!this.mg.isLand(tile)) {
|
||||
return false;
|
||||
}
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
|
||||
// Nuke Wars specific attack rules
|
||||
if (this.isNukeWarsAndBaikal()) {
|
||||
const mapWidth = this.mg.width();
|
||||
const tx = this.mg.x(tile);
|
||||
const attackerLeft = this.smallID() % 2 === 1;
|
||||
const tileLeft = tx < Math.floor(mapWidth / 2);
|
||||
|
||||
// During spawn phase, only attack within own half
|
||||
if (this.mg.inSpawnPhase()) {
|
||||
if (attackerLeft !== tileLeft) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// After spawn phase, only nuclear missiles, warships, and tradeships can cross the midpoint
|
||||
const canCross =
|
||||
unitType === UnitType.AtomBomb ||
|
||||
unitType === UnitType.HydrogenBomb ||
|
||||
unitType === UnitType.MIRV ||
|
||||
unitType === UnitType.MIRVWarhead ||
|
||||
unitType === UnitType.Warship ||
|
||||
unitType === UnitType.TradeShip;
|
||||
|
||||
if (attackerLeft !== tileLeft && !canCross) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
>>>>>>> Stashed changes
|
||||
if (this.mg.hasOwner(tile)) {
|
||||
return this.sharesBorderWith(other);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user