Skip to content

refactor: Defense mode#285

Open
dm94 wants to merge 10 commits intomasterfrom
refactor/defende-mode-fixes
Open

refactor: Defense mode#285
dm94 wants to merge 10 commits intomasterfrom
refactor/defende-mode-fixes

Conversation

@dm94
Copy link
Copy Markdown
Owner

@dm94 dm94 commented Apr 5, 2026

Summary by Sourcery

Refine defense mode behavior to be more robust when deciding when and whom to defend.

Bug Fixes:

  • Prevent defense logic from running when configuration or anti-push logic is not initialized, avoiding potential null pointer issues.
  • Ensure defense range checks and ignored-player checks are consistently applied when selecting attackers and targets.

Enhancements:

  • Simplify and streamline module checks when switching to the DefenseModule, using instance checks instead of direct class comparisons.
  • Improve target selection logic by centralizing ally-help conditions into a dedicated helper and validating ally targets before engaging.
  • Adjust under-attack detection to differentiate between defending existing attackers and reacting to friends under attack, only moving to group members when configured to help them.
  • Clarify configuration flags for group defense behavior by requiring respondAttacks and GROUP help-list entries before moving to attacked group members.

dm94 added 6 commits April 5, 2026 13:56
Prevent NullPointerException by checking defenseConfig and antiPushLogic before use.
Extract repeated antiPushLogic.getIgnoredPlayers() call into a helper method to improve maintainability and avoid null access.
Check that the ally target is not null and is valid before returning it to prevent potential null pointer exceptions or invalid target references.
This improves readability and maintainability by using instanceof for type checking instead of direct class comparisons. The logic remains unchanged.
Only move to help group members when all three conditions are met: goToGroup enabled, respondAttacks enabled, and group is in help list. Additionally, optimize attack detection logic to avoid unnecessary movement checks when already targeting an enemy.
Improve readability and maintainability by moving the complex player filtering logic from friendUnderAttack into a dedicated shouldHelpPlayer method. This also removes unnecessary stream operations and list creation.
…Target

The method name had a spelling error which could cause confusion and affect code readability.
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Apr 5, 2026

Reviewer's Guide

Refactors DefenseMode’s attack detection and ally-help logic for clarity and robustness, adding null-safety around configuration/anti-push logic, simplifying module checks, and extracting reusable helper methods for filtering allies and ignored players.

Updated class diagram for DefenseMode defense and ally-help logic

classDiagram
    class DefenseMode {
        - DefenseConfig defenseConfig
        - AntiPushLogic antiPushLogic
        - HeroAPI heroapi
        - BotApi botApi
        - Collection~Player~ players
        - Ship target
        + onTickBehavior() void
        - setup() void
        - isUnderAttack() boolean
        - hasPreviousTarget() boolean
        - friendUnderAttack() boolean
        - hasAttacker() boolean
        - getTarget(ships Collection~Player~) Ship
        - shouldHelpPlayer(player Player) boolean
        - isIgnoredPlayer(playerId int) boolean
        - goToMemberAttacked() void
        - inGroupAttacked(id int) boolean
    }

    class DefenseConfig {
        + rangeForAttackedEnemy double
        + helpList Collection~HelpList~
        + helpAttack boolean
        + defendEvenAreNotEnemies boolean
        + goToGroup boolean
        + respondAttacks boolean
    }

    class AntiPushLogic {
        + getIgnoredPlayers() Collection~int~
        + registerTarget(target Ship) void
    }

    class HeroAPI {
        + getId() int
        + getMap() Map
        + isAttacking() boolean
        + distanceTo(entity Entity) double
        + getEntityInfo() EntityInfo
    }

    class BotApi {
        + getModule() Module
        + setModule(module Module) void
    }

    class Module
    class DefenseModule
    class PVPModule
    class SentinelModule
    class TemporalModule
    class MapModule

    class Player {
        + isValid() boolean
        + isAttacking() boolean
        + getTarget() Entity
        + getTargetAsShip() Ship
        + getEntityInfo() EntityInfo
        + getId() int
    }

    class Ship {
        + isValid() boolean
        + getId() int
        + getLocationInfo() LocationInfo
    }

    class Pet
    class Npc

    class SharedFunctions {
        + getAttacker(player Player, players Collection~Player~, hero HeroAPI, onlyEnemies boolean) Ship
    }

    class Entity
    class EntityInfo {
        + getClanId() int
        + getClanDiplomacy() Diplomacy
        + isEnemy() boolean
    }

    class LocationInfo {
        + distanceTo(hero HeroAPI) double
    }

    class Diplomacy {
        <<enumeration>>
        ALLIED
    }

    class HelpList {
        <<enumeration>>
        CLAN
        ALLY
        GROUP
        EVERYONE
    }

    DefenseMode --> DefenseConfig
    DefenseMode --> AntiPushLogic
    DefenseMode --> HeroAPI
    DefenseMode --> BotApi
    DefenseMode --> "*" Player
    DefenseMode --> Ship

    DefenseMode ..> SharedFunctions : uses
    DefenseMode ..> HelpList : uses
    DefenseMode ..> Diplomacy : uses

    BotApi --> Module
    DefenseModule --|> Module
    PVPModule --|> Module
    SentinelModule --|> Module
    TemporalModule --|> Module
    MapModule --|> Module

    Player --> EntityInfo
    Player --> Ship
    Ship --> LocationInfo
    EntityInfo --> Diplomacy

    Pet --|> Player
    Npc --|> Entity
    Ship --|> Entity
    Player --|> Entity
