Cool Down System - UQcsse3200/2026-studio-3 GitHub Wiki
Each card in the the player deck is now has a unique card ID attached to it via the cardInstanceFactory
public void addCard(String cardId) {
if (!canAddCard(cardId)) {
throw new IllegalArgumentException("cardId must be registered");
}
cards.add(cardInstanceFactory.create(cardId));
}
This allow the cards to have a "cool down" that disable the card when played for x amount of runs.
This disablement of card is done through the UI level; the status and tracking is stored in CardPlayService (specifically its internal CardCooldownTracker, backed by BattleDeck's discard pile). The UI never queries the tracker directly, it just checks whether a card's exact instance is currently in the discard pile, and renders that slot as dimmed and non-interactive if so.
New, instance-aware methods sit alongside them for code that actually needs to tell copies apart:
| Method | Purpose |
|---|---|
PlayerDeck.getCards() |
every owned card, as instances |
BattleDeck.getHandInstances() / getDiscardPileInstances() / getDrawPileInstances()
|
per-pile snapshots, as instances |
BattleDeck.getAllInstances() |
every card in the battle deck, any pile |
BattleDeck.discardCardInstance(cardId) |
discards the first match and returns which instance moved |
BattleDeck.retrieveInstanceFromDiscard(instance) |
moves one exact instance back to hand, not "first match by ID" |
BattleDeck.setHandInstances(instances) |
replaces the hand with an exact set of instances (used by deck rearrange) |
All of these return/accept com.csse3200.game.cards.runtime.CardInstance.
sequenceDiagram
actor Player
participant BC as BattleController
participant CPS as CardPlayService
participant CD as CardCooldown
participant Tr as CardCooldownTracker
participant Deck as BattleDeck
participant UI as BattleScreen
Note over Player,UI: Turn N: playing a card
Player->>BC: play card (instanceId, target)
BC->>CPS: playCard(request)
CPS->>CPS: validate, spend energy, resolve effects
CPS->>Deck: discardCardInstance(instanceId)
CPS->>CD: roundsFor(card)
CD-->>CPS: 1, 2 or 3 rounds
CPS->>Tr: trackDiscard(instance, rounds)
CPS-->>BC: CardPlayResult
BC->>UI: "handChanged", card shown greyed out (disabled)
Note over Player,UI: Enemy turn: cooldowns do not tick
Note over Player,UI: Turn N+1: start of player round
BC->>BC: enterPlayerStart(): refill energy, reset block, ...
BC->>CPS: onPlayerRoundStart()
CPS->>Tr: tickRoundAndRetrieve()
loop each tracked card
Tr->>Tr: roundsRemaining - 1
opt roundsRemaining <= 0
Tr->>Deck: retrieveInstanceFromDiscard(instance)
Deck->>Deck: discard pile to hand
Tr->>Tr: stop tracking it
end
end
Tr-->>CPS: retrieved instances
CPS-->>BC: retrieved instances
opt any retrieved
BC->>BC: narrate("A card cooled down and returned to your hand.")
BC->>UI: "handChanged", card playable again
end
graph LR
A["Card played"] --> B["BattleDeck.discardCardInstance"]
B --> C["Instance moves: hand → discard"]
C --> D["CardCooldownTracker starts counting"]
D --> E{"Round tick:<br/>roundsRemaining reaches 0?"}
E -- no --> D
E -- yes --> F["BattleDeck.retrieveInstanceFromDiscard"]
F --> G["Instance moves: discard → hand"]
G --> H["BattleScreen slot for that instance<br/>renders enabled again"]
The deck-rearrange popup (DeckEditorComponent) lets the player pick which 5 CardInstances make
up their hand from their entire pool (draw pile + hand + discard pile combined). A card currently
on cooldown can be selected too, but since it can't physically be "in hand" while still cooling
down, CardPlayService.rearrangeHand:
- moves only the not-currently-discarded part of the selection into the real battle-deck hand
(
BattleDeck.setHandInstances), and - leaves any selected-but-discarded instances exactly where they are, cooldown untouched.
The full selection (including the still-cooling-down picks) becomes the new handRowOrder in
BattleScreen, so a reserved cooldown card shows up dimmed in its slot and automatically
un-dims itself the moment CardCooldownTracker retrieves it. No special-casing needed, because
that's just the normal dimming check described above.
sequenceDiagram
actor Player
participant UI as BattleScreen (battleUi)
participant DE as DeckEditorComponent
participant Popup as PopupDisplay
participant CPS as CardPlayService
participant Deck as BattleDeck
participant Pool as ClickableFactory (poolFactory)
participant Disp as Deck editor displays
Note over Player,Disp: Opening the editor
Player->>UI: click "Card Inventory"
UI->>DE: "openMenu" event, open()
DE->>CPS: currentHand(), allInstances(), discardedInstances()
CPS->>Deck: read hand, all piles, discard pile
Note right of DE: pool = hand first, then the rest<br/>selected = current hand<br/>scrollRow = 0
DE->>Popup: show()
Popup-->>DE: onShow, onOpened()
DE->>Disp: OPENED
DE->>Pool: rebuildByTrigger("toggleDeckCard", page of card records)
DE->>Pool: show scroll buttons (if > 2 rows) and "Set Deck"
DE->>Disp: BADGES, PREVIEW, SUMMARY, ERROR("")
DE->>Pool: bringToFront()
Note over Player,Disp: Selecting cards
Player->>Pool: click a card
Pool->>DE: "toggleDeckCard"(instanceId), toggleSelection()
alt already selected
DE->>DE: remove from selected
else hand already has 5
DE->>Disp: ERROR("Hand is full...")
else
DE->>DE: add to selected (selection order = hand order)
end
DE->>Disp: PREVIEW, BADGES (numbered), SUMMARY
Note over Player,Disp: Scrolling
Player->>Pool: click ^ or v (or mouse wheel)
Pool->>DE: "deckScrollUp"/"deckScrollDown", scrollBy(±1)
DE->>Pool: rebuildByTrigger("toggleDeckCard", new page)
Note over Player,Disp: Confirming
Player->>Pool: click "Set Deck"
Pool->>DE: "deckSetDeck", onSetDeckClicked()
alt selected != 5
DE->>Disp: ERROR("Hand must contain exactly 5 cards...")
else
DE->>CPS: rearrangeHand(selected)
CPS->>CPS: drop cards still in the discard pile (on cooldown)
alt playable cards match the current hand
CPS-->>DE: false (no change, no energy spent)
else not enough energy
CPS-->>DE: throws IllegalStateException
DE->>Disp: ERROR("Not enough energy...")
else
CPS->>CPS: spend 1 energy
CPS->>Deck: setHandInstances(playable)
Note right of Deck: old hand + draw pile reshuffled<br/>discard pile untouched
CPS-->>DE: true
end
DE->>UI: onDeckChanged(selected), onDeckRearranged()
UI->>UI: handRowOrder = selected, rebuildHandWidgets()
DE->>Popup: hide()
Popup-->>DE: onHide, onClosed()
DE->>Disp: CLOSED (clears badges, preview, text)
DE->>Pool: remove card widgets, hide scroll and "Set Deck"
end