-
Notifications
You must be signed in to change notification settings - Fork 1
Water Boss Special Move
Phurin Vanasrivilai edited this page Oct 16, 2024
·
7 revisions
The SpecialWaterMove is a special move used in combat by the Water Boss (Leviathan), extending from the SpecialMove abstract class (as described in Combat Moves).
This move applies at random one of the two status effects to the player:
- CONFUSED: The player executes a random move.
- POISONED: Lasts 2 turns. Sleep will restore stamina but not health. The Player's stamina is also decreased by 30% before each affected turn.
In addition to debuffing the player, the move also buffs Leviathan's strength and defense.
/**
* The SpecialWaterMove class represents Water boss's special combat move, which inflicts debuffs
* on the player and buffs Water boss's own stats. This move is unique to Water boss and impacts both
* the target and the attacker.
*/
public class SpecialWaterMove extends SpecialMove {
private static final Logger logger = LoggerFactory.getLogger(SpecialWaterMove.class);
/**
* Constructs the SpecialWaterMove with the given move name and hunger cost.
*
* @param moveName the name of the special move.
* @param hungerCost the hunger cost required to perform the special move.
*/
public SpecialWaterMove(String moveName, int hungerCost) {
super(moveName, hungerCost);
}
/**
* Applies a random status effect to the target player after the move is executed
* Also apply debuff which decreases Player's strength by 20 and defense by 10.
*
* @param targetStats combat stats of the target (player) that will be affected by the debuffs.
*/
@Override
protected void applyDebuffs(CombatStatsComponent targetStats) {
// Applies debuffs to target's stats
targetStats.addStrength(-20);
targetStats.addDefense(-10);
int rand = (int) (MathUtils.random() * 2);
CombatStatsComponent.StatusEffect statusEffect = switch (rand) {
case 0 -> CombatStatsComponent.StatusEffect.CONFUSED;
case 1 -> CombatStatsComponent.StatusEffect.POISONED;
default -> throw new IllegalStateException("Unexpected value: " + rand);
};
targetStats.addStatusEffect(statusEffect);
logger.info("Status effect {} applied to the {}", statusEffect.name(), targetStats.isPlayer() ? "PLAYER" : "ENEMY");
}
/**
* Buffs Water Boss's strength and defense stats after the special move.
* This method increases Water Boss's strength by 10 and defense by 25.
*
* @param attackerStats combat stats of Kanga, who is performing the special move.
*/
@Override
protected void applyBuffs(CombatStatsComponent attackerStats) {
attackerStats.addStrength(10);
attackerStats.addDefense(25);
logger.info("{} increased its strength to {} and defense to {}.",
attackerStats.isPlayer() ? "PLAYER" : "ENEMY",
attackerStats.getStrength(),
attackerStats.getDefense());
}
}
.
.
.
/**
* Sets the player's action based on input and triggers enemy action selection.
* The move combination is then executed, and status effects are processed at the end of the turn.
*
* @param playerActionStr the action chosen by the player as a string.
*/
public void onPlayerActionSelected(String playerActionStr) {
try {
playerAction = Action.valueOf(playerActionStr.toUpperCase());
} catch (IllegalArgumentException e) {
logger.error("Invalid player action: {}", playerActionStr);
return;
}
enemyAction = selectEnemyMove();
handlePlayerConfusion();
logger.info("(BEFORE) PLAYER {}: health {}, hunger {}", playerAction, playerStats.getHealth(), playerStats.getHunger());
logger.info("(BEFORE) ENEMY {}: health {}, hunger {}", enemyAction, enemyStats.getHealth(), enemyStats.getHunger());
// Execute the selected moves for both player and enemy.
executeMoveCombination(playerAction, enemyAction);
handleStatusEffects();
checkCombatEnd();
}
/**
* Randomly select a move to replace the player's selected move if the player has the Confusion status effect
*/
public void handlePlayerConfusion() {
if (playerStats.hasStatusEffect(CombatStatsComponent.StatusEffect.CONFUSED)) {
logger.info("PLAYER is CONFUSED");
ArrayList<Action> actions = new ArrayList<>(List.of(Action.ATTACK, Action.GUARD, Action.SLEEP));
actions.remove(playerAction);
playerAction = actions.get((int) (MathUtils.random() * actions.size()));
moveChangedByConfusion = true;
}
}
/**
* Process Special Move status effects on the Player by reducing Player health and/or hunger.
* Updates the statusEffectDuration and removes expired effects. Confusion only lasts 1 round and is always removed.
*/
public void handleStatusEffects() {
// Don't have a status effect, can skip the rest
if (!playerStats.hasStatusEffect()) return;
//Player has been confused
if (playerStats.hasStatusEffect(CombatStatsComponent.StatusEffect.CONFUSED) && moveChangedByConfusion) {
playerStats.removeStatusEffect(CombatStatsComponent.StatusEffect.CONFUSED);
moveChangedByConfusion = false;
}
//check if player has been affected by other status effects, handle appropriately
//note current implementation means if a player is both poisoned and bleeding, they will only be affected by bleed
if (playerStats.hasStatusEffect(CombatStatsComponent.StatusEffect.BLEEDING)) {
handleBleed();
} else if (playerStats.hasStatusEffect(CombatStatsComponent.StatusEffect.POISONED)) {
handlePoisoned();
} else if (playerStats.hasStatusEffect(CombatStatsComponent.StatusEffect.SHOCKED)) {
handleShocked();
}
}
private void handlePoisoned() {
if (statusEffectDuration == 0) {
statusEffectDuration = playerStats.getStatusEffectDuration(CombatStatsComponent.StatusEffect.POISONED);
} else {
// Poison reduces hunger by 30% each round.
playerStats.addHunger((int) (-0.3 * playerStats.getMaxHunger()));
if (--statusEffectDuration <= 0) {
playerStats.removeStatusEffect(CombatStatsComponent.StatusEffect.POISONED);
}
}
}
.
.
.
- The
CombatMoveComponent
class is extensively unit tested for each method. - The base
CombatMove
class is extensively unit tested for each method. - The
SpecialMove
and it's concrete subclassSpecialWaterMove
are tested method-wise.
/**
* Unit tests for the SpecialWaterMove class.
* These tests use Mockito to mock the behaviour of dependent components (e.g., CombatStatsComponent).
*/
@ExtendWith(GameExtension.class)
class SpecialWaterMoveTest {
private SpecialWaterMove specialWaterMove;
private CombatStatsComponent mockTargetStats;
private CombatStatsComponent mockAttackerStats;
/**
* Initial setup before each test. Creates an instance of SpecialWaterMove and
* mocks the necessary dependencies.
*/
@BeforeEach
void setUp() {
// Create an instance of SpecialWaterMove with a mock move name and hunger cost.
specialWaterMove = new SpecialWaterMove("Water Fury", 30);
// Mock the target and attacker stats (CombatStatsComponent).
mockTargetStats = mock(CombatStatsComponent.class);
mockAttackerStats = mock(CombatStatsComponent.class);
}
/**
* Test to verify that the applyDebuffs method correctly applies the debuff to the target
* by reducing strength and defense, and applies a random status effect.
*/
@Test
void testApplyDebuffs() {
// Act: Apply the debuffs to the target stats.
specialWaterMove.applyDebuffs(mockTargetStats);
// Assert: Verify that the target's strength and defense are decreased.
verify(mockTargetStats).addStrength(-20);
verify(mockTargetStats).addDefense(-10);
// Capture the added status effect (CONFUSED or POISONED).
ArgumentCaptor<CombatStatsComponent.StatusEffect> statusCaptor = ArgumentCaptor.forClass(CombatStatsComponent.StatusEffect.class);
verify(mockTargetStats).addStatusEffect(statusCaptor.capture());
CombatStatsComponent.StatusEffect appliedEffect = statusCaptor.getValue();
assertTrue(appliedEffect == CombatStatsComponent.StatusEffect.CONFUSED ||
appliedEffect == CombatStatsComponent.StatusEffect.POISONED,
"Random status effect should be CONFUSED or POISONED.");
}
/**
* Test to verify that the applyBuffs method correctly buffs Water Boss's strength
* and defense by the expected amounts.
*/
@Test
void testApplyBuffs() {
// Act: Apply the buffs to the attacker's stats.
specialWaterMove.applyBuffs(mockAttackerStats);
// Assert: Verify that the attacker's strength and defense are increased.
verify(mockAttackerStats).addStrength(10);
verify(mockAttackerStats).addDefense(25);
}
/**
* Test to ensure that the logger outputs the correct message when applyDebuffs is called.
* We can test the side effects (logging) of the method using Mockito's verification features.
*/
@Test
void testApplyDebuffsLogsCorrectMessage() {
// Act: Apply the debuffs to trigger the logger.
specialWaterMove.applyDebuffs(mockTargetStats);
// Since logger is static and logs to output, here we focus on behaviour verification (mock calls).
verify(mockTargetStats).addStrength(-20);
verify(mockTargetStats).addDefense(-10);
verify(mockTargetStats, times(1)).addStatusEffect(any(CombatStatsComponent.StatusEffect.class));
}
/**
* Test to ensure that the logger outputs the correct message when applyBuffs is called.
* Again, this is focused on verifying behaviour and state, not direct logging output.
*/
@Test
void testApplyBuffsLogsCorrectMessage() {
// Set up mock stats to return specific values.
when(mockAttackerStats.getStrength()).thenReturn(50);
when(mockAttackerStats.getDefense()).thenReturn(75);
// Act: Apply the buffs to trigger the logger.
specialWaterMove.applyBuffs(mockAttackerStats);
// Assert: Verify that the logger logs the correct message for buffs.
verify(mockAttackerStats, times(1)).addStrength(10);
verify(mockAttackerStats, times(1)).addDefense(25);
}
}