2661 PR 3/3 Warship Manual Override, Aggro Override, and Heal-at-Port Command#3501
2661 PR 3/3 Warship Manual Override, Aggro Override, and Heal-at-Port Command#3501Zixer1 wants to merge 14 commits intoopenfrontio:mainfrom
Conversation
WalkthroughThe changes introduce a warship healing and retreat system. Warships can now retreat to friendly ports for healing based on configurable health thresholds and healing rates. Port capacity management, docking mechanics, and visual indicators (blue for retreating warships) enable this feature across configuration, core game logic, execution orders, and rendering layers. Changes
Sequence DiagramsequenceDiagram
participant W as Warship
participant GE as Game Engine
participant PS as Port Selector
participant P as Port
participant Heal as Healing System
W->>GE: tick() with health check
alt Health below retreat threshold
GE->>PS: select friendly port
PS->>P: evaluate capacity & distance
PS-->>GE: return best port
GE->>W: setRetreating(true)<br/>setTargetTile(port)
end
alt Retreating & near port
GE->>W: move toward port via pathfinding
W->>P: enter healing radius
GE->>Heal: apply docked healing<br/>(fractional accumulation)
Heal->>W: modifyHealth(+bonus)
end
alt Fully healed or port destroyed
GE->>W: setRetreating(false)<br/>clear docking state
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (4)
src/core/execution/WarshipExecution.ts (1)
501-532: Consider renaming for clarity.Both
findNearestAvailablePort()andfindNearestAvailablePortTile()returnTileRef, but only one includes "Tile" in the name. The difference is whetherthis.warshipis excluded from the capacity check.A clearer naming might be:
findNearestAvailablePortTile()→ stays as-isfindNearestAvailablePort()→findNearestAvailablePortTileForSwitch()or simply inline the difference with a parameterThis is a minor readability improvement and can be addressed later.
Also applies to: 585-618
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/core/execution/WarshipExecution.ts` around lines 501 - 532, The two similarly named methods cause confusion — findNearestAvailablePort() and findNearestAvailablePortTile() both return TileRef but differ only in whether the warship is excluded from the capacity check; rename or unify them to make intent explicit. Either rename findNearestAvailablePort() to findNearestAvailablePortTileForSwitch() (or another clear name) and update all callers, or replace both with a single method findNearestAvailablePortTile(includeSelfInCapacityCheck: boolean) and update callers to pass the appropriate flag; ensure you update references to these symbols (findNearestAvailablePort, findNearestAvailablePortTile) and any logic that relies on isPortFullOfHealing so behavior remains identical after the rename/merge.src/core/execution/HealAtPortExecution.ts (1)
35-41: Consider addingisActive()check on the port.The current check verifies the owner has a port at
portTile, but does not confirm the port is still active. If a port is destroyed in the same tick (before this execution runs), the warship could be sent to a dead port.This is likely benign since
WarshipExecutionwill detect the missing port and cancel the retreat (as shown in the tests). Still, adding the check makes the validation more robust and fail-fast.🔧 Proposed fix
const isFriendlyPort = this.owner .units(UnitType.Port) - .some((port) => port.tile() === this.portTile); + .some((port) => port.tile() === this.portTile && port.isActive()); if (!isFriendlyPort) { console.warn("HealAtPortExecution: target port is not friendly"); return; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/core/execution/HealAtPortExecution.ts` around lines 35 - 41, The current check in HealAtPortExecution that finds a friendly port via this.owner.units(UnitType.Port).some(port => port.tile() === this.portTile) does not verify the port is active; update the validation to also call port.isActive() (or equivalent) so only active ports are considered (e.g., change the predicate to check both port.tile() === this.portTile and port.isActive()). Ensure the early return/log stays in place when no active friendly port is found so the execution fails fast.tests/Warship.test.ts (2)
662-674: Timing assumption: 50 ticks = 5 seconds.The test assumes a specific tick rate (10 ticks/second). If the game's tick rate differs or changes, this test could give false positives/negatives. Consider adding a comment documenting this assumption, or deriving the tick count from a config value if available.
+ // 48 ticks is just under 5 seconds at 10 ticks/sec; retreat should still be suppressed executeTicks(game, 48); expect(warship.retreating()).toBe(false); + // After 50+ ticks, suppression expires and retreat should resume let resumedRetreat = false; for (let i = 0; i < 5; i++) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Warship.test.ts` around lines 662 - 674, The test in Warship.test.ts relies on a hardcoded tick count (executeTicks(game, 48) and looping 5 times) which assumes 10 ticks/sec; update the test to avoid brittle timing by either (a) adding a clear comment above the block stating the 10 ticks/sec assumption and why 48/5 were chosen, or (b) derive the tick counts from the game's tick-rate configuration (use any available symbol like game.tickRate, game.TICKS_PER_SECOND, or a test helper) and compute the wait as Math.ceil(desiredSeconds * ticksPerSecond) before calling executeTicks or game.executeNextTick in the loop, and ensure warship.retreating() is checked against that computed duration.
471-476: Fragile cast to access private internals.The
as unknown aspattern works for testing private behavior, but it will break silently if internal method/property names change. Consider adding a comment noting this coupling, or extracting a test-only interface if this pattern grows.+ // NOTE: Accessing private internals for testing. Update if WarshipExecution internals change. const executionInternals = execution as unknown as { findTargetUnit: () => typeof transport | undefined; pathfinder: { next: () => { status: PathStatus; node: number }; }; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Warship.test.ts` around lines 471 - 476, The test currently uses a fragile cast "as unknown as { findTargetUnit: ..., pathfinder: { next: ... } }" to access private internals of the execution object; update the test to avoid silent breakage by extracting a named test-only interface (e.g., ExecutionInternals) that exposes findTargetUnit and pathfinder.next (and PathStatus), then cast execution to that interface, or at minimum add a clear comment above the cast explaining the coupling and that renaming these internals requires updating the test; reference the execution variable, executionInternals alias, findTargetUnit, pathfinder, next and PathStatus when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/core/execution/HealAtPortExecution.ts`:
- Around line 35-41: The current check in HealAtPortExecution that finds a
friendly port via this.owner.units(UnitType.Port).some(port => port.tile() ===
this.portTile) does not verify the port is active; update the validation to also
call port.isActive() (or equivalent) so only active ports are considered (e.g.,
change the predicate to check both port.tile() === this.portTile and
port.isActive()). Ensure the early return/log stays in place when no active
friendly port is found so the execution fails fast.
In `@src/core/execution/WarshipExecution.ts`:
- Around line 501-532: The two similarly named methods cause confusion —
findNearestAvailablePort() and findNearestAvailablePortTile() both return
TileRef but differ only in whether the warship is excluded from the capacity
check; rename or unify them to make intent explicit. Either rename
findNearestAvailablePort() to findNearestAvailablePortTileForSwitch() (or
another clear name) and update all callers, or replace both with a single method
findNearestAvailablePortTile(includeSelfInCapacityCheck: boolean) and update
callers to pass the appropriate flag; ensure you update references to these
symbols (findNearestAvailablePort, findNearestAvailablePortTile) and any logic
that relies on isPortFullOfHealing so behavior remains identical after the
rename/merge.
In `@tests/Warship.test.ts`:
- Around line 662-674: The test in Warship.test.ts relies on a hardcoded tick
count (executeTicks(game, 48) and looping 5 times) which assumes 10 ticks/sec;
update the test to avoid brittle timing by either (a) adding a clear comment
above the block stating the 10 ticks/sec assumption and why 48/5 were chosen, or
(b) derive the tick counts from the game's tick-rate configuration (use any
available symbol like game.tickRate, game.TICKS_PER_SECOND, or a test helper)
and compute the wait as Math.ceil(desiredSeconds * ticksPerSecond) before
calling executeTicks or game.executeNextTick in the loop, and ensure
warship.retreating() is checked against that computed duration.
- Around line 471-476: The test currently uses a fragile cast "as unknown as {
findTargetUnit: ..., pathfinder: { next: ... } }" to access private internals of
the execution object; update the test to avoid silent breakage by extracting a
named test-only interface (e.g., ExecutionInternals) that exposes findTargetUnit
and pathfinder.next (and PathStatus), then cast execution to that interface, or
at minimum add a clear comment above the cast explaining the coupling and that
renaming these internals requires updating the test; reference the execution
variable, executionInternals alias, findTargetUnit, pathfinder, next and
PathStatus when making the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f9478eee-e777-41d9-aaa3-71faa373f62a
📒 Files selected for processing (10)
src/client/graphics/layers/UnitLayer.tssrc/core/configuration/Config.tssrc/core/configuration/DefaultConfig.tssrc/core/execution/HealAtPortExecution.tssrc/core/execution/MoveWarshipExecution.tssrc/core/execution/WarshipExecution.tssrc/core/game/Game.tssrc/core/game/GameView.tssrc/core/game/UnitImpl.tstests/Warship.test.ts
Part of #2661 (split into 3 PRs so they are not too large..)
Description:
Part 3/3 of #2661.
This PR adds the retreat control and override behavior for warships:
Please complete the following:
Please put your Discord username so you can be contacted if a bug or regression is found:
zixer._