BattleActions & BattleArea & BattleDisplay - UQcsse3200/2026-studio-3 GitHub Wiki
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"]
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
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
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
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.
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.
———
Before submitting a card to the controller, BattleActions classifies it as a player intent.
The classification rules are:
- A card with type ATTACK becomes PlayerIntent.ATTACK.
- A non-attack card containing a BLOCK effect becomes PlayerIntent.DEFEND.
- 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.
———
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:
- Resolves the card effects.
- Spends the required energy.
- Moves the card to the discard pile.
- Draws a replacement card.
- Broadcasts the resolved player and enemy effects.
- Applies the effects to the relevant entities.
- Fires handChanged.
- Writes a message to the battle log.
- Checks whether the battle has ended.
- 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.
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"]
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 --> [*]