Skip to content

Tutorial Enemy Integration Debug Commands and Forms

1109TT edited this page Oct 19, 2026 · 1 revision

Tutorial Enemy Integration, Battle Debug Commands and Enemy Forms

Team 1 · Issue #436 · Related tutorial integration #365 · Last reviewed: 19 October 2026

Overview and scope

This work connects the Votive Attendant to the tutorial battle, adds two commands for reproducing enemy states during testing, and supports visual form changes for the Riven Knight and Tomb Guardian.

  • The tutorial battle uses the votive_attendant configuration, atlas and dedicated TutorialEnemyAI.
  • setenemyhp changes the health of an enemy in a zero-based battle slot.
  • forceintent replaces one already-displayed upcoming intent; the next selection returns to the normal AI.
  • The Riven Knight's form follows the AI's actual enrage state.
  • The Tomb Guardian holds its taunting form while its own Taunt is active.
  • A missing form-specific animation falls back to the normal animation for the same action.

Li provided the tutorial enemy's AI decision pattern. This ticket integrates that AI into the battle rather than claiming authorship of its original design. The final form artwork and its atlas checks are documented separately in Enemy Action and Form Animations and its test plan.

Architecture

classDiagram
    direction LR

    class BattleScreen {
        +createBattle()
        +registerCommands()
    }
    class BattleController {
        +getEnemyInSlot(slot)
        +forceNextEnemyIntent(slot, intent)
        +getAliveTaunterTargetIds()
    }
    class SetEnemyHealthCommand {
        +action(args) boolean
    }
    class ForceIntentCommand {
        +action(args) boolean
    }
    class EnemyFactory {
        +create(config) Entity
    }
    class EnemyBehaviourComponent {
        +setAI(ai)
        +rollIntent() EnemyIntent
        +forceCurrentIntent(intent)
    }
    class EnemyAI {
        <<interface>>
        +decide(context) EnemyIntent
    }
    class TutorialEnemyAI {
        +decide(context) EnemyIntent
    }
    class EnrageLowHealthAI {
        +decide(context) EnemyIntent
        +isEnraged() boolean
    }
    class CombatStatsComponent {
        +setHealth(health)
        +applyStatusEffect(type, value, duration)
    }
    class EnemyAnimationController {
        -currentForm String
        +setForm(form)
    }
    class AnimationRenderComponent {
        +hasAnimation(name) boolean
        +startAnimation(name)
    }

    BattleScreen --> BattleController : manages battle
    BattleScreen --> EnemyFactory : creates enemies
    BattleScreen --> SetEnemyHealthCommand : registers
    BattleScreen --> ForceIntentCommand : registers
    SetEnemyHealthCommand --> BattleController : finds enemy slot
    ForceIntentCommand --> BattleController : overrides intent
    EnemyFactory --> EnemyBehaviourComponent : attaches
    EnemyFactory --> EnemyAnimationController : attaches
    EnemyBehaviourComponent --> EnemyAI : requests decision
    TutorialEnemyAI ..|> EnemyAI
    EnrageLowHealthAI ..|> EnemyAI
    BattleController --> CombatStatsComponent : reads statuses
    EnemyAnimationController --> AnimationRenderComponent : plays animation
Loading

The commands and form system reuse the existing battle components. They do not create a replacement AI framework or a second battle state.

Tutorial enemy integration

For a tutorial battle, BattleScreen selects votive_attendant instead of a normal encounter line-up. EnemyFactory creates it from configs/enemies.json and its atlas. BattleScreen then assigns TutorialEnemyAI to the enemy's EnemyBehaviourComponent.

The tutorial AI follows a fixed attack → defend → buff cycle. Its attack is designed to leave a living player with at least one health. EnemyBehaviourComponent also caps the resolved tutorial attack after combat modifiers, so Vulnerable cannot unexpectedly make that attack lethal. The buff increases the enemy's base attack for later actions.

sequenceDiagram
    participant Screen as BattleScreen
    participant Factory as EnemyFactory
    participant Behaviour as EnemyBehaviourComponent
    participant AI as TutorialEnemyAI

    Screen->>Factory: Create "votive_attendant"
    Factory-->>Screen: Enemy entity with art and components
    Screen->>Behaviour: setAI(new TutorialEnemyAI())
    loop Each enemy intent selection
        Behaviour->>AI: decide(battle context)
        AI-->>Behaviour: Attack, Defend or Buff
        Behaviour-->>Screen: Telegraph selected intent
    end
Loading

The rendered tutorial and the remaining acceptance criteria of #365 still require joint checking with Team 2. See also Tutorial Battle Setup and Enemy Integration and the Tutorial Acceptance Test Plan.

Battle debug commands

Both commands are registered in BattleScreen and are available through the battle debug terminal. Slots count from 0, left to right.

setenemyhp <slot> <health>