Loading

File-Level Changes

Change Details Files
Hardened onTickBehavior flow with null-safety and simplified module selection before switching into DefenseModule and registering anti-push targets.
  • Return early from onTickBehavior when defenseConfig is null.
  • Lazily initialize antiPushLogic via setup() and short-circuit if it remains null.
  • Store botApi.getModule() in a local Module variable and replace class equality checks with instanceof checks for DefenseModule, PVPModule, SentinelModule, TemporalModule, and MapModule.
  • Retain the existing GG-map guard, and only instantiate DefenseModule when currentModule is compatible and hero is under attack.
  • Register the current target with antiPushLogic without using the this qualifier.
src/main/java/com/deeme/behaviours/defense/DefenseMode.java
Reworked attack detection to separate concerns between previous target, direct attackers, and friends under attack, with more explicit group-help behavior.
  • Changed isUnderAttack() to simply check previous target or attacker first, then separately evaluate friendUnderAttack() and trigger goToMemberAttacked() only when a friend actually needs help.
  • Renamed hasPreviusTarget() to hasPreviousTarget() and kept its distance/validity checks intact.
  • Updated hasAttacker() to use a new isIgnoredPlayer() helper instead of directly accessing antiPushLogic’s ignoredPlayers list.
  • Strengthened goToMemberAttacked() guard by also requiring respondAttacks to be true and HelpList.GROUP to be enabled in defenseConfig.
src/main/java/com/deeme/behaviours/defense/DefenseMode.java
Simplified and generalized ally-target selection and filtering with helper methods and broader collection support.
  • Changed friendUnderAttack() to call getTarget(players) directly instead of pre-filtering into a List, and to only rely on target validity for its result.
  • Updated getTarget(...) to accept a generic Collection<? extends Player> and iterate sequentially, delegating ally eligibility to a new shouldHelpPlayer(Player) helper.
  • Inside getTarget(...), validated both ally attack-targets and attackers from SharedFunctions.getAttacker(...) for non-nullity, validity, non-NPC/Pet, and non-ignored via isIgnoredPlayer.
  • Introduced shouldHelpPlayer(Player) to centralize clan/ally/group/everyone checks while excluding invalid players and pets.
  • Introduced isIgnoredPlayer(int) to safely query antiPushLogic’s ignoredPlayers only when antiPushLogic is non-null.
  • Removed now-unnecessary imports for List and Collectors and the intermediate ships filtering pipeline.
src/main/java/com/deeme/behaviours/defense/DefenseMode.java

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@dm94 dm94 self-assigned this Apr 5, 2026
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 3 issues, and left some high level feedback:

  • The change from botApi.getModule().getClass() != MapModule.class to !(currentModule instanceof MapModule) alters behavior for any subclasses of MapModule (they will now be treated as MapModule), so please confirm this is intentional or narrow the condition if only the concrete class should be exempted.
  • In isUnderAttack(), calling goToMemberAttacked() only when friendUnderAttack() is true changes the previous behavior where group movement could be triggered even when dealing with your own attacker; it’s worth double-checking that this priority change matches the desired defense behavior.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The change from `botApi.getModule().getClass() != MapModule.class` to `!(currentModule instanceof MapModule)` alters behavior for any subclasses of `MapModule` (they will now be treated as MapModule), so please confirm this is intentional or narrow the condition if only the concrete class should be exempted.
- In `isUnderAttack()`, calling `goToMemberAttacked()` only when `friendUnderAttack()` is true changes the previous behavior where group movement could be triggered even when dealing with your own attacker; it’s worth double-checking that this priority change matches the desired defense behavior.

