Files
OpenFrontIO/src/core/game/UnitImpl.ts
T
Ilan Schemoul 8925f48ba7 sam protects againt mirv warhead (#376)
Fixes #483 

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors
![Capture d'écran 2025-03-30
174759](https://github.com/user-attachments/assets/8245723d-68de-4b96-ab19-5d85be18e4d9)


## Please put your Discord username so you can be contacted if a bug or
regression is found:
respectful pinguin
2025-04-17 19:02:50 -07:00

237 lines
5.8 KiB
TypeScript

import { simpleHash, toInt, withinInt } from "../Util";
import {
MessageType,
Player,
Tick,
Unit,
UnitInfo,
UnitSpecificInfos,
UnitType,
} from "./Game";
import { GameImpl } from "./GameImpl";
import { TileRef } from "./GameMap";
import { GameUpdateType, UnitUpdate } from "./GameUpdates";
import { PlayerImpl } from "./PlayerImpl";
export class UnitImpl implements Unit {
private _active = true;
private _health: bigint;
private _lastTile: TileRef = null;
// Currently only warship use it
private _target: Unit = null;
private _moveTarget: TileRef = null;
private _targetedBySAM = false;
private _constructionType: UnitType = undefined;
private _cooldownTick: Tick | null = null;
private _dstPort: Unit | null = null; // Only for trade ships
private _detonationDst: TileRef | null = null; // Only for nukes
private _warshipTarget: Unit | null = null;
private _cooldownDuration: number | null = null;
constructor(
private _type: UnitType,
private mg: GameImpl,
private _tile: TileRef,
private _troops: number,
private _id: number,
public _owner: PlayerImpl,
unitsSpecificInfos: UnitSpecificInfos = {},
) {
this._health = toInt(this.mg.unitInfo(_type).maxHealth ?? 1);
this._lastTile = _tile;
this._dstPort = unitsSpecificInfos.dstPort;
this._detonationDst = unitsSpecificInfos.detonationDst;
this._warshipTarget = unitsSpecificInfos.warshipTarget;
this._cooldownDuration = unitsSpecificInfos.cooldownDuration;
}
id() {
return this._id;
}
toUpdate(): UnitUpdate {
const warshipTarget = this.warshipTarget();
const dstPort = this.dstPort();
return {
type: GameUpdateType.Unit,
unitType: this._type,
id: this._id,
troops: this._troops,
ownerID: this._owner.smallID(),
isActive: this._active,
pos: this._tile,
lastPos: this._lastTile,
health: this.hasHealth() ? Number(this._health) : undefined,
constructionType: this._constructionType,
dstPortId: dstPort ? dstPort.id() : null,
warshipTargetId: warshipTarget ? warshipTarget.id() : null,
detonationDst: this.detonationDst(),
ticksLeftInCooldown: this.ticksLeftInCooldown(this._cooldownDuration),
};
}
type(): UnitType {
return this._type;
}
lastTile(): TileRef {
return this._lastTile;
}
move(tile: TileRef): void {
if (tile == null) {
throw new Error("tile cannot be null");
}
this._lastTile = this._tile;
this._tile = tile;
this.mg.removeUnit(this);
this.mg.addUnit(this);
this.mg.addUpdate(this.toUpdate());
}
setTroops(troops: number): void {
this._troops = troops;
}
troops(): number {
return this._troops;
}
health(): number {
return Number(this._health);
}
hasHealth(): boolean {
return this.info().maxHealth != undefined;
}
tile(): TileRef {
return this._tile;
}
owner(): PlayerImpl {
return this._owner;
}
info(): UnitInfo {
return this.mg.unitInfo(this._type);
}
setOwner(newOwner: Player): void {
const oldOwner = this._owner;
oldOwner._units = oldOwner._units.filter((u) => u != this);
this._owner = newOwner as PlayerImpl;
this.mg.addUpdate(this.toUpdate());
this.mg.displayMessage(
`Your ${this.type()} was captured by ${newOwner.displayName()}`,
MessageType.ERROR,
oldOwner.id(),
);
}
modifyHealth(delta: number): void {
this._health = withinInt(
this._health + toInt(delta),
0n,
toInt(this.info().maxHealth ?? 1),
);
}
delete(displayMessage: boolean = true): void {
if (!this.isActive()) {
throw new Error(`cannot delete ${this} not active`);
}
this._owner._units = this._owner._units.filter((b) => b != this);
this._active = false;
this.mg.addUpdate(this.toUpdate());
this.mg.removeUnit(this);
if (displayMessage && this.type() != UnitType.MIRVWarhead) {
this.mg.displayMessage(
`Your ${this.type()} was destroyed`,
MessageType.ERROR,
this.owner().id(),
);
}
}
isActive(): boolean {
return this._active;
}
constructionType(): UnitType | null {
if (this.type() != UnitType.Construction) {
throw new Error(`Cannot get construction type on ${this.type()}`);
}
return this._constructionType;
}
setConstructionType(type: UnitType): void {
if (this.type() != UnitType.Construction) {
throw new Error(`Cannot set construction type on ${this.type()}`);
}
this._constructionType = type;
this.mg.addUpdate(this.toUpdate());
}
hash(): number {
return this.tile() + simpleHash(this.type()) * this._id;
}
toString(): string {
return `Unit:${this._type},owner:${this.owner().name()}`;
}
setWarshipTarget(target: Unit) {
this._warshipTarget = target;
}
warshipTarget(): Unit {
return this._warshipTarget;
}
detonationDst(): TileRef {
return this._detonationDst;
}
dstPort(): Unit {
return this._dstPort;
}
// set the cooldown to the current tick or remove it
setCooldown(triggerCooldown: boolean): void {
if (triggerCooldown) {
this._cooldownTick = this.mg.ticks();
this.mg.addUpdate(this.toUpdate());
} else {
this._cooldownTick = null;
this.mg.addUpdate(this.toUpdate());
}
}
ticksLeftInCooldown(cooldownDuration: number): Tick {
return Math.max(
0,
cooldownDuration - (this.mg.ticks() - this._cooldownTick),
);
}
isCooldown(): boolean {
return this._cooldownTick ? true : false;
}
setDstPort(dstPort: Unit): void {
this._dstPort = dstPort;
}
setMoveTarget(moveTarget: TileRef) {
this._moveTarget = moveTarget;
}
moveTarget(): TileRef | null {
return this._moveTarget;
}
setTargetedBySAM(targeted: boolean): void {
this._targetedBySAM = targeted;
}
targetedBySAM(): boolean {
return this._targetedBySAM;
}
}