For example, setenemyhp 0 40 sets the first enemy's current health to 40. CombatStatsComponent.setHealth() emits the existing health-update event, allowing the visible bar to refresh. The command rejects missing or non-numeric arguments, a negative health value, and an out-of-range slot. It is useful for reaching a health-dependent phase without repeatedly playing cards; the AI evaluates that health when it next selects an intent.

forceintent <slot> <intent>

Supported names are attack, defend, silence, damage_on_card_play and taunt. For example, forceintent 0 silence changes the first enemy's already-displayed upcoming action to Silence. The override applies to that action only; the following intent is selected by its normal AI. The command rejects missing arguments, non-numeric or invalid slots, unknown names, and dead enemies. The controller accepts the override only during the player turn.

sequenceDiagram
    actor Tester
    participant Terminal
    participant Command as ForceIntentCommand
    participant Battle as BattleController
    participant Behaviour as EnemyBehaviourComponent
    participant AI as EnemyAI

    Tester->>Terminal: forceintent 0 silence
    Terminal->>Command: action(slot=0, intent=silence)
    Command->>Battle: forceNextEnemyIntent(0, silence)
    Battle->>Behaviour: forceCurrentIntent(silence)
    Behaviour-->>Tester: Display Silence as upcoming intent
    Tester->>Battle: End player turn
    Battle->>Behaviour: Execute forced intent
    Note over Behaviour,AI: Override is not a permanent AI rule
    Battle->>Behaviour: Select following intent
    Behaviour->>AI: decide(context)
    AI-->>Behaviour: Normal AI decision
Loading

Enemy form switching

EnemyAnimationController holds a currentForm, normally normal. On enemyFormChanged, it tries <action>_<form> first: for example, idle_enraged. If that animation is unavailable, it plays the normal animation for the same action. A form change does not interrupt an attack or cast already in progress; the new stance is shown when the enemy returns to idle.

Riven Knight: enrage

EnrageLowHealthAI can become enraged when health falls below its threshold or when player memory shows the player has not been defending. After the AI selects an intent, EnemyBehaviourComponent compares the AI's actual isEnraged() state with the previous visual state. It emits enemyFormChanged only when that state changes. The appearance therefore follows the AI's real state rather than inferring enrage from health alone.

Tomb Guardian: Taunt

Taunt is stored on the player under a source-specific key, TAUNT:<enemy ID>. BattleController listens for status application and removal. A Tomb Guardian enters the taunt form only while that Guardian's own Taunt is present and it is alive. Expiration or defeat removes the restriction and restores the normal form. Another enemy's Taunt must not keep this Guardian in its taunting form.

sequenceDiagram
    participant Guardian as Tomb Guardian
    participant PlayerStats as Player CombatStats
    participant Battle as BattleController
    participant Animation as EnemyAnimationController

    Guardian->>PlayerStats: Apply TAUNT:<Guardian ID>
    PlayerStats-->>Battle: statusEffectApplied
    Battle-->>Animation: enemyFormChanged("taunt")
    Animation->>Animation: Play idle_taunt if present
    Note over Animation: Missing form action falls back to normal action
    alt Taunt expires
        PlayerStats-->>Battle: statusEffectRemoved
        Battle-->>Animation: enemyFormChanged("normal")
    else Guardian is defeated
        Guardian-->>Battle: enemyDefeated
        Battle->>PlayerStats: Remove its TAUNT:<Guardian ID>
        Battle-->>Animation: enemyFormChanged("normal")
    end
Loading
Form Expected atlas region names
Riven Knight enrage idle_enraged, attack_enraged, hurt_enraged
Tomb Guardian taunt idle_taunt, hurt_taunt

The regions are optional when an animation is registered. Missing form-specific art must not crash the battle. The art-side naming and atlas checks are covered by #438's animation page.

Test strategy and plan

JUnit tests verify command validation, AI decisions, form events, status ownership and animation fallback. A rendered game is still needed to judge health-bar updates, poses, timing and the full tutorial flow.

ID Test case Expected result Verification
T1 Run setenemyhp 0 40 Enemy health becomes 40 and a health-update event is emitted Automated: BattleDebugCommandsTest — passed
T2 Give setenemyhp an invalid slot, missing argument, non-numeric value or negative health Command is rejected without changing health Automated: BattleDebugCommandsTest — passed
T3 Run forceintent 0 silence and advance the battle Silence is used for the upcoming action only; the following intent comes from normal AI selection Automated: BattleDebugCommandsTest — passed
T4 Try supported and invalid forceintent names Supported names map correctly; invalid input does not replace the current intent Automated: BattleDebugCommandsTest — passed
T5 Trigger Riven Knight enrage through health and memory conditions Visual form follows AI state; a change event is emitted when that state changes Automated: EnrageLowHealthAITest and EnemyBehaviourComponentTest — passed
T6 Apply and expire a Tomb Guardian's own Taunt; defeat a taunter Only its own active Taunt holds the form; expiration or defeat clears it Automated: BattleControllerTest — passed
T7 Request an unavailable form-specific action The matching normal action plays without a crash Automated: EnemyAnimationControllerTest and EnemyFactoryTest — passed
T8 Create the Votive Attendant and exercise its tutorial actions Correct art and AI are assigned; its cycle, buff and non-lethal attack rules work Automated: EnemyFactoryTest, TutorialEnemyAITest and EnemyBehaviourComponentTest — passed
T9 Use both commands in a rendered battle, including a multi-enemy battle Health bar and intent display visibly update for the selected slot Manual in-game check — pending
T10 Play the tutorial battle and observe elite form changes Tutorial progression, sprite alignment, timing and form transitions look correct Manual in-game check — pending
T11 Check the remaining acceptance criteria of #365 with Team 2 Tutorial integration is jointly confirmed Joint check — pending

