Card Effect Handling - UQcsse3200/2026-studio-3 GitHub Wiki
The CardEffectHandler is responsible for applying resolved card effects to the player and enemy entities during battle.
Rather than having the BattleController directly handle every possible card effect, CardEffectHandler centralises this logic into a dedicated class. It receives a list of ResolvedCardEffect objects and applies the appropriate effects to the relevant CombatStatsComponent.
The handler separates effects based on their target:
- Enemy-facing effects – Damage, Poison, and Vulnerable.
- Player-facing effects – Block, Heal, and Strength.
It also determines which living enemies should be affected by a card based on the CardPlayRequest.
flowchart LR
A[CardPlayRequest] --> B[Card Effect Resolution]
B --> C[ResolvedCardEffect List]
C --> D[CardEffectHandler]
D --> E[Player Effects]
D --> F[Enemy Effects]
D --> G[Enemy Target Selection]
E --> H[Player CombatStatsComponent]
F --> I[Enemy CombatStatsComponent]
G --> I
The CardEffectHandler does not determine what a card's effects should be. That responsibility belongs to the card effect resolution system. Instead, it handles the final step of taking those resolved effects and applying them to the correct entities.
-
CardEffectHandler– Applies resolved effects to the player or enemies and determines valid enemy targets. -
CardPlayRequest– Contains information about the card being played and its selected target. -
ResolvedCardEffect– Represents an effect after the card's effects have been resolved. -
CombatStatsComponent– Stores and modifies combat statistics such as health and armour. -
StatusEffect– Represents temporary effects such as Poison, Vulnerable, and Strength. -
TargetType– Determines how a card is targeted, including self-targeting cards.
applyEnemyEffects() is responsible for applying enemy-facing effects to the supplied enemy entities.
public void applyEnemyEffects(
List<Entity> targets,
List<ResolvedCardEffect> effects)The following effects are currently supported:
| EffectBehaviour | |
|---|---|
DAMAGE |
Deals direct damage to the enemy using CombatStatsComponent.takeDamage()
|
POISON |
Applies a Poison status effect using the effect's value and duration |
VULNERABLE |
Applies a Vulnerable status effect using the effect's value and duration |
Effects intended for the player, such as BLOCK, HEAL, and STRENGTH, are ignored by this method because they are handled separately by applyPlayerEffects().
For each enemy, the handler first retrieves its CombatStatsComponent. If the component does not exist, that enemy is skipped.
applyPlayerEffects() is responsible for applying player-facing effects to the player.
public void applyPlayerEffects(
List<ResolvedCardEffect> effects,
Entity player)The following effects are currently supported:
| EffectBehaviour | |
|---|---|
BLOCK |
Adds armour using CombatStatsComponent.addArmour()
|
HEAL |
Immediately heals the player or applies healing as a status effect if a duration is provided |
STRENGTH |
Applies a Strength status effect |
Healing has two different behaviours depending on the duration of the effect.
When the duration is 0, healing is applied immediately:
stats.heal(effect.value());When the duration is greater than 0, healing is instead applied as a status effect:
stats.applyStatusEffect(
effect.type().name(),
effect.value(),
effect.duration());This allows the same HEAL effect type to support both immediate and duration-based healing.
Enemy-facing effects such as DAMAGE, POISON, and VULNERABLE are ignored by this method.
getLivingEnemyTargets() determines which enemies should be affected by a card.
public List<Entity> getLivingEnemyTargets(
CardPlayRequest request,
List<Entity> enemies)Cards targeting SELF do not affect any enemies.
if (request.target().type() == TargetType.SELF) {
return List.of();
}An empty list is returned because the card's effects are intended for the player instead.
For all other target types, the handler currently returns every living enemy.
This means the current implementation supports:
- encounters with a single enemy;
- cards that affect all enemies;
- a simplified multi-enemy targeting system.
Dead enemies are excluded from the returned list.
Precise single-enemy targeting can be added later when the battle system supports selecting an individual enemy from multiple enemies.
The CardEffectHandler does not directly manipulate health or armour values.
Instead, it delegates changes to CombatStatsComponent.
For example:
DAMAGE → CombatStatsComponent.takeDamage()
BLOCK → CombatStatsComponent.addArmour()
HEAL → CombatStatsComponent.heal()
POISON → CombatStatsComponent.applyStatusEffect()
This keeps combat-stat behaviour inside CombatStatsComponent while the handler focuses on determining which effect should be applied to which entity.
This also means existing combat behaviour, such as damage mitigation and death detection, continues to be handled by the combat-stat system.
The handler checks whether an entity contains a CombatStatsComponent before applying effects.
For enemy effects:
if (stats == null) {
continue;
}The individual enemy is skipped if it does not have combat statistics.
For player effects:
if (stats == null) {
return;
}The method exits because there is no valid combat-stat component to modify.
getLivingEnemyTargets() also checks for a valid CombatStatsComponent before checking whether an enemy is dead.
This prevents invalid entities from causing a NullPointerException.
Applies resolved enemy-facing effects to a list of enemy entities.
public void applyEnemyEffects(
List<Entity> targets,
List<ResolvedCardEffect> effects)Applies resolved player-facing effects to the player entity.
public void applyPlayerEffects(
List<ResolvedCardEffect> effects,
Entity player)Returns the living enemies that should receive enemy-facing effects for a card.
public List<Entity> getLivingEnemyTargets(
CardPlayRequest request,
List<Entity> enemies)A typical usage of the handler is:
CardEffectHandler effectHandler = new CardEffectHandler();
List<Entity> targets =
effectHandler.getLivingEnemyTargets(request, enemies);
effectHandler.applyEnemyEffects(targets, resolvedEffects);
effectHandler.applyPlayerEffects(resolvedEffects, player);The battle system can therefore delegate effect application to the handler rather than implementing the individual effect types itself.
The CardEffectHandler tests verify that card effects are correctly applied to players and enemies, while ensuring invalid or irrelevant effects are safely ignored.
Tests verify that:
- DAMAGE correctly reduces an enemy's health.
- POISON is applied to an enemy as a status effect with the correct value and duration.
- VULNERABLE is applied to an enemy as a status effect with the correct value and duration.
- Enemies without a
CombatStatsComponentare safely ignored without causing errors. - Enemy-facing effects are applied only to enemies and do not incorrectly modify player-only stats.
Tests verify that:
- BLOCK correctly increases the player's armour.
- HEAL immediately restores the player's health when no duration is specified.
- HEAL with a duration is applied as a status effect rather than immediately restoring health.
- STRENGTH is applied to the player as a status effect with the correct value and duration.
- A player without a
CombatStatsComponentis safely ignored.
Tests verify that:
- Living enemies are selected as valid targets.
- Dead enemies are excluded from the target list.
- No enemies are returned when all enemies are dead.
- The target selection handles the current behaviour of applying enemy effects to all living enemies.
Overall, these tests ensure that CardEffectHandler correctly separates player-facing and enemy-facing effects, handles status effects appropriately, and safely handles entities that do not contain combat statistics.