feat: cancel attack

Canceling attack takes time and incurs a malus (loose some troops).
Also prettier fixed a file HostLobbyModal.
This commit is contained in:
ilan schemoul
2025-03-03 22:06:35 +01:00
parent 8803cc89b6
commit 9a1f916f4d
11 changed files with 181 additions and 9 deletions
+23
View File
@@ -4,8 +4,11 @@ import { PlayerImpl } from "./PlayerImpl";
export class AttackImpl implements Attack {
private _isActive = true;
public _retreating = false;
public _retreated = false;
constructor(
private _id: string,
private _target: Player | TerraNullius,
private _attacker: Player,
private _troops: number,
@@ -33,6 +36,10 @@ export class AttackImpl implements Attack {
return this._isActive;
}
id() {
return this._id;
}
delete() {
if (this._target.isPlayer()) {
(this._target as PlayerImpl)._incomingAttacks = (
@@ -46,4 +53,20 @@ export class AttackImpl implements Attack {
this._isActive = false;
}
orderRetreat() {
this._retreating = true;
}
executeRetreat() {
this._retreated = true;
}
retreating(): boolean {
return this._retreating;
}
retreated(): boolean {
return this._retreated;
}
}
+7
View File
@@ -149,6 +149,11 @@ export interface Execution {
}
export interface Attack {
id(): string;
retreating(): boolean;
retreated(): boolean;
orderRetreat(): void;
executeRetreat(): void;
target(): Player | TerraNullius;
attacker(): Player;
troops(): number;
@@ -334,6 +339,8 @@ export interface Player {
): Attack;
outgoingAttacks(): Attack[];
incomingAttacks(): Attack[];
orderRetreat(attackID: string): void;
executeRetreat(attackID: string): void;
// Misc
executions(): Execution[];
+2
View File
@@ -77,6 +77,8 @@ export interface AttackUpdate {
attackerID: number;
targetID: number;
troops: number;
id: string;
retreating: boolean;
}
export interface PlayerUpdate {
+31 -1
View File
@@ -41,6 +41,8 @@ import { renderTroops } from "../../client/Utils";
import { TerraNulliusImpl } from "./TerraNulliusImpl";
import { andFN, manhattanDistFN, TileRef } from "./GameMap";
import { AttackImpl } from "./AttackImpl";
import { PseudoRandom } from "../PseudoRandom";
import { consolex } from "../Consolex";
interface Target {
tick: Tick;
@@ -56,6 +58,7 @@ class Donation {
export class PlayerImpl implements Player {
public _lastTileChange: number = 0;
public _pseudo_random: PseudoRandom;
private _gold: bigint;
private _troops: bigint;
@@ -103,6 +106,7 @@ export class PlayerImpl implements Player {
this._workers = 0n;
this._gold = 0n;
this._displayName = this._name; // processName(this._name)
this._pseudo_random = new PseudoRandom(simpleHash(this.playerInfo.id));
}
largestClusterBoundingBox: { min: Cell; max: Cell } | null;
@@ -139,6 +143,8 @@ export class PlayerImpl implements Player {
attackerID: a.attacker().smallID(),
targetID: a.target().smallID(),
troops: a.troops(),
id: a.id(),
retreating: a.retreating(),
}) as AttackUpdate,
),
incomingAttacks: this._incomingAttacks.map(
@@ -147,6 +153,8 @@ export class PlayerImpl implements Player {
attackerID: a.attacker().smallID(),
targetID: a.target().smallID(),
troops: a.troops(),
id: a.id(),
retreating: a.retreating(),
}) as AttackUpdate,
),
outgoingAllianceRequests: outgoingAllianceRequests,
@@ -246,6 +254,22 @@ export class PlayerImpl implements Player {
conquer(tile: TileRef) {
this.mg.conquer(this, tile);
}
orderRetreat(id: string) {
const attack = this._outgoingAttacks.filter((attack) => attack.id() == id);
if (!attack || !attack[0]) {
consolex.warn(`Didn't find outgoing attack with id ${id}`);
return;
}
attack[0].orderRetreat();
}
executeRetreat(id: string): void {
const attack = this._outgoingAttacks.filter((attack) => attack.id() == id);
// Execution is delayed so it's not an error that the attack does not exist.
if (!attack || !attack[0]) {
return;
}
attack[0].executeRetreat();
}
relinquish(tile: TileRef) {
if (this.mg.owner(tile) != this) {
throw new Error(`Cannot relinquish tile not owned by this player`);
@@ -845,7 +869,13 @@ export class PlayerImpl implements Player {
troops: number,
sourceTile: TileRef,
): Attack {
const attack = new AttackImpl(target, this, troops, sourceTile);
const attack = new AttackImpl(
this._pseudo_random.nextID(),
target,
this,
troops,
sourceTile,
);
this._outgoingAttacks.push(attack);
if (target.isPlayer()) {
(target as PlayerImpl)._incomingAttacks.push(attack);