Automated result

On the local version reviewed on 19 October 2026, seven relevant test classes completed with 174 tests, 0 failures and 0 errors (BUILD SUCCESSFUL) using JDK 21. These classes include pre-existing regression tests; 174 is not the number of tests newly added for #436. This was a targeted run, not a full-project test or a coverage measurement.

From source/, run the same targeted classes with:

.\gradlew.bat :core:test --tests "com.csse3200.game.ui.terminal.BattleDebugCommandsTest" --tests "com.csse3200.game.components.combat.BattleControllerTest" --tests "com.csse3200.game.components.enemy.EnemyBehaviourComponentTest" --tests "com.csse3200.game.components.enemy.EnemyAnimationControllerTest" --tests "com.csse3200.game.components.enemy.EnemyAI.EnrageLowHealthAITest" --tests "com.csse3200.game.components.enemy.EnemyAI.TutorialEnemyAITest" --tests "com.csse3200.game.entities.factories.EnemyFactoryTest"

Run the complete core suite separately with ./gradlew core:test (or .\gradlew.bat :core:test on Windows). The project's Gradle version requires a compatible JDK; Java 8 is too old.

Manual procedure

  1. Start the desktop game (.\gradlew.bat :desktop:run on Windows) and enter the tutorial battle. Confirm that the opponent is the Votive Attendant rather than the regular Bone Crawler.
  2. Observe several enemy turns. Confirm that it attacks, defends and buffs in its intended cycle, and that an ordinary tutorial attack cannot reduce a living player below one health.
  3. In a suitable battle, open the debug terminal with F1 and run setenemyhp 0 40. Check the correct enemy's visible health bar. Try an invalid slot and a non-numeric health value; neither should crash or change health.
  4. During the player turn, run forceintent 0 silence or another supported name. Check the displayed intent, end the turn, and confirm that the enemy performs it once. Its following intent should return to AI selection.
  5. Observe the Riven Knight entering and leaving enrage, including the memory-triggered case. Its visible form should match the actual AI state.
  6. Observe the Tomb Guardian casting Taunt. Its form should remain active only while its own Taunt is on the player and should end on expiration or defeat.
  7. Check sprite position, visible bounds, status visuals and fallback poses. Component tests cannot establish whether these look correct in the rendered game.

Record screenshots or short clips and the observed result before changing T9–T11 from pending to passed.

Current status and remaining verification

The targeted automated tests passed. The complete core test suite, the required coverage gate, rendered-game checks, and the remaining #365 joint acceptance items are not claimed as complete here. Add their actual results and evidence after they are performed.

Implementation references

  • source/core/src/main/com/csse3200/game/screens/BattleScreen.java — tutorial line-up, tutorial AI assignment and command registration.
  • source/core/src/main/com/csse3200/game/ui/terminal/commands/SetEnemyHealthCommand.java — health command and argument checks.
  • source/core/src/main/com/csse3200/game/ui/terminal/commands/ForceIntentCommand.java — intent-name mapping and argument checks.
  • source/core/src/main/com/csse3200/game/components/combat/BattleController.java — slot lookup, one-use intent override and Taunt-form coordination.
  • source/core/src/main/com/csse3200/game/components/enemy/EnemyBehaviourComponent.java — intent selection, enrage-form event and tutorial attack resolution.
  • source/core/src/main/com/csse3200/game/components/enemy/EnemyAnimationController.java — current form and animation fallback.
  • source/core/src/main/com/csse3200/game/entities/factories/EnemyFactory.java — registration of available form animations.
  • source/core/assets/configs/enemies.json — Votive Attendant configuration.

Table of Contents

Home

Game Engine

Getting Started

Entities and Components

Service Locator

Loading Resources

Logging

Unit Testing

Debug Terminal

Input Handling

UI

Animations

Audio

AI

Physics

Game Screens and Areas

Terrain

Concurrency & Threading

Settings

Encounters
Tutorials and Contextual Help (Team 2)
Cards (Team 6)
Story (Team 6)
Level design, battle system, turn order, spawn (Team 3)
Cards, Save/Load & Bestiary (Team 5)
Rewards and Battle Settlement (Team 7)

Troubleshooting

MacOS Setup Guide

Clone this wiki locally