mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-23 13:37:20 +00:00
2661 PR 3/3 Warship Manual Override, Aggro Override, and Heal-at-Port Command (#3501)
Part of [#2661](https://github.com/openfrontio/OpenFrontIO/issues/2661) (split into 3 PRs so they are not too large..) ## Description: Part 3/3 of [#2661](https://github.com/openfrontio/OpenFrontIO/issues/2661). This PR adds the retreat control and override behavior for warships: - Manual override: moving a warship manually cancels retreat and suppresses auto-retreat for 5 seconds - Aggro override: a retreating warship will aggro a nearby enemy transport or warship before continuing retreat - Heal-at-port command for sending a warship to a friendly port manually - Friendly-port validation for HealAtPortExecution - Regression tests for manual override, aggro override, and heal-at-port behavior ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: zixer._ --------- Co-authored-by: iamlewis <lewismmmm@gmail.com> Co-authored-by: evanpelle <evanpelle@gmail.com>
This commit is contained in:
co-authored by
iamlewis
evanpelle
parent
f1d0136a06
commit
742a544a69
+95
-22
@@ -6,9 +6,11 @@ import {
|
||||
Tick,
|
||||
TrainType,
|
||||
TrajectoryTile,
|
||||
TransportShipState,
|
||||
Unit,
|
||||
UnitInfo,
|
||||
UnitType,
|
||||
WarshipState,
|
||||
} from "./Game";
|
||||
import { GameImpl } from "./GameImpl";
|
||||
import { TileRef } from "./GameMap";
|
||||
@@ -21,7 +23,8 @@ export class UnitImpl implements Unit {
|
||||
private _targetUnit: Unit | undefined;
|
||||
private _health: bigint;
|
||||
private _lastTile: TileRef;
|
||||
private _retreating: boolean = false;
|
||||
private _transportShipState: TransportShipState | undefined = undefined;
|
||||
private _warshipState: WarshipState | undefined = undefined;
|
||||
private _targetedBySAM = false;
|
||||
private _reachedTarget = false;
|
||||
private _wasDestroyedByEnemy: boolean = false;
|
||||
@@ -33,7 +36,6 @@ export class UnitImpl implements Unit {
|
||||
// Number of missiles in cooldown, if empty all missiles are ready.
|
||||
private _missileTimerQueue: number[] = [];
|
||||
private _hasTrainStation: boolean = false;
|
||||
private _patrolTile: TileRef | undefined;
|
||||
private _level: number = 1;
|
||||
private _targetable: boolean = true;
|
||||
private _loaded: boolean | undefined;
|
||||
@@ -61,8 +63,16 @@ export class UnitImpl implements Unit {
|
||||
"lastSetSafeFromPirates" in params
|
||||
? (params.lastSetSafeFromPirates ?? 0)
|
||||
: 0;
|
||||
this._patrolTile =
|
||||
"patrolTile" in params ? (params.patrolTile ?? undefined) : undefined;
|
||||
if (this._type === UnitType.TransportShip) {
|
||||
this._transportShipState = { isRetreating: false, troops: 0 };
|
||||
}
|
||||
if ("patrolTile" in params) {
|
||||
this._warshipState = {
|
||||
state: "patrolling",
|
||||
patrolTile: params.patrolTile,
|
||||
lastCombatTick: -100,
|
||||
};
|
||||
}
|
||||
this._targetUnit =
|
||||
"targetUnit" in params ? (params.targetUnit ?? undefined) : undefined;
|
||||
this._loaded =
|
||||
@@ -92,14 +102,6 @@ export class UnitImpl implements Unit {
|
||||
return this._targetable;
|
||||
}
|
||||
|
||||
setPatrolTile(tile: TileRef): void {
|
||||
this._patrolTile = tile;
|
||||
}
|
||||
|
||||
patrolTile(): TileRef | undefined {
|
||||
return this._patrolTile;
|
||||
}
|
||||
|
||||
isUnit(): this is Unit {
|
||||
return true;
|
||||
}
|
||||
@@ -128,7 +130,14 @@ export class UnitImpl implements Unit {
|
||||
lastOwnerID: this._lastOwner?.smallID(),
|
||||
isActive: this._active,
|
||||
reachedTarget: this._reachedTarget,
|
||||
retreating: this._retreating,
|
||||
warshipState:
|
||||
this._warshipState !== undefined
|
||||
? { ...this.warshipState() }
|
||||
: undefined,
|
||||
transportShipState:
|
||||
this._transportShipState !== undefined
|
||||
? this.transportShipState()
|
||||
: undefined,
|
||||
pos: this._tile,
|
||||
markedForDeletion: this._deletionAt ?? false,
|
||||
targetable: this._targetable,
|
||||
@@ -232,6 +241,13 @@ export class UnitImpl implements Unit {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
attacker !== undefined &&
|
||||
delta < 0 &&
|
||||
this._warshipState !== undefined
|
||||
) {
|
||||
this._warshipState.lastCombatTick = this.mg.ticks();
|
||||
}
|
||||
this._health = nextHealth;
|
||||
this.mg.addUpdate(this.toUpdate());
|
||||
if (this._health === 0n) {
|
||||
@@ -335,22 +351,79 @@ export class UnitImpl implements Unit {
|
||||
return this._destroyer;
|
||||
}
|
||||
|
||||
retreating(): boolean {
|
||||
return this._retreating;
|
||||
warshipState(): WarshipState {
|
||||
if (this._warshipState === undefined) {
|
||||
throw new Error("warshipState called on non-warship unit");
|
||||
}
|
||||
this._warshipState.isInCombat = this.isInCombat();
|
||||
return this._warshipState;
|
||||
}
|
||||
|
||||
setRetreating(retreating: boolean): void {
|
||||
if (this._retreating !== retreating) {
|
||||
this._retreating = retreating;
|
||||
updateWarshipState(update: Partial<WarshipState>): void {
|
||||
if (this._warshipState === undefined) {
|
||||
throw new Error("updateWarshipState called on non-warship unit");
|
||||
}
|
||||
if (update.isInCombat) {
|
||||
this.markInCombat();
|
||||
}
|
||||
const merged = { ...this._warshipState, ...update };
|
||||
if (
|
||||
merged.state === this._warshipState.state &&
|
||||
merged.patrolTile === this._warshipState.patrolTile &&
|
||||
merged.retreatPort === this._warshipState.retreatPort
|
||||
)
|
||||
return;
|
||||
this._warshipState = {
|
||||
state: merged.state,
|
||||
patrolTile: merged.patrolTile,
|
||||
retreatPort: merged.retreatPort,
|
||||
lastCombatTick: this._warshipState.lastCombatTick,
|
||||
};
|
||||
this.mg.addUpdate(this.toUpdate());
|
||||
}
|
||||
|
||||
isInCombat(): boolean {
|
||||
return this.mg.ticks() - this._warshipState!.lastCombatTick <= 3;
|
||||
}
|
||||
|
||||
private markInCombat(): void {
|
||||
const wasInCombat = this.isInCombat();
|
||||
this._warshipState!.lastCombatTick = this.mg.ticks();
|
||||
if (!wasInCombat) {
|
||||
this.mg.addUpdate(this.toUpdate());
|
||||
}
|
||||
}
|
||||
|
||||
orderBoatRetreat() {
|
||||
if (this.type() !== UnitType.TransportShip) {
|
||||
throw new Error("Cannot retreat " + this.type());
|
||||
transportShipState(): TransportShipState {
|
||||
if (this._transportShipState === undefined) {
|
||||
throw new Error("transportShipState called on non-transport-ship unit");
|
||||
}
|
||||
return {
|
||||
isRetreating: this._transportShipState.isRetreating,
|
||||
troops: this._troops,
|
||||
};
|
||||
}
|
||||
|
||||
updateTransportShipState(update: Partial<TransportShipState>): void {
|
||||
if (this._transportShipState === undefined) {
|
||||
throw new Error(
|
||||
"updateTransportShipState called on non-transport-ship unit",
|
||||
);
|
||||
}
|
||||
let changed = false;
|
||||
if (
|
||||
update.isRetreating !== undefined &&
|
||||
this._transportShipState.isRetreating !== update.isRetreating
|
||||
) {
|
||||
this._transportShipState = {
|
||||
...this._transportShipState,
|
||||
isRetreating: update.isRetreating,
|
||||
};
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
this.mg.addUpdate(this.toUpdate());
|
||||
}
|
||||
this.setRetreating(true);
|
||||
}
|
||||
|
||||
isUnderConstruction(): boolean {
|
||||
|
||||
Reference in New Issue
Block a user