BattleActions & BattleArea & BattleDisplay - UQcsse3200/2026-studio-3 GitHub Wiki

1. Screen navigation

flowchart TD
    MM["MainMenuScreen<br/>ClickableFactory + MainMenuActions"]
    MM -- "click 'map'" --> MMA_map["MainMenuActions.onStart<br/>runState.endRun"]
    MM -- "click 'shop'" --> MMA_shop["MainMenuActions.onShop"]
    MM -- "click 'exit'" --> Exit["GdxGame.exit"]

    MMA_map --> MAP["MapScreen<br/>ScreenType.MAP"]
    MMA_shop --> MG["MainGameScreen<br/>builds EncounterGameArea"]

    MAP --> SEED["MapScreen.startNewRun<br/>runState.startRun graph bottomNodeId<br/>start node CURRENT, neighbours AVAILABLE"]
    SEED --> DISP["MapDisplay + MapInputHandler<br/>+ MapSelectionController"]
    DISP -- "click AVAILABLE node<br/>MapSelectionController fires 'nodeSelected'" --> ENTER["MapScreen.enterEncounter nodeId<br/>runState.enterEncounter nodeId"]

    ENTER -- "RoomType COMBAT or FINAL" --> BATTLE["BattleScreen"]
    ENTER -- "RoomType SHOP or EVENT" --> ENC["EncounterScreen<br/>map team placeholder"]

    BATTLE -- "BattleController fires 'battleEnded' win" --> BEND["BattleActions.onBattleEnd win<br/>runState.completeEncounter win"]
    BEND --> EBS["EndBattleScreen<br/>VICTORY or DEFEAT"]
    EBS -- "won and run active" --> MAP
    EBS -- "lost" --> LOSE["runState.endRun"] --> MM

    ENC -- "complete" --> MAP
    ENC -- "fail" --> MM

    MG -- "EncounterGameArea chance success" --> SHOP["ShopDisplay<br/>spend gold on cards"]
Loading

2. Battle screen wiring (who talks to whom)

flowchart LR
    subgraph screen["BattleScreen"]
      FGA["ForestGameArea<br/>getPlayer / getEnemies"]
      DECK["BattleDeck"]
      LIB["CardLibrary implements CardService<br/>Team 6"]
      RESOLVER["CardEffectResolver<br/>Team 5"]
      CTRL["BattleController<br/>the FSM"]
      UI["battleUi Entity"]
    end

    LIB --> RESOLVER
    FGA --> CTRL
    DECK --> CTRL
    RESOLVER --> CTRL
    LIB --> CTRL

    UI --- BA["BattleActions"]
    UI --- CF["ClickableFactory<br/>card widgets and buttons"]
    UI --- DF["DisplayingFactory<br/>BattleLogDisplay etc"]

    BA -- "listeners and events" --- CTRL
    CF -- "'playCard' cardId targetId" --> BA
    BA -- "'handChanged' rebuildHand" --> CF
    CTRL -- "'enemyEffects'" --> T1["Team 1 enemy"]
    CTRL -- "'playerEffects'" --> T7["Team 7 player"]
    CTRL -- "'battleLog'" --> DF
Loading

3. What happens when a card is "played" (the main flow)

rite.

flowchart TD
    S1["1. Play card<br/>Clickable / DragNDrop"]
    S2["2. Route it<br/>BattleActions"]
    S3["3. Step FSM to PLAYER<br/>_ATTACK<br/>BattleController"]
    S4{"4. In hand?<br/>Enough energy?"}
    FAIL["Narrate, card stays,<br/>keep your turn"]
    S5["5. Resolve to numbers<br/>CardEffectResolver"]
    S6["6. Spend energy,<br/>discard, draw one"]
    S7["7. Broadcast + apply<br/>effects"]
    S8["8. Rebuild hand,<br/>battle log popup"]
    S9{"9. Battle over?"}
    WIN["VICTORY<br/>battleEnded, node done"]
    BACK["Back to PLAYER_TURN"]

    S1 --> S2 --> S3 --> S4
    S4 -- no --> FAIL
    S4 -- yes --> S5 --> S6 --> S7 --> S8 --> S9
    S9 -- "enemies dead" --> WIN
    S9 -- "still fighting" --> BACK
