mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 15:20:43 +00:00
Clean up and refactor the Unit class (#769)
## Description: * Merged similar fields so they can be reused (eg warshipTarget => targetUnit) * simplified isCooldown api * added "touch" method to send update to UI layer * standardized on "undefined" ## Please complete the following: - [ ] 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: <DISCORD USERNAME> evan <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Refactor** - Unified and simplified how unit targets and cooldowns are managed across all unit types, resulting in more consistent in-game behavior for nukes, warships, trade ships, and SAM launchers. - Updated naming and logic for unit targeting and cooldowns, improving clarity in status displays and interactions. - Reorganized unit interface and streamlined cooldown handling for smoother gameplay experience. - **Bug Fixes** - Corrected visual indicators for nukes and warships to accurately reflect their targets. - **Tests** - Updated automated tests to align with the new cooldown and targeting logic. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -461,7 +461,7 @@ export class NameLayer implements Layer {
|
||||
);
|
||||
});
|
||||
const isMyPlayerTarget = nukesSentByOtherPlayer.find((unit) => {
|
||||
const detonationDst = unit.detonationDst();
|
||||
const detonationDst = unit.targetTile();
|
||||
if (detonationDst === undefined) return false;
|
||||
const targetId = this.game.owner(detonationDst).id();
|
||||
return myPlayer && targetId === myPlayer.id();
|
||||
|
||||
@@ -291,7 +291,7 @@ export class UnitLayer implements Layer {
|
||||
}
|
||||
|
||||
private handleWarShipEvent(unit: UnitView) {
|
||||
if (unit.warshipTargetId()) {
|
||||
if (unit.targetUnitId()) {
|
||||
this.drawSprite(unit, colord({ r: 200, b: 0, g: 0 }));
|
||||
} else {
|
||||
this.drawSprite(unit);
|
||||
@@ -502,7 +502,7 @@ export class UnitLayer implements Layer {
|
||||
|
||||
if (this.alternateView) {
|
||||
let rel = this.relationship(unit);
|
||||
const dstPortId = unit.dstPortId();
|
||||
const dstPortId = unit.targetUnitId();
|
||||
if (unit.type() === UnitType.TradeShip && dstPortId !== undefined) {
|
||||
const target = this.game.unit(dstPortId)?.owner();
|
||||
const myPlayer = this.game.myPlayer();
|
||||
|
||||
@@ -53,11 +53,8 @@ export class MissileSiloExecution implements Execution {
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.silo.isCooldown() &&
|
||||
this.silo.ticksLeftInCooldown(this.mg.config().SiloCooldown()) === 0
|
||||
) {
|
||||
this.silo.setCooldown(false);
|
||||
if (this.silo.ticksLeftInCooldown() === 0) {
|
||||
this.silo.touch();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export class MoveWarshipExecution implements Execution {
|
||||
console.log("MoveWarshipExecution: warship is already dead");
|
||||
return;
|
||||
}
|
||||
warship.setMoveTarget(this.position);
|
||||
warship.setTargetTile(this.position);
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ export class NukeExecution implements Execution {
|
||||
.units(UnitType.MissileSilo)
|
||||
.find((silo) => silo.tile() === spawn);
|
||||
if (silo) {
|
||||
silo.setCooldown(true);
|
||||
silo.launch();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -135,10 +135,10 @@ export class SAMLauncherExecution implements Execution {
|
||||
unit.owner() !== this.player && !this.player.isFriendly(unit.owner()),
|
||||
)
|
||||
.filter((unit) => {
|
||||
const dst = unit.detonationDst();
|
||||
const dst = unit.targetTile();
|
||||
return (
|
||||
this.sam !== null &&
|
||||
dst !== null &&
|
||||
dst !== undefined &&
|
||||
this.mg.manhattanDist(dst, this.sam.tile()) <
|
||||
this.MIRVWarheadProtectionRadius
|
||||
);
|
||||
@@ -149,19 +149,17 @@ export class SAMLauncherExecution implements Execution {
|
||||
target = this.getSingleTarget();
|
||||
}
|
||||
|
||||
if (
|
||||
this.sam.isCooldown() &&
|
||||
this.sam.ticksLeftInCooldown(this.mg.config().SAMCooldown()) === 0
|
||||
) {
|
||||
this.sam.setCooldown(false);
|
||||
if (this.sam.ticksLeftInCooldown() === 0) {
|
||||
// Touch SAM to update sprite to show not in cooldown.
|
||||
this.sam.touch();
|
||||
}
|
||||
|
||||
const isSingleTarget = target && !target.targetedBySAM();
|
||||
if (
|
||||
(isSingleTarget || mirvWarheadTargets.length > 0) &&
|
||||
!this.sam.isCooldown()
|
||||
!this.sam.isInCooldown()
|
||||
) {
|
||||
this.sam.setCooldown(true);
|
||||
this.sam.launch();
|
||||
const type =
|
||||
mirvWarheadTargets.length > 0 ? UnitType.MIRVWarhead : target?.type();
|
||||
if (type === undefined) throw new Error("Unknown unit type");
|
||||
|
||||
@@ -94,7 +94,7 @@ export class TradeShipExecution implements Execution {
|
||||
return;
|
||||
} else {
|
||||
this._dstPort = ports[0];
|
||||
this.tradeShip.setDstPort(this._dstPort);
|
||||
this.tradeShip.setTargetUnit(this._dstPort);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ export class TradeShipExecution implements Execution {
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
// Fire unit event to rerender.
|
||||
this.tradeShip.move(this.tradeShip.tile());
|
||||
this.tradeShip.touch();
|
||||
break;
|
||||
case PathFindResultType.NextTile:
|
||||
this._dstPort.cachePut(this.tradeShip.tile(), result.tile);
|
||||
|
||||
@@ -19,9 +19,9 @@ export class WarshipExecution implements Execution {
|
||||
private _owner: Player;
|
||||
private active = true;
|
||||
private warship: Unit | null = null;
|
||||
private mg: Game | null = null;
|
||||
private mg: Game;
|
||||
|
||||
private target: Unit | null = null;
|
||||
private target: Unit | undefined = undefined;
|
||||
private pathfinder: PathFinder | null = null;
|
||||
|
||||
private patrolTile: TileRef | undefined;
|
||||
@@ -35,6 +35,7 @@ export class WarshipExecution implements Execution {
|
||||
) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg;
|
||||
if (!mg.hasPlayer(this.playerID)) {
|
||||
console.log(`WarshipExecution: player ${this.playerID} not found`);
|
||||
this.active = false;
|
||||
@@ -42,7 +43,6 @@ export class WarshipExecution implements Execution {
|
||||
}
|
||||
this.pathfinder = PathFinder.Mini(mg, 5000);
|
||||
this._owner = mg.player(this.playerID);
|
||||
this.mg = mg;
|
||||
this.patrolTile = this.patrolCenterTile;
|
||||
this.random = new PseudoRandom(mg.ticks());
|
||||
}
|
||||
@@ -56,14 +56,14 @@ export class WarshipExecution implements Execution {
|
||||
const result = this.pathfinder.nextTile(this.warship.tile(), target);
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this.warship.setMoveTarget(null);
|
||||
this.warship.move(this.warship.tile());
|
||||
this.warship.setTargetTile(undefined);
|
||||
this.warship.touch();
|
||||
return;
|
||||
case PathFindResultType.NextTile:
|
||||
this.warship.move(result.tile);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
this.warship.move(this.warship.tile());
|
||||
this.warship.touch();
|
||||
break;
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.log(`path not found to target`);
|
||||
@@ -72,7 +72,11 @@ export class WarshipExecution implements Execution {
|
||||
}
|
||||
|
||||
private shoot() {
|
||||
if (this.mg === null || this.warship === null || this.target === null) {
|
||||
if (
|
||||
this.mg === null ||
|
||||
this.warship === null ||
|
||||
this.target === undefined
|
||||
) {
|
||||
throw new Error("Warship not initialized");
|
||||
}
|
||||
const shellAttackRate = this.mg.config().warshipShellAttackRate();
|
||||
@@ -89,7 +93,7 @@ export class WarshipExecution implements Execution {
|
||||
if (!this.target.hasHealth()) {
|
||||
// Don't send multiple shells to target that can be oneshotted
|
||||
this.alreadySentShell.add(this.target);
|
||||
this.target = null;
|
||||
this.target = undefined;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -105,8 +109,11 @@ export class WarshipExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.warship.setWarshipTarget(this.target);
|
||||
if (this.target === null || this.target.type() !== UnitType.TradeShip) {
|
||||
this.warship.setTargetUnit(this.target);
|
||||
if (
|
||||
this.target === undefined ||
|
||||
this.target.type() !== UnitType.TradeShip
|
||||
) {
|
||||
// Patrol unless we are hunting down a tradeship
|
||||
const result = this.pathfinder.nextTile(
|
||||
this.warship.tile(),
|
||||
@@ -115,13 +122,13 @@ export class WarshipExecution implements Execution {
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this.patrolTile = undefined;
|
||||
this.warship.move(this.warship.tile());
|
||||
this.warship.touch();
|
||||
break;
|
||||
case PathFindResultType.NextTile:
|
||||
this.warship.move(result.tile);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
this.warship.move(this.warship.tile());
|
||||
this.warship.touch();
|
||||
return;
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.log(`path not found to patrol tile`);
|
||||
@@ -153,13 +160,12 @@ export class WarshipExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
if (this.target !== null && !this.target.isActive()) {
|
||||
this.target = null;
|
||||
if (this.target !== undefined && !this.target.isActive()) {
|
||||
this.target = undefined;
|
||||
}
|
||||
const hasPort = this._owner.units(UnitType.Port).length > 0;
|
||||
if (this.mg === null) throw new Error("Game not initialized");
|
||||
const warship = this.warship;
|
||||
if (warship === null) throw new Error("Warship not initialized");
|
||||
if (warship === undefined) throw new Error("Warship not initialized");
|
||||
const ships = this.mg
|
||||
.nearbyUnits(
|
||||
this.warship.tile(),
|
||||
@@ -175,68 +181,67 @@ export class WarshipExecution implements Execution {
|
||||
(unit.type() !== UnitType.TradeShip ||
|
||||
(hasPort &&
|
||||
this.warship !== null &&
|
||||
unit.dstPort()?.owner() !== this.warship.owner() &&
|
||||
!unit.dstPort()?.owner().isFriendly(this.warship.owner()) &&
|
||||
unit.targetUnit()?.owner() !== this.warship.owner() &&
|
||||
!unit.targetUnit()?.owner().isFriendly(this.warship.owner()) &&
|
||||
unit.isSafeFromPirates() !== true)),
|
||||
);
|
||||
|
||||
this.target =
|
||||
ships.sort((a, b) => {
|
||||
const { unit: unitA, distSquared: distA } = a;
|
||||
const { unit: unitB, distSquared: distB } = b;
|
||||
this.target = ships.sort((a, b) => {
|
||||
const { unit: unitA, distSquared: distA } = a;
|
||||
const { unit: unitB, distSquared: distB } = b;
|
||||
|
||||
// Prioritize Warships
|
||||
if (
|
||||
unitA.type() === UnitType.Warship &&
|
||||
unitB.type() !== UnitType.Warship
|
||||
)
|
||||
return -1;
|
||||
if (
|
||||
unitA.type() !== UnitType.Warship &&
|
||||
unitB.type() === UnitType.Warship
|
||||
)
|
||||
return 1;
|
||||
// Prioritize Warships
|
||||
if (
|
||||
unitA.type() === UnitType.Warship &&
|
||||
unitB.type() !== UnitType.Warship
|
||||
)
|
||||
return -1;
|
||||
if (
|
||||
unitA.type() !== UnitType.Warship &&
|
||||
unitB.type() === UnitType.Warship
|
||||
)
|
||||
return 1;
|
||||
|
||||
// Then favor Transport Ships over Trade Ships
|
||||
if (
|
||||
unitA.type() === UnitType.TransportShip &&
|
||||
unitB.type() !== UnitType.TransportShip
|
||||
)
|
||||
return -1;
|
||||
if (
|
||||
unitA.type() !== UnitType.TransportShip &&
|
||||
unitB.type() === UnitType.TransportShip
|
||||
)
|
||||
return 1;
|
||||
// Then favor Transport Ships over Trade Ships
|
||||
if (
|
||||
unitA.type() === UnitType.TransportShip &&
|
||||
unitB.type() !== UnitType.TransportShip
|
||||
)
|
||||
return -1;
|
||||
if (
|
||||
unitA.type() !== UnitType.TransportShip &&
|
||||
unitB.type() === UnitType.TransportShip
|
||||
)
|
||||
return 1;
|
||||
|
||||
// If both are the same type, sort by distance (lower `distSquared` means closer)
|
||||
return distA - distB;
|
||||
})[0]?.unit ?? null;
|
||||
// If both are the same type, sort by distance (lower `distSquared` means closer)
|
||||
return distA - distB;
|
||||
})[0]?.unit;
|
||||
|
||||
const moveTarget = this.warship.moveTarget();
|
||||
const moveTarget = this.warship.targetTile();
|
||||
if (moveTarget) {
|
||||
this.goToMoveTarget(moveTarget);
|
||||
// If we have a "move target" then we cannot target trade ships as it
|
||||
// requires moving.
|
||||
if (this.target && this.target.type() === UnitType.TradeShip) {
|
||||
this.target = null;
|
||||
this.target = undefined;
|
||||
}
|
||||
} else if (!this.target || this.target.type() !== UnitType.TradeShip) {
|
||||
this.patrol();
|
||||
}
|
||||
|
||||
if (
|
||||
this.target === null ||
|
||||
this.target === undefined ||
|
||||
!this.target.isActive() ||
|
||||
this.target.owner() === this._owner ||
|
||||
this.target.isSafeFromPirates() === true
|
||||
) {
|
||||
// In case another warship captured or destroyed target, or the target escaped into safe waters
|
||||
this.target = null;
|
||||
this.target = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
this.warship.setWarshipTarget(this.target);
|
||||
this.warship.setTargetUnit(this.target);
|
||||
|
||||
// If we have a move target we do not want to go after trading ships
|
||||
if (!this.target) {
|
||||
@@ -258,7 +263,7 @@ export class WarshipExecution implements Execution {
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this._owner.captureUnit(this.target);
|
||||
this.target = null;
|
||||
this.target = undefined;
|
||||
this.warship.move(this.warship.tile());
|
||||
return;
|
||||
case PathFindResultType.NextTile:
|
||||
|
||||
+30
-32
@@ -322,57 +322,55 @@ export class PlayerInfo {
|
||||
}
|
||||
|
||||
export interface Unit {
|
||||
// Common properties.
|
||||
id(): number;
|
||||
|
||||
// Properties
|
||||
type(): UnitType;
|
||||
troops(): number;
|
||||
owner(): Player;
|
||||
info(): UnitInfo;
|
||||
|
||||
// Location
|
||||
delete(displayerMessage?: boolean): void;
|
||||
tile(): TileRef;
|
||||
lastTile(): TileRef;
|
||||
move(tile: TileRef): void;
|
||||
|
||||
// State
|
||||
isActive(): boolean;
|
||||
setOwner(owner: Player): void;
|
||||
touch(): void;
|
||||
toUpdate(): UnitUpdate;
|
||||
|
||||
// Targeting
|
||||
setTargetTile(cell: TileRef | undefined): void;
|
||||
targetTile(): TileRef | undefined;
|
||||
setTargetUnit(unit: Unit | undefined): void;
|
||||
targetUnit(): Unit | undefined;
|
||||
setTargetedBySAM(targeted: boolean): void;
|
||||
targetedBySAM(): boolean;
|
||||
|
||||
// Health
|
||||
hasHealth(): boolean;
|
||||
health(): number;
|
||||
modifyHealth(delta: number): void;
|
||||
|
||||
setWarshipTarget(target: Unit | null): void; // warship only
|
||||
warshipTarget(): Unit | null;
|
||||
// Troops
|
||||
setTroops(troops: number): void;
|
||||
troops(): number;
|
||||
|
||||
setOwner(owner: Player): void;
|
||||
setCooldown(triggerCooldown: boolean): void;
|
||||
ticksLeftInCooldown(cooldownDuration: number): Tick;
|
||||
isCooldown(): boolean;
|
||||
setDstPort(dstPort: Unit): void;
|
||||
dstPort(): Unit | null; // Only for trade ships
|
||||
// --- UNIT SPECIFIC ---
|
||||
|
||||
// SAMs & Missile Silos
|
||||
launch(): void;
|
||||
ticksLeftInCooldown(): Tick | undefined;
|
||||
isInCooldown(): boolean;
|
||||
|
||||
// Trade Ships
|
||||
setSafeFromPirates(): void; // Only for trade ships
|
||||
isSafeFromPirates(): boolean; // Only for trade ships
|
||||
detonationDst(): TileRef | null; // Only for nukes
|
||||
|
||||
setMoveTarget(cell: TileRef | null): void;
|
||||
moveTarget(): TileRef | null;
|
||||
|
||||
setTargetedBySAM(targeted: boolean): void;
|
||||
targetedBySAM(): boolean;
|
||||
|
||||
// Mutations
|
||||
setTroops(troops: number): void;
|
||||
delete(displayerMessage?: boolean): void;
|
||||
|
||||
// Only for Construction type
|
||||
// Construction
|
||||
constructionType(): UnitType | null;
|
||||
setConstructionType(type: UnitType): void;
|
||||
|
||||
// Updates
|
||||
toUpdate(): UnitUpdate;
|
||||
|
||||
cachePut(from: TileRef, to: TileRef): void; // ports only
|
||||
cacheGet(from: TileRef): TileRef | undefined; // ports only
|
||||
// Ports
|
||||
cachePut(from: TileRef, to: TileRef): void;
|
||||
cacheGet(from: TileRef): TileRef | undefined;
|
||||
}
|
||||
|
||||
export interface TerraNullius {
|
||||
|
||||
@@ -73,9 +73,8 @@ export interface UnitUpdate {
|
||||
pos: TileRef;
|
||||
lastPos: TileRef;
|
||||
isActive: boolean;
|
||||
dstPortId?: number; // Only for trade ships
|
||||
detonationDst?: TileRef; // Only for nukes
|
||||
warshipTargetId?: number;
|
||||
targetUnitId?: number; // Only for trade ships
|
||||
targetTile?: TileRef; // Only for nukes
|
||||
health?: number;
|
||||
constructionType?: UnitType;
|
||||
ticksLeftInCooldown?: Tick;
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
GameUpdates,
|
||||
Gold,
|
||||
NameViewData,
|
||||
nukeTypes,
|
||||
Player,
|
||||
PlayerActions,
|
||||
PlayerBorderTiles,
|
||||
@@ -83,7 +82,7 @@ export class UnitView {
|
||||
return this.data.pos;
|
||||
}
|
||||
owner(): PlayerView {
|
||||
return this.gameView.playerBySmallID(this.data.ownerID) as PlayerView;
|
||||
return this.gameView.playerBySmallID(this.data.ownerID)! as PlayerView;
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.data.isActive;
|
||||
@@ -97,20 +96,11 @@ export class UnitView {
|
||||
constructionType(): UnitType | undefined {
|
||||
return this.data.constructionType;
|
||||
}
|
||||
dstPortId(): number | undefined {
|
||||
return this.data.dstPortId;
|
||||
targetUnitId(): number | undefined {
|
||||
return this.data.targetUnitId;
|
||||
}
|
||||
detonationDst(): TileRef | undefined {
|
||||
if (!nukeTypes.includes(this.type())) {
|
||||
throw Error("Must be a nuke");
|
||||
}
|
||||
return this.data.detonationDst;
|
||||
}
|
||||
warshipTargetId(): number | undefined {
|
||||
if (this.type() !== UnitType.Warship) {
|
||||
throw Error("Must be a warship");
|
||||
}
|
||||
return this.data.warshipTargetId;
|
||||
targetTile(): TileRef | undefined {
|
||||
return this.data.targetTile;
|
||||
}
|
||||
ticksLeftInCooldown(): Tick | undefined {
|
||||
return this.data.ticksLeftInCooldown;
|
||||
|
||||
@@ -796,7 +796,7 @@ export class PlayerImpl implements Player {
|
||||
// only get missilesilos that are not on cooldown
|
||||
const spawns = this.units(UnitType.MissileSilo)
|
||||
.filter((silo) => {
|
||||
return !silo.isCooldown();
|
||||
return !silo.isInCooldown();
|
||||
})
|
||||
.sort(distSortUnit(this.mg, tile));
|
||||
if (spawns.length === 0) {
|
||||
|
||||
+44
-58
@@ -14,20 +14,16 @@ import { PlayerImpl } from "./PlayerImpl";
|
||||
|
||||
export class UnitImpl implements Unit {
|
||||
private _active = true;
|
||||
private _targetTile: TileRef | undefined;
|
||||
private _targetUnit: Unit | undefined;
|
||||
private _health: bigint;
|
||||
private _lastTile: TileRef;
|
||||
private _moveTarget: TileRef | null = null;
|
||||
private _targetedBySAM = false;
|
||||
private _safeFromPiratesCooldown: number; // Only for trade ships
|
||||
private _lastSetSafeFromPirates: number; // Only for trade ships
|
||||
private _constructionType: UnitType | undefined;
|
||||
private _lastOwner: PlayerImpl | null = null;
|
||||
private _troops: number;
|
||||
private _cooldownTick: Tick | null = null;
|
||||
private _dstPort: Unit | undefined = undefined; // Only for trade ships
|
||||
private _detonationDst: TileRef | undefined = undefined; // Only for nukes
|
||||
private _warshipTarget: Unit | undefined = undefined;
|
||||
private _cooldownDuration: number | undefined = undefined;
|
||||
private _cooldownStartTick: Tick | null = null;
|
||||
private _pathCache: Map<TileRef, TileRef> = new Map();
|
||||
|
||||
constructor(
|
||||
@@ -40,19 +36,22 @@ export class UnitImpl implements Unit {
|
||||
) {
|
||||
this._lastTile = _tile;
|
||||
this._health = toInt(this.mg.unitInfo(_type).maxHealth ?? 1);
|
||||
this._safeFromPiratesCooldown = this.mg
|
||||
.config()
|
||||
.safeFromPiratesCooldownMax();
|
||||
|
||||
this._troops = "troops" in params ? (params.troops ?? 0) : 0;
|
||||
this._dstPort = "dstPort" in params ? params.dstPort : undefined;
|
||||
this._cooldownDuration =
|
||||
"cooldownDuration" in params ? params.cooldownDuration : undefined;
|
||||
this._lastSetSafeFromPirates =
|
||||
"lastSetSafeFromPirates" in params
|
||||
? (params.lastSetSafeFromPirates ?? 0)
|
||||
: 0;
|
||||
}
|
||||
touch(): void {
|
||||
this.mg.addUpdate(this.toUpdate());
|
||||
}
|
||||
setTileTarget(tile: TileRef | undefined): void {
|
||||
this._targetTile = tile;
|
||||
}
|
||||
tileTarget(): TileRef | undefined {
|
||||
return this._targetTile;
|
||||
}
|
||||
|
||||
cachePut(from: TileRef, to: TileRef): void {
|
||||
this._pathCache.set(from, to);
|
||||
@@ -66,13 +65,6 @@ export class UnitImpl implements Unit {
|
||||
}
|
||||
|
||||
toUpdate(): UnitUpdate {
|
||||
const warshipTarget = this.warshipTarget();
|
||||
const dstPort = this.dstPort();
|
||||
if (this._lastTile === null) throw new Error("null _lastTile");
|
||||
const ticksLeftInCooldown =
|
||||
this._cooldownDuration !== undefined
|
||||
? this.ticksLeftInCooldown(this._cooldownDuration)
|
||||
: undefined;
|
||||
return {
|
||||
type: GameUpdateType.Unit,
|
||||
unitType: this._type,
|
||||
@@ -85,10 +77,9 @@ export class UnitImpl implements Unit {
|
||||
lastPos: this._lastTile,
|
||||
health: this.hasHealth() ? Number(this._health) : undefined,
|
||||
constructionType: this._constructionType,
|
||||
dstPortId: dstPort?.id() ?? undefined,
|
||||
warshipTargetId: warshipTarget?.id() ?? undefined,
|
||||
detonationDst: this.detonationDst() ?? undefined,
|
||||
ticksLeftInCooldown,
|
||||
targetUnitId: this._targetUnit?.id() ?? undefined,
|
||||
targetTile: this.targetTile() ?? undefined,
|
||||
ticksLeftInCooldown: this.ticksLeftInCooldown() ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -97,7 +88,6 @@ export class UnitImpl implements Unit {
|
||||
}
|
||||
|
||||
lastTile(): TileRef {
|
||||
if (this._lastTile === null) throw new Error("null _lastTile");
|
||||
return this._lastTile;
|
||||
}
|
||||
|
||||
@@ -111,6 +101,7 @@ export class UnitImpl implements Unit {
|
||||
this.mg.addUnit(this);
|
||||
this.mg.addUpdate(this.toUpdate());
|
||||
}
|
||||
|
||||
setTroops(troops: number): void {
|
||||
this._troops = troops;
|
||||
}
|
||||
@@ -203,52 +194,47 @@ export class UnitImpl implements Unit {
|
||||
return `Unit:${this._type},owner:${this.owner().name()}`;
|
||||
}
|
||||
|
||||
setWarshipTarget(target: Unit) {
|
||||
this._warshipTarget = target;
|
||||
launch(): void {
|
||||
this._cooldownStartTick = this.mg.ticks();
|
||||
this.mg.addUpdate(this.toUpdate());
|
||||
}
|
||||
|
||||
warshipTarget(): Unit | null {
|
||||
return this._warshipTarget ?? null;
|
||||
}
|
||||
|
||||
detonationDst(): TileRef | null {
|
||||
return this._detonationDst ?? null;
|
||||
}
|
||||
|
||||
dstPort(): Unit | null {
|
||||
return this._dstPort ?? null;
|
||||
}
|
||||
|
||||
// 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());
|
||||
ticksLeftInCooldown(): Tick | undefined {
|
||||
let cooldownDuration = 0;
|
||||
if (this.type() === UnitType.SAMLauncher) {
|
||||
cooldownDuration = this.mg.config().SAMCooldown();
|
||||
} else if (this.type() === UnitType.MissileSilo) {
|
||||
cooldownDuration = this.mg.config().SiloCooldown();
|
||||
} else {
|
||||
this._cooldownTick = null;
|
||||
this.mg.addUpdate(this.toUpdate());
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!this._cooldownStartTick) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return cooldownDuration - (this.mg.ticks() - this._cooldownStartTick);
|
||||
}
|
||||
|
||||
ticksLeftInCooldown(cooldownDuration: number): Tick {
|
||||
const cooldownTick = this._cooldownTick ?? 0;
|
||||
return Math.max(0, cooldownDuration - (this.mg.ticks() - cooldownTick));
|
||||
isInCooldown(): boolean {
|
||||
const ticksLeft = this.ticksLeftInCooldown();
|
||||
return ticksLeft !== undefined && ticksLeft > 0;
|
||||
}
|
||||
|
||||
isCooldown(): boolean {
|
||||
return this._cooldownTick ? true : false;
|
||||
setTargetTile(targetTile: TileRef | undefined) {
|
||||
this._targetTile = targetTile;
|
||||
}
|
||||
|
||||
setDstPort(dstPort: Unit): void {
|
||||
this._dstPort = dstPort;
|
||||
targetTile(): TileRef | undefined {
|
||||
return this._targetTile;
|
||||
}
|
||||
|
||||
setMoveTarget(moveTarget: TileRef) {
|
||||
this._moveTarget = moveTarget;
|
||||
setTargetUnit(target: Unit | undefined): void {
|
||||
this._targetUnit = target;
|
||||
}
|
||||
|
||||
moveTarget(): TileRef | null {
|
||||
return this._moveTarget;
|
||||
targetUnit(): Unit | undefined {
|
||||
return this._targetUnit;
|
||||
}
|
||||
|
||||
setTargetedBySAM(targeted: boolean): void {
|
||||
@@ -266,7 +252,7 @@ export class UnitImpl implements Unit {
|
||||
isSafeFromPirates(): boolean {
|
||||
return (
|
||||
this.mg.ticks() - this._lastSetSafeFromPirates <
|
||||
this._safeFromPiratesCooldown
|
||||
this.mg.config().safeFromPiratesCooldownMax()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,17 +73,19 @@ describe("MissileSilo", () => {
|
||||
});
|
||||
|
||||
test("missilesilo should cooldown as long as configured", async () => {
|
||||
expect(attacker.units(UnitType.MissileSilo)[0].isCooldown()).toBeFalsy();
|
||||
expect(attacker.units(UnitType.MissileSilo)[0].isInCooldown()).toBeFalsy();
|
||||
// send the nuke far enough away so it doesnt destroy the silo
|
||||
attackerBuildsNuke(null, game.ref(50, 50));
|
||||
expect(attacker.units(UnitType.AtomBomb)).toHaveLength(1);
|
||||
|
||||
for (let i = 0; i < game.config().SiloCooldown() - 1; i++) {
|
||||
for (let i = 0; i < game.config().SiloCooldown() - 2; i++) {
|
||||
game.executeNextTick();
|
||||
expect(attacker.units(UnitType.MissileSilo)[0].isCooldown()).toBeTruthy();
|
||||
expect(
|
||||
attacker.units(UnitType.MissileSilo)[0].isInCooldown(),
|
||||
).toBeTruthy();
|
||||
}
|
||||
|
||||
game.executeNextTick();
|
||||
expect(attacker.units(UnitType.MissileSilo)[0].isCooldown()).toBeFalsy();
|
||||
expect(attacker.units(UnitType.MissileSilo)[0].isInCooldown()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
+5
-5
@@ -78,7 +78,7 @@ describe("SAM", () => {
|
||||
test("sam should cooldown as long as configured", async () => {
|
||||
const sam = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {});
|
||||
game.addExecution(new SAMLauncherExecution(defender.id(), null, sam));
|
||||
expect(sam.isCooldown()).toBeFalsy();
|
||||
expect(sam.isInCooldown()).toBeFalsy();
|
||||
const nuke = attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 2), {
|
||||
detonationDst: game.ref(1, 2),
|
||||
});
|
||||
@@ -86,14 +86,14 @@ describe("SAM", () => {
|
||||
executeTicks(game, 3);
|
||||
|
||||
expect(nuke.isActive()).toBeFalsy();
|
||||
for (let i = 0; i < game.config().SAMCooldown() - 2; i++) {
|
||||
for (let i = 0; i < game.config().SAMCooldown() - 3; i++) {
|
||||
game.executeNextTick();
|
||||
expect(sam.isCooldown()).toBeTruthy();
|
||||
expect(sam.isInCooldown()).toBeTruthy();
|
||||
}
|
||||
|
||||
executeTicks(game, 2);
|
||||
|
||||
expect(sam.isCooldown()).toBeFalsy();
|
||||
expect(sam.isInCooldown()).toBeFalsy();
|
||||
});
|
||||
|
||||
test("two sams should not target twice same nuke", async () => {
|
||||
@@ -110,6 +110,6 @@ describe("SAM", () => {
|
||||
executeTicks(game, 3);
|
||||
|
||||
expect(nuke.isActive()).toBeFalsy();
|
||||
expect([sam1, sam2].filter((s) => s.isCooldown())).toHaveLength(1);
|
||||
expect([sam1, sam2].filter((s) => s.isInCooldown())).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user