mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-02 17:38:05 +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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user