Loading
Full call-by-call sequence (every method + event)
sequenceDiagram
    autonumber
    actor P
    participant W
    participant DT
    participant BA
    participant BC
    participant FSM
    participant CS
    participant CR
    participant DK
    participant EN
    participant ST
    participant BS

    alt self-target card, e.g. Defend
        P->>W: click card widget
        W->>BA: trigger playCard with cardId and player
    else attack card
        P->>W: drag card onto enemy sprite
        W->>DT: dropped on hitbox, userObject is enemyId
        W->>BA: DragNDrop dragStop fires playCard with cardId and enemyId
    end

    BA->>BA: onCardPlayed cardId targetId
    BA->>CS: getCard cardId returns CardConfig
    BA->>BA: classifyCard gives PlayerIntent ATTACK DEFEND or OTHER
    BA->>BC: submitCardPlayRequest CardPlayRequest and intent

    BC->>FSM: getNextPhase PLAYER_TURN PLAYER_ATTACK_SELECTED
    FSM-->>BC: PLAYER_ATTACK
    BC-->>BA: fires battlePhaseChanged, re-broadcast as phaseChange
    BC->>BC: pendingCard is request, enterPlayerAttack then resolvePlayerCard

    rect rgb(235,240,255)
    note over BC,DK: BattleController.playCardThroughCardSystem request
    BC->>CS: getCard request.cardID returns CardConfig
    BC->>DK: hand contains card.id? else fail Card not in hand
    BC->>EN: canAfford card.cost? else fail Not enough energy
    BC->>CR: resolve card and playerEffectState
    CR->>CR: CardValidator.validate card
    CR->>CR: EffectExecutor.resolve per EffectConfig
    CR-->>BC: CardEffectResolution with list of ResolvedCardEffect
    BC->>EN: spendEnergy card.cost
    BC->>DK: playCard card.id, hand to discard
    BC->>DK: drawOne, draw a replacement
    BC->>BC: build CardPlayResult success
    end

    rect rgb(235,255,235)
    note over BC,ST: BattleController.dispatchCardEffects request result
    BC-->>BA: fires enemyEffects, BA re-fires to Team 1
    BC-->>BA: fires playerEffects, BA re-fires to Team 7
    BC->>ST: enemy effects DAMAGE takeDamage, POISON and VULNERABLE applyStatusEffect
    BC->>ST: player effects BLOCK addArmor, HEAL heal
    BC-->>BA: fires handChanged with updatedHand
    BA-->>BS: handChanged
    BS->>BS: ClickableFactory.rebuildHand, played card gone, drawn card shown
    end

    BC-->>BA: fires battleLog, e.g. You played strike DAMAGE 6 to enemy
    BC->>BC: finishPlayerCardAction, handle PLAYER_ACTION_RESOLVED
    BC->>BC: PLAYER_ATTACK to PLAYER_RESOLVED, then isBattleOver

    alt all enemies dead
        BC->>BC: VICTORY, enterVictory
        BC-->>BA: fires battleEnded true
        BA->>BA: runState.completeEncounter true
        BA->>BS: Gdx.app.postRunnable, game.setScreen VICTORY
    else both sides alive
        BC->>BC: PLAYER_CONTINUES, back to PLAYER_TURN
    end
Loading

Static UI

Static controls and displays are defined in:

core/assets/sprites/BattleUi.json

The current static UI includes:

  • Exit button
  • Up and down controls
  • End-turn button
  • Card instruction label
  • Battle log display

ClickableFactory loads the clickable records, while DisplayingFactory loads the displaying records.

Dynamic card hand

The player's card hand is not stored in BattleUi.json. It is generated at runtime by BattleScreen.buildHandRecords() using the cards currently contained in BattleDeck.

Each card becomes a ClickableRecord with one of two variants:

Card target Variant Interaction
━━━━━━━━━━━━━━ ━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SELF inout The player clicks the card and the target is automatically set to "player" ────────────── ───────── ──────────────────────────────────────────────────────────────────────────── Enemy target drag The player drags the card onto an enemy drop target

When a card is successfully played, the controller moves it from the hand to the discard pile and draws a replacement. It then fires handChanged.

BattleScreen listens for this event and calls:

uiFactory.rebuildHand(buildHandRecords());

This removes the old card widgets and creates a new set from the updated battle deck.

———

Card classification

Before submitting a card to the controller, BattleActions classifies it as a player intent.

The classification rules are:

  1. A card with type ATTACK becomes PlayerIntent.ATTACK.
  2. A non-attack card containing a BLOCK effect becomes PlayerIntent.DEFEND.
  3. Every other card becomes PlayerIntent.OTHER.

The selected intent determines which player-action phase the battle state machine enters:

ATTACK → PLAYER_ATTACK DEFEND → PLAYER_DEFEND OTHER → PLAYER_OTHER

This means card plays do not always enter PLAYER_ATTACK; the phase depends on the card configuration.

———

Card-play validation and failure behaviour

A submitted card is accepted only when:

  • The battle is currently in PLAYER_TURN.
  • The card exists in the card library.
  • The card is present in the player's hand.
  • The player has enough energy to pay its cost.

When the request succeeds, the controller:

  1. Resolves the card effects.
  2. Spends the required energy.
  3. Moves the card to the discard pile.
  4. Draws a replacement card.
  5. Broadcasts the resolved player and enemy effects.
  6. Applies the effects to the relevant entities.
  7. Fires handChanged.
  8. Writes a message to the battle log.
  9. Checks whether the battle has ended.
  10. Returns the state machine to PLAYER_TURN if the battle continues.

When the request fails, no energy is spent and the card remains in the player's hand. A failure message is sent to the battle log.

If BattleActions receives an unknown card ID, it ignores the request because the card cannot be found in CardLibrary.


4. Effect resolution detail (CardEffectResolver.resolve)

flowchart TD
    A["resolve CardConfig card, PlayerEffectState state"] --> B["CardValidator.validate card"]
    B -- "errors" --> ERR["throw IllegalArgumentException"]
    B -- "ok" --> C{"for each EffectConfig<br/>in card.effects"}
    C --> D["EffectExecutor.resolve cardId, effect, card.target, index, state"]
    D --> E["ResolvedCardEffect: type, value, duration, target"]
    E --> C
    C -- "done" --> F["CardEffectResolution: cardId, list of ResolvedCardEffect"]
    F --> G["enemyEffects: effects where target is not SELF"]
    F --> H["playerEffects: effects where target is SELF"]
Loading

5. Battle FSM: the phases a card action steps through

Labels are the BattleEvent that triggers each transition.

stateDiagram-v2
    [*] --> SETUP
    SETUP --> PLAYER_TURN: SETUP_COMPLETE
    PLAYER_TURN --> PLAYER_ATTACK: ATTACK_SELECTED
    PLAYER_TURN --> PLAYER_DEFEND: DEFEND_SELECTED
    PLAYER_TURN --> PLAYER_OTHER: OTHER_SELECTED
    PLAYER_ATTACK --> PLAYER_RESOLVED: ACTION_RESOLVED
    PLAYER_DEFEND --> PLAYER_RESOLVED: ACTION_RESOLVED
    PLAYER_OTHER --> PLAYER_RESOLVED: ACTION_RESOLVED
    PLAYER_RESOLVED --> PLAYER_TURN: PLAYER_CONTINUES
    PLAYER_RESOLVED --> VICTORY: ENEMIES_DEFEATED
    PLAYER_TURN --> PLAYER_END: END_REQUESTED
    PLAYER_END --> ENEMY_TURN: turn ended
    ENEMY_TURN --> PLAYER_TURN: PHASE_COMPLETE
    ENEMY_TURN --> DEFEAT: PLAYER_DEFEATED
    VICTORY --> [*]
    DEFEAT --> [*]
Loading
⚠️ **GitHub.com Fallback** ⚠️