Skip to content

Conversation

@Hdt80bro
Copy link
Contributor

@Hdt80bro Hdt80bro commented Nov 13, 2025

This PR refactors the AIBrain's defeat code. The main change is moving the unit destruction logic to be all in one spot, in SimUtils.lua, no matter how the brain was defeated.

In the process, in was discovered that the armies are handled differently in a recall (it transferred only built units before the 10 seconds wait - it now also transfers unbuilt units, and after the 10 grace period like in a regular defeat), so that has now been standardized. TransferUnitsToKiller was also using the incorrect callback in one particular case.

Summary by CodeRabbit

  • Bug Fixes

    • Defeat/recall/disconnect flows now transfer units consistently and behave more reliably at end of game.
  • New Features

    • Utilities for safer unit transfers, timed waits, and targeted handling of unsafe/abandoned commanders.
    • Kennel pause/upgrade and unit-upgrade behaviors for transferred units.
    • Lobby option types refined for clearer share/disconnect choices.
    • End-of-game delay reduced to 1 second (configurable constant).
  • Refactor

    • Centralized defeat/recall state and consolidated army/unit management paths.
  • Documentation

    • Added changelog entry documenting the fix.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 13, 2025

Warning

Rate limit exceeded

@lL1l1 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 14 minutes and 31 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 6b9ba0b and f760bea.

📒 Files selected for processing (2)
  • changelog/snippets/fix.6985.md (1 hunks)
  • lua/SimUtils.lua (28 hunks)

Walkthrough

Centralizes defeat/recall/abandon handling via AIBrain:SetDefeatStatus and threaded Kill* handlers; adds many SimUtils helpers for transfers, upgrades, waits, and kill flows; tightens scope/type annotations; adds lobby share type aliases; and reduces end-game delay default to 1 second.

Changes

Cohort / File(s) Summary
Changelog
changelog/snippets/fix.6965.md
New changelog snippet documenting the defeat backend refactor and consistent unit transfer behavior.
Engine types
engine/Sim.lua
Adjusted GetGameTick() return annotation from number to integer.
Scope fix
lua/SimSync.lua
Made cdDuration local in StartCountdown to avoid global leakage.
Sim utilities
lua/SimUtils.lua
Large additions/refactor: new helpers (PauseTransferredKennels, UpgradeTransferredKennels, UpgradeUnits, KillUnsafeCommanders, KillArmyOnDelayedRecall, KillArmyOnACUDeath, KillRecalledArmy, KillAbandonedArmy, WaitUntilUnitsDeadOrTimeout), new constants (EndGameGracePeriod, CommanderSafeTime, MinimumShareTime), safer transfer/ownership flows, kennel/drone handling, upgrade/rebuild orchestration, and many nil-safety/iteration improvements.
AIBrain integration
lua/aibrain.lua
Added SetDefeatStatus(self, status); refactored defeat/recall/abandon flows to call SetDefeatStatus and fork Kill* threads; changed Army field annotation to integer; re-exposed several SimUtils helpers for compatibility.
Lobby option types
lua/ui/lobby/lobbyOptions.lua
Added type aliases ShareOption, DisconnectShareOption, DisconnectShareCommandersOption; updated GameOptions fields to use these aliases.
Victory timing
lua/sim/VictoryCondition/AbstractVictoryCondition.lua
Reduced DelayBeforeGameEnds default from 3 to 1 and used the field in EndGameThread.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Player
    participant AIBrain
    participant SimUtils
    participant Engine
    Note over Player,AIBrain: Player or game triggers defeat/recall/abandon
    Player->>AIBrain: trigger defeat/recall/abandon
    AIBrain->>AIBrain: SetDefeatStatus(status)
    AIBrain->>SimUtils: ForkThread(KillAbandonedArmy / KillRecalledArmy / KillArmyOnDelayedRecall, args)
    alt Transfer & upgrade phase
      SimUtils->>Engine: TransferUnitsToBrain / TransferUnitsToHighestBrain
      SimUtils->>Engine: GiveResourcesToPlayer / UpdateUnitCap
      SimUtils->>SimUtils: PauseTransferredKennels / UpgradeTransferredKennels / UpgradeUnits
    end
    alt Kill/cleanup phase
      SimUtils->>Engine: KillUnits / QueryUnitState
      SimUtils->>SimUtils: WaitUntilUnitsDeadOrTimeout
    end
    SimUtils-->>AIBrain: final state/update (defeat/recall processed)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus review on:
    • lua/SimUtils.lua: threading, race conditions, new public helpers, and engine interactions.
    • lua/aibrain.lua: SetDefeatStatus integration and interplay with forked Kill* threads.
    • Unit transfer/upgrade and kennel/drone handling paths for nil-safety and correctness.

