Skip to content

Tianpok/refactor status effect domain events#24

Merged
liang799 merged 15 commits into
mainfrom
tianpok/refactor-status-effect-domain-events
Apr 9, 2026
Merged

Tianpok/refactor status effect domain events#24
liang799 merged 15 commits into
mainfrom
tianpok/refactor-status-effect-domain-events

Conversation

@liang799
Copy link
Copy Markdown
Owner

@liang799 liang799 commented Apr 9, 2026

Summary by CodeRabbit

  • New Features

    • Added a Strength Boost status effect (temporary attack bonus).
  • Bug Fixes

    • More consistent status-effect timing and human-readable outcome notes.
    • Fixed turn-blocking behavior for stunned combatants.
    • Smoke Bomb blocking/expiration behavior clarified.
  • Refactor

    • Unified attack resolution for consistent damage and outcome reporting.
    • Special-skill cooldown and availability logic centralized for accurate UI/display.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

Centralized attack resolution into a new immutable AttackResolution returned by Combatant.attack(...); status-effect event publishing/observer model removed in favor of outcome-returning lifecycle methods and typed outcome records; reporting switched to string notes; Player/special-skill APIs and registry/engine/UI/test code updated to the new outcome model.

Changes

Cohort / File(s) Summary
Actions (attack delegation)
src/main/java/sc2002/turnbased/actions/.../ArcaneBlastAction.java, src/main/java/sc2002/turnbased/actions/BasicAttackAction.java, src/main/java/sc2002/turnbased/actions/ShieldBashAction.java
Actions delegate combat to actor.attack(...), receive an AttackResolution, and construct ActionEvent from it; removed inline damage/status-observation logic and related imports.
Actions (status/apply & special-skill delegation)
src/main/java/sc2002/turnbased/actions/DefendAction.java, UseSmokeBombAction.java, UsePowerStoneSkillAction.java, UseSpecialSkillAction.java
Removed observation scopes; apply status effects via actor.addStatusEffect(...) and convert outcomes into StatusEffectReportEvent/notes; special-skill targeting and execution delegated through PlayerCharacter APIs.
Domain: combat & attack result types
src/main/java/sc2002/turnbased/domain/Combatant.java, AttackResolution.java, CombatantId.java
Added CombatantId; new attack(...) overloads return immutable AttackResolution (with append helper); addStatusEffect now returns List<CombatantStatusOutcome>; status-effect lifecycle and query APIs reworked (consume/complete/active-statuses/getTurnBlockReason).
Domain: player/enemy/special-skill
src/main/java/sc2002/turnbased/domain/PlayerCharacter.java, EnemyCombatant.java, SpecialSkill.java
Enemy stores turnAction and exposes takeTurn(...); PlayerCharacter exposes specialSkillTargetingMode(), useSpecialSkill(...), useSpecialSkillWithoutCooldown(...), getSpecialSkillName(), advanceRoundState(); SpecialSkill exposes actionName(), targetingMode(...), use(...), useWithoutTriggeringCooldown(...).
Status-effect model (interfaces → outcomes)
src/main/java/sc2002/turnbased/domain/status/* (many files)
Removed publisher/observer interfaces and event types; StatusEffect API reshaped (description, onApply, onExpire, onRoundEnd, modifyIncomingDamage, modifyStats, getTurnBlockReason, mergeWith); added outcome types (CombatantStatusOutcome, DamageModifier, StatusEffectChange, StatusEffectOutcome, enums) and new effects (e.g., StrengthBoostStatusEffect); removed legacy interfaces (StatModifierEffect, IncomingDamageModifierEffect, MergeableStatusEffect, TurnInterferingEffect) and related event classes.
StatusEffectRegistry & factory
src/main/java/sc2002/turnbased/domain/status/StatusEffectRegistry.java, DefaultStatusEffectRegistryFactory.java
Registry no longer uses an event publisher; add(...) returns outcome list; adjustIncomingDamage(...) returns DamageAdjustment with modifiers; added getTurnBlockReason(...), completeRound(...), consumeOutcomes(); factory now accepts Supplier<StatusEffectRegistry>.
Reporting & mapping
src/main/java/sc2002/turnbased/report/ActionEvent.java, StatusEffectReportEvent.java, SkippedTurnEvent.java, StatusEffectReportMapper.java
Report payloads changed from typed StatusEffectEvent lists to List<String> notes; ActionEvent gained constructor taking AttackResolution; new mapper converts CombatantStatusOutcome → readable notes.
Engine & setups
src/main/java/sc2002/turnbased/engine/BattleEngine.java, EasyLevelSetup.java, MediumLevelSetup.java, HardLevelSetup.java
BattleEngine uses getTurnBlockReason() and consumeStatusEffectOutcomes() for skip/reporting; enemy takeTurn() used; round completion emits status-effect reports; setups now wire DefaultStatusEffectRegistryFactory(StatusEffectRegistry::new).
UI & CLI/GUI
src/main/java/sc2002/turnbased/ui/..., src/main/java/sc2002/turnbased/ui/gui/...
Formatter and UI switched to status-effect notes (List<String>); status lists read via getActiveStatuses(); special-skill availability gated by canUseSpecialSkill(); registry factory wiring updated.
Tests: unit/integration updates & additions
src/test/java/... (many files)
Tests updated to use notes/outcomes and new APIs; added tests for Combatant.attack(...), PlayerCharacter special-skill behavior, StrengthBoostStatusEffect, BattleEngine reports; removed observation-scope and publisher-fake tests; added smoke-bomb integration tests.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Player as Player
    participant Combatant as Attacker(Combatant)
    participant Registry as StatusEffectRegistry
    participant Target as Target(Combatant)
    participant Engine as BattleEngine

    Player->>Combatant: attack(target) / attack(target, power)
    Combatant->>Registry: modifyIncomingDamage(owner=target, attacker=this, damage)
    Registry-->>Combatant: DamageAdjustment(damage, modifiers)
    Combatant->>Target: receiveDamage(adjustedDamage)
    Target-->>Combatant: hpBefore,hpAfter,eliminated
    Combatant->>Registry: record/prune effects, collect outcomes (onApply/onExpire/onRoundEnd)
    Combatant-->>Player: AttackResolution(attackUsed,targetDefense,hpBefore,hpAfter,damage,eliminated,statusEffectOutcomes)
    Combatant->>Engine: emit ActionEvent(actor, actionName, target, AttackResolution)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Poem

🐰

I nibble notes where events once sang,
Outcomes now in tidy strings I hang.
Attacks resolve with measured bite,
Statuses whisper, no loud light.
A quieter hop, the code feels light.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly references the main change: refactoring status effect domain events, which is the primary focus of this large changeset.
Linked Issues check ✅ Passed The PR addresses both issues #22 and #23 through comprehensive domain refactoring: replacing string-based names with Combatant entities in API signatures [#22] and eliminating direct event publisher coupling in favor of domain-driven event outcomes [#23].
Out of Scope Changes check ✅ Passed All changes are directly related to the linked issues: domain API refactoring (removing event publishers, updating signatures), infrastructure updates (BattleEngine, UI adapters), and comprehensive test coverage for new outcome-based patterns.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tianpok/refactor-status-effect-domain-events

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

Copy link
Copy Markdown

@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: 7

🧹 Nitpick comments (2)
src/test/java/sc2002/turnbased/domain/status/SmokeBombStatusEffectTest.java (1)

77-98: Consider simplifying the notes assertion.

The current approach of extracting individual notes via get(0) and comparing to a constructed list works, but could be more direct.

♻️ Optional: Simplify notes assertions
         assertAll(
             () -> assertEquals(0, firstAdjustment.damage()),
             () -> assertEquals(0, secondAdjustment.damage()),
             () -> assertEquals(25, thirdAdjustment.damage()),
-            () -> assertEquals(
-                List.of("Smoke Bomb blocked the attack", "Smoke Bomb blocked the attack"),
-                List.of(firstAdjustment.notes().get(0), secondAdjustment.notes().get(0))
-            ),
+            () -> assertEquals(List.of("Smoke Bomb blocked the attack"), firstAdjustment.notes()),
+            () -> assertEquals(List.of("Smoke Bomb blocked the attack"), secondAdjustment.notes()),
             () -> assertEquals(List.of(), thirdAdjustment.notes()),
             () -> assertTrue(effect.isExpired())
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/sc2002/turnbased/domain/status/SmokeBombStatusEffectTest.java`
around lines 77 - 98, The notes assertion can be simplified by directly
comparing the notes lists instead of extracting get(0) entries; update the
assertions in SmokeBombStatusEffectTest (using firstAdjustment,
secondAdjustment, thirdAdjustment and their notes() results) to assert that
firstAdjustment.notes() equals List.of("Smoke Bomb blocked the attack") and
secondAdjustment.notes() equals List.of("Smoke Bomb blocked the attack"), and
assert that thirdAdjustment.notes() equals List.of() (or emptyList()); keep the
other damage and isExpired assertions unchanged.
src/main/java/sc2002/turnbased/domain/status/StunStatusEffect.java (1)

33-41: Note: getTurnBlockReason has side effects.

This method decrements blockedTurnsRemaining when blocking a turn, which could be unexpected given the "get" prefix. However, this pattern is consistent with how status effects are consumed when activated. Consider renaming to consumeBlockedTurn() or adding a brief doc comment explaining the mutation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/sc2002/turnbased/domain/status/StunStatusEffect.java` around
lines 33 - 41, getTurnBlockReason in StunStatusEffect mutates
blockedTurnsRemaining (it decrements the counter) which is surprising for a
getter; rename getTurnBlockReason to consumeBlockedTurn (or similar) to signal
side effects, update all callers, and keep the same behavior (decrement
blockedTurnsRemaining and return Optional.of(description()) when >0,
Optional.empty() when 0); alternatively, if renaming is too invasive, add a
concise Javadoc to getTurnBlockReason documenting that it consumes one blocked
turn by decrementing blockedTurnsRemaining and returns the blocking reason via
description(), so callers know it has side effects.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/sc2002/turnbased/actions/DefendAction.java`:
- Around line 24-26: DefendAction is returning presentation strings by calling
actor.addStatusEffect(...) which ultimately produces List<String> via
StatusEffectRegistry.add and DefendStatusEffect.onApply; instead, change
DefendAction to return a structured domain event/value (e.g., a
StatusEffectApplied domain object) rather than StatusEffectReportEvent populated
with strings. Concretely, have actor.addStatusEffect(...) (or a new method)
return a semantic result object (like StatusEffectApplicationResult or
StatusEffectInstance) and construct a domain-level StatusEffectAppliedEvent from
that, leaving rendering of human-readable messages to the reporting/presentation
layer; update usages of StatusEffectReportEvent so it accepts these structured
domain objects rather than List<String>.

In
`@src/main/java/sc2002/turnbased/domain/status/DefaultStatusEffectRegistryFactory.java`:
- Around line 17-18: The factory method
DefaultStatusEffectRegistryFactory.create() currently returns
statusEffectRegistrySupplier.get() without validating the produced registry;
modify create() to fail fast if the supplier returns null by checking the result
of statusEffectRegistrySupplier.get() (e.g. via Objects.requireNonNull or an
explicit null check) and throw a clear exception (IllegalStateException or NPE)
with a descriptive message so wiring errors are detected at the factory
boundary.

In `@src/main/java/sc2002/turnbased/domain/status/StatusEffectRegistry.java`:
- Around line 80-83: pruneExpiredEffects currently appends onExpire(...) output
to the shared pendingNotes buffer, causing side-effects during read-only calls
like activeStatuses() and apply(); change pruneExpiredEffects(Combatant) to
return a List<String> of expiry notes instead of mutating pendingNotes, then
update callers: have mutating operations (e.g., apply(), any methods that should
report expiries) collect the returned notes and append them to pendingNotes,
while read-only methods (activeStatuses(), other query paths around lines 93-95
and 104-123) call pruneExpiredEffects but ignore or discard the returned notes
so no global state is changed. Ensure references to onExpire(...) remain but are
used only to build the returned List<String> inside pruneExpiredEffects.
- Around line 36-49: In adjustIncomingDamage (StatusEffectRegistry), ensure the
final damage value cannot be negative by clamping damage to zero before
constructing and returning the DamageAdjustment; keep collecting and consuming
the notes as-is so existing status effect notes (from modifyIncomingDamage and
onExpire) are preserved—i.e., after the loop but before new
DamageAdjustment(damage, ...), set damage = Math.max(0, damage) (or equivalent)
so DamageAdjustment never carries a negative damage value used by
Combatant.resolveAttackFrom.

In `@src/main/java/sc2002/turnbased/domain/status/StrengthBoostStatusEffect.java`:
- Around line 13-22: The constructor StrengthBoostStatusEffect currently permits
roundsRemaining == 0 which yields an immediately-expired effect and misleading
onApply messaging; update the validation in the StrengthBoostStatusEffect(int
attackBonus, int roundsRemaining) constructor to require roundsRemaining > 0
(throw IllegalArgumentException with a clear message like "roundsRemaining must
be positive") and leave attackBonus validation as-is, and update or add a short
comment on the constructor to document that roundsRemaining must be positive; if
zero is intended instead, add a clarifying comment and adjust onApply()
messaging to handle the zero-case.

In `@src/main/java/sc2002/turnbased/domain/status/StunStatusEffect.java`:
- Around line 11-16: The constructor StunStatusEffect currently allows
blockedTurnsRemaining == 0 which creates an immediately-expired stun; change the
validation to require blockedTurnsRemaining > 0 (e.g., if (blockedTurnsRemaining
<= 0) throw new IllegalArgumentException("blockedTurnsRemaining must be
positive")) so only positive turn counts are accepted, matching the approach
used in StrengthBoostStatusEffect; update the error message accordingly.

In `@src/main/java/sc2002/turnbased/report/StatusEffectReportEvent.java`:
- Around line 6-8: The event currently exposes raw narration strings
(StatusEffectReportEvent with List<String> statusEffectNotes) which loses
structured state; change the payload to a typed value object (e.g.,
StatusEffectNote) or a specific domain event type and update the record
signature to use List<StatusEffectNote> statusEffectNotes, keeping the defensive
copy and null check (List.copyOf(Objects.requireNonNull(...))) in the compact
constructor; then update all producers/consumers of StatusEffectReportEvent to
construct and consume the StatusEffectNote instances and move any string
formatting into the formatter/renderer layer rather than in the event.

---

Nitpick comments:
In `@src/main/java/sc2002/turnbased/domain/status/StunStatusEffect.java`:
- Around line 33-41: getTurnBlockReason in StunStatusEffect mutates
blockedTurnsRemaining (it decrements the counter) which is surprising for a
getter; rename getTurnBlockReason to consumeBlockedTurn (or similar) to signal
side effects, update all callers, and keep the same behavior (decrement
blockedTurnsRemaining and return Optional.of(description()) when >0,
Optional.empty() when 0); alternatively, if renaming is too invasive, add a
concise Javadoc to getTurnBlockReason documenting that it consumes one blocked
turn by decrementing blockedTurnsRemaining and returns the blocking reason via
description(), so callers know it has side effects.

In `@src/test/java/sc2002/turnbased/domain/status/SmokeBombStatusEffectTest.java`:
- Around line 77-98: The notes assertion can be simplified by directly comparing
the notes lists instead of extracting get(0) entries; update the assertions in
SmokeBombStatusEffectTest (using firstAdjustment, secondAdjustment,
thirdAdjustment and their notes() results) to assert that
firstAdjustment.notes() equals List.of("Smoke Bomb blocked the attack") and
secondAdjustment.notes() equals List.of("Smoke Bomb blocked the attack"), and
assert that thirdAdjustment.notes() equals List.of() (or emptyList()); keep the
other damage and isExpired assertions unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e9825057-8eae-4508-8b36-fdbe5336ded6

📥 Commits

Reviewing files that changed from the base of the PR and between 7c7f5c0 and a591805.

📒 Files selected for processing (68)
  • src/main/java/sc2002/turnbased/actions/ArcaneBlastAction.java
  • src/main/java/sc2002/turnbased/actions/BasicAttackAction.java
  • src/main/java/sc2002/turnbased/actions/DefendAction.java
  • src/main/java/sc2002/turnbased/actions/ShieldBashAction.java
  • src/main/java/sc2002/turnbased/actions/UsePowerStoneSkillAction.java
  • src/main/java/sc2002/turnbased/actions/UseSmokeBombAction.java
  • src/main/java/sc2002/turnbased/actions/UseSpecialSkillAction.java
  • src/main/java/sc2002/turnbased/domain/AttackResolution.java
  • src/main/java/sc2002/turnbased/domain/Combatant.java
  • src/main/java/sc2002/turnbased/domain/CombatantId.java
  • src/main/java/sc2002/turnbased/domain/EnemyCombatant.java
  • src/main/java/sc2002/turnbased/domain/PlayerCharacter.java
  • src/main/java/sc2002/turnbased/domain/SpecialSkill.java
  • src/main/java/sc2002/turnbased/domain/status/ArcanePowerStatusEffect.java
  • src/main/java/sc2002/turnbased/domain/status/DamageAdjustment.java
  • src/main/java/sc2002/turnbased/domain/status/DefaultStatusEffectRegistryFactory.java
  • src/main/java/sc2002/turnbased/domain/status/DefendStatusEffect.java
  • src/main/java/sc2002/turnbased/domain/status/IncomingDamageModifierEffect.java
  • src/main/java/sc2002/turnbased/domain/status/MergeableStatusEffect.java
  • src/main/java/sc2002/turnbased/domain/status/SmokeBombStatusEffect.java
  • src/main/java/sc2002/turnbased/domain/status/StatModifierEffect.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffect.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffectEventPublisher.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffectKind.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffectObservationScope.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffectObserver.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffectRegistry.java
  • src/main/java/sc2002/turnbased/domain/status/StrengthBoostStatusEffect.java
  • src/main/java/sc2002/turnbased/domain/status/StunStatusEffect.java
  • src/main/java/sc2002/turnbased/domain/status/TurnEffectResolution.java
  • src/main/java/sc2002/turnbased/domain/status/TurnInterferingEffect.java
  • src/main/java/sc2002/turnbased/domain/status/TurnWindow.java
  • src/main/java/sc2002/turnbased/domain/status/event/ArcanePowerAppliedEvent.java
  • src/main/java/sc2002/turnbased/domain/status/event/DefendAppliedEvent.java
  • src/main/java/sc2002/turnbased/domain/status/event/SmokeBombActivatedEvent.java
  • src/main/java/sc2002/turnbased/domain/status/event/SmokeBombAppliedEvent.java
  • src/main/java/sc2002/turnbased/domain/status/event/StatusEffectEvent.java
  • src/main/java/sc2002/turnbased/domain/status/event/StatusEffectExpiredEvent.java
  • src/main/java/sc2002/turnbased/domain/status/event/StunAppliedEvent.java
  • src/main/java/sc2002/turnbased/engine/BattleEngine.java
  • src/main/java/sc2002/turnbased/engine/EasyLevelSetup.java
  • src/main/java/sc2002/turnbased/engine/HardLevelSetup.java
  • src/main/java/sc2002/turnbased/engine/MediumLevelSetup.java
  • src/main/java/sc2002/turnbased/report/ActionEvent.java
  • src/main/java/sc2002/turnbased/report/SkippedTurnEvent.java
  • src/main/java/sc2002/turnbased/report/StatusEffectReportEvent.java
  • src/main/java/sc2002/turnbased/ui/BattleConsoleFormatter.java
  • src/main/java/sc2002/turnbased/ui/CliPlayerDecisionProvider.java
  • src/main/java/sc2002/turnbased/ui/ConsoleBattleUi.java
  • src/main/java/sc2002/turnbased/ui/TurnBasedArenaCli.java
  • src/main/java/sc2002/turnbased/ui/gui/GuiPlayerDecisionProvider.java
  • src/main/java/sc2002/turnbased/ui/gui/TurnBasedArenaGui.java
  • src/test/java/sc2002/turnbased/actions/ArcaneBlastActionTest.java
  • src/test/java/sc2002/turnbased/domain/CombatantTest.java
  • src/test/java/sc2002/turnbased/domain/EnemyCombatantTest.java
  • src/test/java/sc2002/turnbased/domain/PlayerCharacterTest.java
  • src/test/java/sc2002/turnbased/domain/status/ArcanePowerStatusEffectTest.java
  • src/test/java/sc2002/turnbased/domain/status/DefendStatusEffectTest.java
  • src/test/java/sc2002/turnbased/domain/status/SmokeBombStatusEffectTest.java
  • src/test/java/sc2002/turnbased/domain/status/StatusEffectRegistryTest.java
  • src/test/java/sc2002/turnbased/domain/status/StrengthBoostStatusEffectTest.java
  • src/test/java/sc2002/turnbased/domain/status/StunStatusEffectTest.java
  • src/test/java/sc2002/turnbased/e2e/CustomBattleFlowE2ETest.java
  • src/test/java/sc2002/turnbased/integration/BattleEngineAppendixAScenariosIntegrationTest.java
  • src/test/java/sc2002/turnbased/integration/SmokeBombBattleFlowIntegrationTest.java
  • src/test/java/sc2002/turnbased/integration/StatusEffectObservationScopeBattleFlowIntegrationTest.java
  • src/test/java/sc2002/turnbased/support/FakeStatusEffectEventPublisher.java
  • src/test/java/sc2002/turnbased/support/TestDependencies.java
💤 Files with no reviewable changes (18)
  • src/main/java/sc2002/turnbased/domain/status/StatModifierEffect.java
  • src/main/java/sc2002/turnbased/domain/status/MergeableStatusEffect.java
  • src/main/java/sc2002/turnbased/domain/status/event/SmokeBombAppliedEvent.java
  • src/main/java/sc2002/turnbased/domain/status/TurnInterferingEffect.java
  • src/main/java/sc2002/turnbased/domain/status/event/ArcanePowerAppliedEvent.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffectEventPublisher.java
  • src/main/java/sc2002/turnbased/domain/status/event/StunAppliedEvent.java
  • src/main/java/sc2002/turnbased/domain/status/event/StatusEffectExpiredEvent.java
  • src/main/java/sc2002/turnbased/domain/status/TurnWindow.java
  • src/main/java/sc2002/turnbased/domain/status/event/SmokeBombActivatedEvent.java
  • src/main/java/sc2002/turnbased/domain/status/TurnEffectResolution.java
  • src/test/java/sc2002/turnbased/integration/StatusEffectObservationScopeBattleFlowIntegrationTest.java
  • src/main/java/sc2002/turnbased/domain/status/IncomingDamageModifierEffect.java
  • src/main/java/sc2002/turnbased/domain/status/event/DefendAppliedEvent.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffectObservationScope.java
  • src/main/java/sc2002/turnbased/domain/status/event/StatusEffectEvent.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffectObserver.java
  • src/test/java/sc2002/turnbased/support/FakeStatusEffectEventPublisher.java

Comment thread src/main/java/sc2002/turnbased/actions/DefendAction.java Outdated
Comment thread src/main/java/sc2002/turnbased/domain/status/StunStatusEffect.java
Comment thread src/main/java/sc2002/turnbased/report/StatusEffectReportEvent.java
@liang799 liang799 marked this pull request as draft April 9, 2026 10:22
@liang799 liang799 marked this pull request as ready for review April 9, 2026 11:29
Copy link
Copy Markdown

@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)
src/main/java/sc2002/turnbased/domain/status/StatusEffectChange.java (1)

11-20: Tighten the invariants by type.

The factories only build sensible states, but the public constructor still allows impossible combinations like EXPIRED with a non-zero magnitude or duration. Guarding that here will keep the new outcome type self-consistent.

Suggested guard
     public StatusEffectChange {
         Objects.requireNonNull(source, "source");
         Objects.requireNonNull(type, "type");
         if (magnitude < 0) {
             throw new IllegalArgumentException("magnitude must not be negative");
         }
         if (duration < 0) {
             throw new IllegalArgumentException("duration must not be negative");
         }
+        if (type == StatusEffectChangeType.EXPIRED && (magnitude != 0 || duration != 0)) {
+            throw new IllegalArgumentException("expired changes must not carry magnitude or duration");
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/sc2002/turnbased/domain/status/StatusEffectChange.java` around
lines 11 - 20, The constructor for StatusEffectChange must enforce that certain
Outcome/Type values are self-consistent: add a guard in the public constructor
(the StatusEffectChange(...) initializer block) validating the 'type' value so
impossible combinations are rejected — e.g., if type == EXPIRED then require
magnitude == 0 and duration == 0 (throw IllegalArgumentException otherwise);
keep the existing null checks and non-negative checks for magnitude and duration
and throw similarly descriptive IllegalArgumentException when the invariant is
violated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/sc2002/turnbased/engine/BattleEngine.java`:
- Around line 232-236: The current BattleEngine.completeRound() calls
player.completeRound() and enemy.completeRound() but ignores their return
values; change BattleEngine.completeRound() so it captures the
CombatantStatusOutcome(s) returned by Combatant.completeRound() for the player
and for each Combatant in spawnedEnemies, and forward those outcomes into the
engine's event stream/queue using the engine's existing event dispatch method
(e.g., call the event dispatch/publish/addEvent helper your engine uses) so
end-of-round expiries/notes are emitted rather than dropped.
- Around line 101-120: The code currently calls
actor.consumeStatusEffectOutcomes() unconditionally which drains status outcomes
even when the actor takes a normal turn; change the logic in BattleEngine so you
only call actor.consumeStatusEffectOutcomes() inside the branches that emit
skipped-turn events (i.e., after checking actor.isAlive() and when
actor.getTurnBlockReason() is present) and pass those outcomes to
SkippedTurnEvent.fromStatusEffectOutcomes; leave the normal-turn path untouched
so no outcomes are consumed unless a skipped/ELIMINATED event is being emitted
(keep emit(...) and battleEventListener usage the same).

---

Nitpick comments:
In `@src/main/java/sc2002/turnbased/domain/status/StatusEffectChange.java`:
- Around line 11-20: The constructor for StatusEffectChange must enforce that
certain Outcome/Type values are self-consistent: add a guard in the public
constructor (the StatusEffectChange(...) initializer block) validating the
'type' value so impossible combinations are rejected — e.g., if type == EXPIRED
then require magnitude == 0 and duration == 0 (throw IllegalArgumentException
otherwise); keep the existing null checks and non-negative checks for magnitude
and duration and throw similarly descriptive IllegalArgumentException when the
invariant is violated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4093f7bf-e623-4f44-8021-2e0be4fefff2

📥 Commits

Reviewing files that changed from the base of the PR and between a591805 and c4f54fe.

📒 Files selected for processing (33)
  • src/main/java/sc2002/turnbased/actions/ArcaneBlastAction.java
  • src/main/java/sc2002/turnbased/actions/DefendAction.java
  • src/main/java/sc2002/turnbased/actions/ShieldBashAction.java
  • src/main/java/sc2002/turnbased/actions/UseSmokeBombAction.java
  • src/main/java/sc2002/turnbased/domain/AttackResolution.java
  • src/main/java/sc2002/turnbased/domain/Combatant.java
  • src/main/java/sc2002/turnbased/domain/status/ArcanePowerStatusEffect.java
  • src/main/java/sc2002/turnbased/domain/status/CombatantStatusOutcome.java
  • src/main/java/sc2002/turnbased/domain/status/DamageAdjustment.java
  • src/main/java/sc2002/turnbased/domain/status/DamageModifier.java
  • src/main/java/sc2002/turnbased/domain/status/DamageModifierType.java
  • src/main/java/sc2002/turnbased/domain/status/DefaultStatusEffectRegistryFactory.java
  • src/main/java/sc2002/turnbased/domain/status/DefendStatusEffect.java
  • src/main/java/sc2002/turnbased/domain/status/SmokeBombStatusEffect.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffect.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffectChange.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffectChangeType.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffectOutcome.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffectRegistry.java
  • src/main/java/sc2002/turnbased/domain/status/StrengthBoostStatusEffect.java
  • src/main/java/sc2002/turnbased/domain/status/StunStatusEffect.java
  • src/main/java/sc2002/turnbased/engine/BattleEngine.java
  • src/main/java/sc2002/turnbased/report/ActionEvent.java
  • src/main/java/sc2002/turnbased/report/SkippedTurnEvent.java
  • src/main/java/sc2002/turnbased/report/StatusEffectReportEvent.java
  • src/main/java/sc2002/turnbased/report/StatusEffectReportMapper.java
  • src/test/java/sc2002/turnbased/domain/CombatantTest.java
  • src/test/java/sc2002/turnbased/domain/status/ArcanePowerStatusEffectTest.java
  • src/test/java/sc2002/turnbased/domain/status/DefaultStatusEffectRegistryFactoryTest.java
  • src/test/java/sc2002/turnbased/domain/status/SmokeBombStatusEffectTest.java
  • src/test/java/sc2002/turnbased/domain/status/StatusEffectRegistryTest.java
  • src/test/java/sc2002/turnbased/domain/status/StrengthBoostStatusEffectTest.java
  • src/test/java/sc2002/turnbased/domain/status/StunStatusEffectTest.java
✅ Files skipped from review due to trivial changes (3)
  • src/main/java/sc2002/turnbased/domain/status/StatusEffectChangeType.java
  • src/main/java/sc2002/turnbased/domain/status/DamageModifierType.java
  • src/main/java/sc2002/turnbased/actions/UseSmokeBombAction.java
🚧 Files skipped from review as they are similar to previous changes (13)
  • src/main/java/sc2002/turnbased/actions/DefendAction.java
  • src/test/java/sc2002/turnbased/domain/status/StrengthBoostStatusEffectTest.java
  • src/test/java/sc2002/turnbased/domain/status/ArcanePowerStatusEffectTest.java
  • src/main/java/sc2002/turnbased/domain/status/DamageAdjustment.java
  • src/main/java/sc2002/turnbased/report/StatusEffectReportEvent.java
  • src/main/java/sc2002/turnbased/report/ActionEvent.java
  • src/main/java/sc2002/turnbased/domain/status/ArcanePowerStatusEffect.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffect.java
  • src/main/java/sc2002/turnbased/domain/status/StrengthBoostStatusEffect.java
  • src/main/java/sc2002/turnbased/report/SkippedTurnEvent.java
  • src/test/java/sc2002/turnbased/domain/status/SmokeBombStatusEffectTest.java
  • src/test/java/sc2002/turnbased/domain/status/StunStatusEffectTest.java
  • src/main/java/sc2002/turnbased/domain/status/StatusEffectRegistry.java

Comment thread src/main/java/sc2002/turnbased/engine/BattleEngine.java
Comment thread src/main/java/sc2002/turnbased/engine/BattleEngine.java Outdated
Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/sc2002/turnbased/engine/BattleEngine.java`:
- Around line 61-63: The RoundSummaryEvent is created from stale status data
because createRoundSummary() is called before completeRound() mutates status
expiry; reorder the calls so completeRound(battleEventListener) runs before
emit(createRoundSummary(roundNumber), battleEventListener), or alternatively
modify createRoundSummary() to query getActiveStatuses() after completeRound()
completes; apply the same reorder/adjustment to the analogous block around the
code referenced at lines 236-250 to ensure RoundSummaryEvent and
StatusEffectReportEvent see a consistent, post-completion status view
(referencing spawnBackupIfNeeded, createRoundSummary, completeRound, emit,
getActiveStatuses, RoundSummaryEvent, StatusEffectReportEvent).
- Around line 102-115: The code calls actor.getTurnBlockReason() before checking
actor.isAlive(), which lets getTurnBlockReason() expire effects and queue
outcomes for actors that are already dead; move the getTurnBlockReason()
invocation so the isAlive() check (and the
SkippedTurnEvent.fromStatusEffectOutcomes/actor.consumeStatusEffectOutcomes()
branch) runs first—i.e., check actor.isAlive() and handle
elimination/consumeStatusEffectOutcomes() before calling getTurnBlockReason() to
avoid mutating state for eliminated combatants.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6309ef9e-13fd-48b1-a449-cd48eb46476e

📥 Commits

Reviewing files that changed from the base of the PR and between c4f54fe and 5971477.

📒 Files selected for processing (2)
  • src/main/java/sc2002/turnbased/engine/BattleEngine.java
  • src/test/java/sc2002/turnbased/engine/BattleEngineTest.java

Comment thread src/main/java/sc2002/turnbased/engine/BattleEngine.java
Comment thread src/main/java/sc2002/turnbased/engine/BattleEngine.java
@liang799 liang799 merged commit 6ab1f3a into main Apr 9, 2026
3 checks passed
@liang799 liang799 deleted the tianpok/refactor-status-effect-domain-events branch April 9, 2026 12:56
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.

Mixing implementation with domain leaking presentation concerns into domain logic

1 participant