## Individual Comments

### Comment 1
<location path="src/main/java/com/deeme/behaviours/defense/DefenseMode.java" line_range="113-117" />
<code_context>
-        if (botApi.getModule() != null && botApi.getModule().getClass() != DefenseModule.class
-                && !((botApi.getModule().getClass() == PVPModule.class
-                        || botApi.getModule().getClass() == SentinelModule.class)
+        Module currentModule = botApi.getModule();
+        if (currentModule != null && !(currentModule instanceof DefenseModule)
+                && !((currentModule instanceof PVPModule || currentModule instanceof SentinelModule)
                         && heroapi.isAttacking())
-                && !((botApi.getModule() instanceof TemporalModule)
-                        && botApi.getModule().getClass() != MapModule.class)
+                && !((currentModule instanceof TemporalModule)
+                        && !(currentModule instanceof MapModule))
                 && isUnderAttack()) {
</code_context>
<issue_to_address>
**question (bug_risk):** Using instanceof in the Temporal/Map module condition subtly changes behavior for MapModule subclasses.

The old logic used `getClass() != MapModule.class`, while the new logic uses `!(currentModule instanceof MapModule)`. With this change, any `MapModule` subclass is now excluded from the Temporal branch, whereas previously subclasses were treated as generic `TemporalModule`s. If you have custom `MapModule` subclasses, this can change when `DefenseMode` triggers. If that’s not intended, consider preserving the exact-class check or explicitly documenting this behavioral change.
</issue_to_address>

### Comment 2
<location path="src/main/java/com/deeme/behaviours/defense/DefenseMode.java" line_range="136-145" />
<code_context>
     }

     private boolean isUnderAttack() {
-        goToMemberAttacked();
+        if (hasPreviousTarget() || hasAttacker()) {
+            return true;
+        }

-        return hasPreviusTarget() || hasAttacker() || friendUnderAttack();
+        boolean friendNeedsHelp = friendUnderAttack();
+        if (friendNeedsHelp) {
+            goToMemberAttacked();
+        }
+        return friendNeedsHelp;
     }

</code_context>
<issue_to_address>
**question (bug_risk):** The new isUnderAttack flow changes when group movement is triggered compared to the previous version.

Previously, `goToMemberAttacked()` was always called at the start of `isUnderAttack()`, regardless of the results of `hasPreviusTarget()` / `hasAttacker()` / `friendUnderAttack()`. Now it is only called when `friendUnderAttack()` is true and only after ruling out `hasPreviousTarget()` / `hasAttacker()`. As a result, if the hero has a previous target or direct attacker, it will no longer move toward attacked group members. Please confirm this behavioral change is intentional; if not, you may need to reintroduce group movement in those cases.
</issue_to_address>

### Comment 3
<location path="src/main/java/com/deeme/behaviours/defense/DefenseMode.java" line_range="224-227" />
<code_context>
+        return antiPushLogic != null && antiPushLogic.getIgnoredPlayers().contains(playerId);
+    }
+
     private void goToMemberAttacked() {
-        if (!defenseConfig.goToGroup) {
+        if (!defenseConfig.goToGroup
+                || !defenseConfig.respondAttacks
+                || !defenseConfig.helpList.contains(HelpList.GROUP)) {
             return;
         }
</code_context>
<issue_to_address>
**question (bug_risk):** Adding respondAttacks and GROUP-helpList checks changes when group navigation can happen.

`goToMemberAttacked()` now requires `respondAttacks == true` and `helpList` containing `HelpList.GROUP`, in addition to `goToGroup`. Configs that previously triggered group movement with only `goToGroup` set will stop doing so. If that behavior change isn’t explicitly desired, consider limiting `respondAttacks`/`helpList.GROUP` to targeting logic and leaving the movement gate as just `goToGroup`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

dm94 added 4 commits April 5, 2026 15:50
Move the player filtering logic from getTarget method to the caller in friendUnderAttack. This simplifies getTarget by removing the responsibility of checking shouldHelpPlayer, making the method more focused on selecting a target from a pre-filtered list.
The previous condition incorrectly excluded TemporalModule instances that were also MapModule. Using class equality check ensures only pure TemporalModule instances are excluded when determining if under attack.
- Merge multiple conditions into a single return statement
- Remove redundant friendNeedsHelp variable
- Maintain same behavior while improving readability
Update the plugin version in the manifest file to reflect the new development release.
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.

1 participant