Possibly related PRs

Poem

🐇 I hopped through code at dawn's soft light,
I nudged the troops to steer them right,
Threads tidy now, transfers neat,
Kennels pause and upgrades beat,
I thump with joy — the game's polite!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Simplify defeat backend' directly describes the main objective of refactoring the defeat code to centralize unit-destruction logic.
Description check ✅ Passed The description clearly explains the main changes and rationale: centralizing defeat logic in SimUtils.lua and standardizing recall behavior. However, the Testing and Checklist sections are not completed.
Docstring Coverage ✅ Passed Docstring coverage is 95.24% which is sufficient. The required threshold is 80.00%.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@lL1l1 lL1l1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks mostly good to me. Needs a snippet and after you unset the draft status I can approve it.

---@param shareOption 'FullShare' | 'ShareUntilDeath' | 'PartialShare' | 'TransferToKiller' | 'Defectors' | 'CivilianDeserter'
---@param shareTime number Game time in ticks
---@param shareOption ShareOption
---@param shareTime integer Game time in ticks
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
---@param shareTime integer Game time in ticks
---@param shareTime integer # Game time in ticks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand, the majority of the repo doesn't use this optional syntax

Copy link
Contributor

@lL1l1 lL1l1 Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to clean up the comment style for annotations (using a # or not) in a separate pr then, though that would be low priority.

lua/SimUtils.lua Outdated
Comment on lines 1250 to 1251
-- KillArmy waits 10 seconds before acting, while FakeTeleport waits 3 seconds, so the ACU shouldn't explode.
FakeTeleportUnits(safeCommanders, true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer forking the teleport as a thread means it always recalls the ACU before possibly killing it, so the comment isn't needed.

It also means that the grace period for an abandoned army is 13 seconds instead of 10. In my opinion that is reasonable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks, I didn't notice, but that is reasonable indeed - it mirrors the recall behavior where the defeat doesn't happen until the recall finishes (which uses the same function), except that the defeat state is set first.

@Hdt80bro Hdt80bro marked this pull request as ready for review November 30, 2025 06:53
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
lua/SimUtils.lua (1)

1106-1142: Return type annotation mismatch.

The function is annotated as returning integer|false but never returns false. It returns:

  • 0 when units are empty
  • timeout when timeout is reached (including negative values)
  • elapsed when all units are dead

Consider updating the annotation to just integer.

 ---@param units Entity[]
 ---@param timeout? integer in ticks
----@return integer|false elapsed
+---@return integer elapsed
 function ThreadSuspendUntilUnitsDead(units, timeout)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a32f1c4 and d41ed34.

📒 Files selected for processing (6)
  • changelog/snippets/fix.6965.md (1 hunks)
  • engine/Sim.lua (1 hunks)
  • lua/SimSync.lua (1 hunks)
  • lua/SimUtils.lua (26 hunks)
  • lua/aibrain.lua (8 hunks)
  • lua/ui/lobby/lobbyOptions.lua (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
lua/SimUtils.lua (4)
lua/system/utils.lua (3)
  • table.empty (68-71)
  • table.getsize (82-89)
  • table.destructiveCat (277-281)
engine/Sim.lua (5)
  • OkayToMessWithArmy (1057-1058)
  • SetArmyUnitCap (1170-1171)
  • GetArmyBrain (588-589)
  • GetGameTick (624-625)
  • IssueStop (966-967)
lua/SimSync.lua (1)
  • StartCountdown (168-179)
lua/ScenarioFramework.lua (1)
  • FakeTeleportUnits (944-982)
🔇 Additional comments (30)
lua/SimSync.lua (1)

169-169: LGTM!

Good change to explicitly scope cdDuration as a local variable, improving code clarity without changing behavior.

engine/Sim.lua (1)

623-624: LGTM!

Correct type refinement — game ticks are discrete integer values, so integer is more accurate than number.

lua/ui/lobby/lobbyOptions.lua (2)

13-15: LGTM!

Well-organized type aliases that centralize share option definitions and improve type safety across the codebase.


37-39: LGTM!

Using the new type aliases for GameOptions fields provides consistency with the type definitions above.

changelog/snippets/fix.6965.md (1)

1-1: LGTM!

Clear and concise changelog entry accurately summarizing the refactor.

lua/aibrain.lua (8)

11-13: LGTM!

Clean imports for the new centralized army kill utilities.


68-68: LGTM!

Type annotation change from Army to integer correctly reflects that this caches GetArmyIndex() which returns an integer.


168-176: LGTM!

Using _ for unused loop variables is idiomatic and improves code clarity.


434-448: LGTM!

SetDefeatStatus effectively centralizes the common defeat/recall state transition logic: updating unit cap, triggering ping callbacks, recall notifications, and disabling AI. This is the core of the refactoring.


451-458: LGTM!

OnDraw and OnVictory correctly only set the status without calling SetDefeatStatus, as they don't need unit cap redistribution or AI disabling.


461-473: LGTM!

OnDefeat now properly delegates state management to SetDefeatStatus before forking the kill thread.


507-525: LGTM!

OnRecalled now properly uses SetDefeatStatus and delegates to KillRecalledArmy. The guard against duplicate recalls is appropriate.


1169-1174: LGTM!

Backwards compatibility imports ensure existing code depending on these functions from this module continues to work.

lua/SimUtils.lua (17)

30-31: LGTM!

Using _ for unused loop indices is appropriate.

Also applies to: 49-51


113-114: LGTM!

The pattern a.Blueprint or a:GetBlueprint() handles both direct blueprint access and method-based access, improving compatibility.


579-592: LGTM!

Good addition of the @type RebuildTracker annotation for clarity.


709-730: LGTM!

The GiveUnitsToPlayer function now has cleaner nil-safety and proper type annotations.


743-766: LGTM!

KillSharedUnits now properly iterates backwards to safely remove elements while iterating, and the logic is cleaner.


768-806: LGTM!

UpdateUnitCap has improved validation and clearer logic for distributing unit cap.


815-854: LGTM!

TransferUnitsToBrain is cleaner with better early returns and uses table.destructiveCat for efficiently merging unit lists.


856-905: LGTM!

GetAllegianceCategories and TransferUnitsToHighestBrain are well-structured with proper sorting by rating.


907-936: LGTM!

Good extraction of EndGameGracePeriod, KillUnits, KillWalls, and KillRemaining as helper functions. These improve code organization and reusability.


996-1036: LGTM!

TransferUnitsToKiller correctly handles different victory conditions and determines the appropriate killer index.


1038-1081: LGTM!

KillArmy is well-organized with clear handling of each share condition.


1083-1104: LGTM!

KillRecalledArmy correctly handles the recall scenario by excluding subcommanders from transfer (since they also recall) and simplifying the share logic.


1155-1172: LGTM!

KillUnsafeCommanders correctly filters commanders based on the CommanderSafeTime threshold and kills those that were recently damaged.


1174-1224: LGTM!

KillArmyOnDelayedRecall properly handles the delayed recall scenario with countdown display and timeout logic. The abuse detection (all commanders dying early in assassination mode) is a good safeguard.


1226-1246: LGTM!

KillArmyOnACUDeath correctly handles the permanent share scenario, waiting for shared commanders to die before applying the share condition.


1248-1299: LGTM!

KillAbandonedArmy is the main entry point for handling disconnected players. It correctly:

  1. Resolves SameAsShare to the actual share option
  2. Handles Explode and Recall ACU options with safety checks
  3. Handles RecallDelayed and Permanent options by delegating to appropriate functions
  4. Falls back to KillArmy with warning on unknown options

1303-1372: LGTM!

DisableAI has improved nil-safety with explicit nil assignments when destroying managers, ensuring cleaner cleanup.

Copy link
Contributor

@lL1l1 lL1l1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some testing using the launch script and 4 players (fullshare, assassination):

  • Defeat through recalling seems fine, although I know that in the live game that it kills units before the sim stops.
  • Defeat through ctrl k seems fine
  • Defeat through abandonment kills units before the sim stops.

There is a ~109 tick difference between AbandonedByPlayer being called and the game ending, which varies due to the ACU kill and victory condition running on different threads. The difference in recall between here and live game is probably due to the additional 3 seconds to the grace period.

The ideal solution would be to make recall and abandon work sequentially with the victory condition thread, but for now the grace period can be increased.

---@param shareOption 'FullShare' | 'ShareUntilDeath' | 'PartialShare' | 'TransferToKiller' | 'Defectors' | 'CivilianDeserter'
---@param shareTime number Game time in ticks
---@param shareOption ShareOption
---@param shareTime integer Game time in ticks
Copy link
Contributor

@lL1l1 lL1l1 Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to clean up the comment style for annotations (using a # or not) in a separate pr then, though that would be low priority.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
lua/SimUtils.lua (1)

114-132: Missing dead-check after WaitTicks.

After WaitTicks(1), a kennel may have been destroyed. Unlike UpgradeTransferredKennels (line 140) and UpgradeUnits (line 190), this function doesn't guard against destroyed units before calling SetPaused.

     for _, unit in kennels do
+        if unit:BeenDestroyed() then
+            continue
+        end
         unit:SetPaused(true)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc8ed1c and e121cc5.

📒 Files selected for processing (1)
  • lua/SimUtils.lua (30 hunks)
🔇 Additional comments (15)
lua/SimUtils.lua (15)

5-6: LGTM! Caching ArmyBrains as a local upvalue is a standard Lua optimization for frequently accessed globals.


134-172: LGTM! Good use of BeenDestroyed() checks after each WaitTicks call, and the or {} pattern safely handles nil PodData.


174-208: LGTM! The upgrade logic correctly guards against destroyed units and properly manages build rates and consumption.


210-215: LGTM! The fallback to GetBlueprint() method call handles units where the Blueprint field may not be directly accessible.


672-709: LGTM! Well-structured extraction of RebuildUnits and proper filtering in TransferUnfinishedUnitsAfterDeath to exclude upgrades and factory-built units.


746-808: LGTM! Backward iteration in KillSharedUnits correctly handles removal during iteration. UpdateUnitCap has proper defensive checks and clear redistribution logic.


810-907: LGTM! Clean separation between TransferUnitsToBrain (handles the actual transfer) and TransferUnitsToHighestBrain (handles rating-based sorting). The WaitSeconds(1) between transfers correctly prevents double-transfers.


1108-1145: LGTM! Well-designed polling function with proper edge case handling for empty tables and non-positive timeouts. The return value semantics (returning timeout vs elapsed) enable callers to detect timeout vs natural completion.


1151-1175: LGTM! The commander safety check logic correctly identifies commanders that were recently damaged (potential disconnect abuse) and kills them while preserving genuinely safe commanders.


1177-1228: LGTM! The delayed recall flow correctly orchestrates unit sharing, countdown display, timeout waiting, and conditional handling for disconnect abuse detection.


1252-1303: LGTM! Comprehensive handling of all disconnect share options with appropriate fallbacks and warnings for unknown options.


1307-1376: LGTM! Thorough AI cleanup that properly disables and destroys all manager components, with nil assignments to break reference cycles and allow garbage collection.


1432-1454: LGTM! Improved defensive programming with early returns for invalid states (observers, self-transfers, enemies) and proper validation of defeat status and resource values before transfer.


476-487: LGTM! Proper type annotation for the external factory component access pattern.


496-508: LGTM! Proper guards before forking threads ensure no unnecessary thread creation for empty work sets.

@Hdt80bro
Copy link
Contributor Author

Your changes look good to me, here's my recap so we're on the same page:

  • Your local variables name are definitely better than the ones I was able to come up with.
  • The function name ThreadSuspendUntilUnitsDead was intended to mirror the other thread functions like ThreadSuspend and ThreadResume but reading it back, it does make more sense to mirror the higher level functions like WaitSeconds (which it itself does call) instead. I'm torn on whether the OrTimeout part is necessary in WaitUntilUnitsDeadOrTimeout as it's an optional parameter that most functions would have; I'll defer to your judgment here as you have better names :)
  • Comment updates are good; the one you removed was one I copied. There were several like that I deleted, but missed that one.
  • I changed ArmyBrains[] to GetArmyBrains() purely for consistency knowing it was slower, since it looked like the previous author preferred it and it wasn't being called in performance critical paths; changing all of them back is fine.
  • Compacting the imports doesn't affect performance since it's called literally once, but it is more readable, so I like that.
  • We really should establish commenting best practices. I'll bring it up in Discord, though it's unlikely anyone will have strong opinions...

At a glance, increasing the grace period to 12 seconds seems curious to me because even 10 seconds already seems too long to me for players to have to wait for. If these changes really started blowing the units too soon, I think we should decrease the victory monitor's wait interval instead.

@lL1l1
Copy link
Contributor

lL1l1 commented Dec 20, 2025

I'm torn on whether the OrTimeout part is necessary in WaitUntilUnitsDeadOrTimeout as it's an optional parameter that most functions would have; I'll defer to your judgment here as you have better names :)

I think it's nice to have in the name because as you said, most functions would have it. It also makes more clear what the options are for using the function.

At a glance, increasing the grace period to 12 seconds seems curious to me because even 10 seconds already seems too long to me for players to have to wait for. If these changes really started blowing the units too soon, I think we should decrease the victory monitor's wait interval instead.

There's a 5 second delay that checks for draws, and the wait interval is already 4 ticks (5 in code but WaitTicks is weird). There's DelayBeforeGameEnds at 3 seconds, but it doesn't seem to do anything gameplay-wise since it comes after victory is already declared, so I'll reduce that by 2 seconds instead. I agree that the grace period should be minimized, although there is a discussion to be had considering the reward of killing a player and being able to abuse their idle units for 10 seconds (or however long). Which, if official, means there should be a patch for giving units shortly after dying before the victory condition detects defeat.

@lL1l1
Copy link
Contributor

lL1l1 commented Dec 20, 2025

Another way to get all units to blow up is to have a player die before the game ends, and then have their last teammate die in the middle of the transfer. That could be another common scenario in games since one player leaving or dying can result in the rest of their team leaving shortly after. I think it's better to close up the PR though, this is "simplify defeat backend", not adding some mechanism that integrates army transfer on defeat and the overall match state.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lua/sim/VictoryCondition/AbstractVictoryCondition.lua (1)

274-274: Update the outdated comment.

The comment still references "three seconds later" but the delay is now 1 second. Consider updating it to either specify the current delay or reference the configurable field.

🔎 Suggested fix
-    --- Ends the game. The monitoring thread is stopped. The game ends three seconds later to give all players a window of opportunity to share the game results with the server. 
+    --- Ends the game. The monitoring thread is stopped. The game ends after DelayBeforeGameEnds seconds to give all players a window of opportunity to share the game results with the server. 
♻️ Duplicate comments (1)
lua/SimUtils.lua (1)

735-741: Critical: Potential nil dereference on empty table.

Line 737 checks not units (nil), but an empty table {} passes this check. Line 741 then accesses units[1].Army, which will cause a nil dereference if units is an empty table.

🔎 Proposed fix
 function GiveUnitsToPlayer(data, units)
     local manualShare = ScenarioInfo.Options.ManualUnitShare
-    if manualShare == 'none' or not units then
+    if manualShare == 'none' or not units or not units[1] then
         return
     end
🧹 Nitpick comments (1)
lua/SimUtils.lua (1)

1465-1466: Optional: Inconsistent brain access pattern.

Lines 1465-1466 use GetArmyBrain() instead of the ArmyBrains[] table used elsewhere in the file (e.g., lines 246, 800, 1053). While this function is infrequently called (UI-triggered), using the upvalued table would be more consistent with the performance optimization applied throughout this file.

🔎 Proposed change for consistency
-    local fromBrain = GetArmyBrain(data.From)
-    local toBrain = GetArmyBrain(data.To)
+    local fromBrain = ArmyBrains[data.From]
+    local toBrain = ArmyBrains[data.To]
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e121cc5 and 18ddac7.

📒 Files selected for processing (3)
  • lua/SimSync.lua (1 hunks)
  • lua/SimUtils.lua (28 hunks)
  • lua/sim/VictoryCondition/AbstractVictoryCondition.lua (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lua/SimSync.lua
🧰 Additional context used
🧬 Code graph analysis (2)
lua/sim/VictoryCondition/AbstractVictoryCondition.lua (1)
lua/simInit.lua (1)
  • WaitSeconds (33-39)
lua/SimUtils.lua (6)
engine/Sim.lua (3)
  • GetGameTick (624-625)
  • IssueStop (966-967)
  • GetArmyBrain (588-589)
lua/system/utils.lua (3)
  • table.empty (68-71)
  • table.getsize (82-89)
  • table.destructiveCat (277-281)
engine/Core.lua (3)
  • ForkThread (181-182)
  • WARN (560-561)
  • IsDestroyed (243-244)
lua/sim/score.lua (1)
  • CalculateBrainScore (84-103)
lua/SimSync.lua (1)
  • StartCountdown (169-180)
lua/ScenarioFramework.lua (1)
  • FakeTeleportUnits (944-982)
🔇 Additional comments (30)
lua/sim/VictoryCondition/AbstractVictoryCondition.lua (2)

47-47: LGTM: Delay reduction aligns with PR objectives.

The reduction from 3 to 1 second matches the planned 2-second decrease discussed in the PR comments.


289-289: Excellent refactor: Makes delay configurable.

Replacing the hardcoded value with self.DelayBeforeGameEnds improves maintainability and allows subclasses to customize the delay if needed.

lua/SimUtils.lua (28)

5-7: LGTM: Performance optimization.

Upvaluing ArmyBrains is a standard Lua optimization that improves access speed by avoiding repeated global lookups.


91-91: LGTM: Typo correction.

Fixed "hiearchy" → "hierarchy" in the TODO comment.


200-203: Verify re-entrancy intention for DefaultBuildRate.

Line 200 only sets DefaultBuildRate if it doesn't already exist. If UpgradeUnits is called multiple times on the same units, this preserves the original build rate, which seems intentional. However, if this function is meant to be called only once per unit, the check may hide a logic error.

Confirm whether UpgradeUnits can be called multiple times on the same units and whether preserving the original DefaultBuildRate across calls is the intended behavior.


231-233: LGTM: Defensive blueprint access.

Added fallback to GetBlueprint() method for units that don't have the Blueprint field cached, making the comparator more robust.


246-246: LGTM: Consistent with performance optimization.

Uses the upvalued ArmyBrains table for direct access, consistent with the optimization at line 6.


496-496: LGTM: Enhanced type annotation.

Added type annotation for better IDE support and code clarity.


519-530: LGTM: More idiomatic empty check.

Replaced table.getn() > 0 with not table.empty(), which is clearer and more robust (handles both nil and empty tables correctly).


565-565: LGTM: Type documentation.

Added explicit type annotation for better code documentation.


616-618: LGTM: Explicit indexing.

Using explicit index variable i makes the relationship between units and trackers clearer.


641-645: LGTM: Conventional unused variable.

Using _ instead of k when the key is unused follows Lua conventions.


694-731: LGTM: Well-structured rebuild logic.

The new functions properly:

  • Encapsulate the rebuild workflow (start, try for each army, finalize)
  • Filter unbuilt units appropriately (excluding upgrades and factory-parented units)
  • Use defensive checks before processing

The nil-safety check on Line 727 correctly handles empty tables by checking the first element.


768-791: LGTM: Safe shared unit cleanup.

Properly handles empty tables with early return and correctly iterates backwards when removing elements from the array.


796-830: LGTM: Robust unit cap redistribution.

Properly validates share options, checks army states, and correctly distributes cap to eligible brains (allies or all, depending on settings).


840-878: LGTM: Improved transfer orchestration.

Enhanced function now:

  • Returns transferred units for caller tracking
  • Properly sequences transfers with 1-second delays to prevent duplicate transfers
  • Uses consistent nil-safety patterns

882-899: LGTM: Clear allegiance categorization.

The refactored function clearly categorizes brains into Civilians, Enemies, and Allies with consistent patterns.


908-929: LGTM: Rating-based transfer prioritization.

The function correctly prioritizes transfer to highest-rated players, with a sensible fallback to score-based ratings for AI players.


933-950: LGTM: Grace period and unit cleanup.

The 10-second grace period allows observers to see the final game state before units are destroyed. The KillUnits helper safely checks if units are destroyed before killing them.


952-960: LGTM: Clear helper functions.

Simple, focused helpers that improve code readability in the kill flow.


966-1018: LGTM: Improved borrowed unit handling.

All three functions properly:

  • Check for empty collections before processing
  • Validate army states before transfers
  • Use consistent nil-safety patterns

1022-1060: LGTM: Enhanced killer transfer logic.

The function now accepts an optional category parameter for flexibility. The killer determination correctly handles team-based scenarios in decapitation mode by finding the killer of the last allied commander to die.


1064-1105: LGTM: Well-structured army defeat handling.

The centralized KillArmy function clearly orchestrates the defeat flow:

  • Applies the grace period consistently
  • Handles each share option with appropriate logic
  • Properly sequences operations (share, kill walls, kill remaining)

1107-1128: LGTM: Recall-specific defeat handling.

KillRecalledArmy correctly simplifies the logic for recall scenarios by:

  • Excluding SUBCOMMANDERs (since they recall too)
  • Only handling the relevant share options (CivilianDeserter, Defectors)
  • Using the same grace period

1130-1167: LGTM: Robust timeout utility.

WaitUntilUnitsDeadOrTimeout properly handles all edge cases:

  • Empty unit lists (returns 0)
  • Negative/zero timeouts (returns timeout for comparison purposes)
  • Distinguishes between natural completion and timeout

The return value semantics are well-documented and consistent.


1171-1196: LGTM: Commander safety handling.

The new constants and KillUnsafeCommanders function properly implement disconnect abuse prevention by killing commanders that took damage within the safety window (2 minutes).


1199-1250: LGTM: Complex but correct delayed recall logic.

KillArmyOnDelayedRecall properly handles the delayed recall disconnect option:

  • Shares all units temporarily
  • Creates countdown UI for players
  • Detects abuse (early commander death) and falls back to standard share
  • Filters unsafe commanders before recall
  • Sequences operations correctly

The complexity is justified by the feature requirements.


1252-1272: LGTM: Permanent ACU share logic.

KillArmyOnACUDeath correctly implements the permanent ACU share option with abuse detection for early deaths.


1274-1325: LGTM: Comprehensive disconnect handling.

KillAbandonedArmy properly orchestrates all disconnect scenarios:

  • Routes to appropriate kill functions based on ACU option
  • Applies abuse detection only in assassination mode
  • Forces FullShare in non-assassination modes for delayed/permanent options
  • Handles all edge cases with appropriate warnings

1335-1398: LGTM: Improved AI cleanup consistency.

The refactored DisableAI function uses consistent patterns for:

  • Empty table checks
  • Platoon handle validation
  • Explicit nil assignments for cleanup

@Hdt80bro
Copy link
Contributor Author

Yeah, that will have to wait for another time.
Everything looks good to me then!

@lL1l1 lL1l1 merged commit b1d2d40 into develop Dec 20, 2025
6 checks passed
@lL1l1 lL1l1 deleted the simplify-defeat-backend branch December 20, 2025 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants