Lobby barrel-smash reward + roll/slam barrel destruction

Bull's boulder roll now homes on and ploughs through lobby barrels, and
the slam shockwave bursts them within its radius. HUD tallies barrels on
load and fires a confetti "WOooOoW!" + two gold bonus HP pips when the
last one breaks; gates open from run state as fights are cleared.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 01:38:57 +03:00
parent 5c1e881a53
commit 34dc5b026e
9 changed files with 301 additions and 43 deletions
+54 -7
View File
@@ -34,11 +34,20 @@ var pending_col: int = -1
## Debug override — when set, active_level() returns this regardless of map state.
## Set via the console `level` command; cleared on start_new_run().
var debug_level_override: LevelDef = null
## The lobby is an interstitial hub between fights, not a map node: the player is sent
## here after clearing a level (go_to_lobby()) to move around freely before choosing the
## next node. While set, active_level() serves the lobby; cleared once a node is picked
## (select_node) or a fresh run begins.
## The lobby is the hub the run starts and returns to: a walk-around room whose gates lead
## into the fights. While set, active_level() serves the lobby; cleared once a gate is
## entered (choose_level) and set again on a win (return_to_lobby).
var enter_lobby: bool = false
## Level picked at a lobby gate and being fought now — active_level() serves it while
## enter_lobby is false. Null in the lobby itself.
var lobby_choice: LevelDef = null
## Ids of levels the player has cleared this run (StringName -> true). Drives which lobby
## gates are open: the bear gate stays shut until the arena is beaten.
var cleared: Dictionary = {}
## Run-long bonus health (yellow pips) earned from lobby rewards. Persists across level
## loads — the player reads it on spawn — and is capped so it can't stack past BONUS_HP_CAP.
var bonus_hp: int = 0
const BONUS_HP_CAP: int = 2
# Level catalog. Every level shares the shell scene; what changes is the level module
# (its geometry + spawners) and the opponent. The arena (matador wave, normal + boss
@@ -85,6 +94,8 @@ func active_level() -> LevelDef:
return debug_level_override
if enter_lobby:
return _lobby
if lobby_choice != null:
return lobby_choice
var n := node_at(pending_row, pending_col)
if n == null:
n = current_node()
@@ -115,7 +126,11 @@ func start_new_run() -> void:
pending_row = -1
pending_col = -1
debug_level_override = null
enter_lobby = false
# The run opens in the lobby hub; its gates lead into the fights.
enter_lobby = true
lobby_choice = null
cleared = {}
bonus_hp = 0
run_started.emit()
@@ -177,10 +192,42 @@ func select_node(node: MapNode) -> void:
## Send the player to the lobby hub instead of a fight — active_level() serves the lobby
## until a node is picked. Called after clearing a level so the player lands in the lobby
## rather than jumping straight to the next fight.
## until a gate is entered.
func go_to_lobby() -> void:
enter_lobby = true
lobby_choice = null
## Enter the fight behind a lobby gate: active_level() serves it until the player wins and
## returns to the lobby. Ignored for an unknown id.
func choose_level(id: StringName) -> void:
var l := get_level(id)
if l == null:
return
enter_lobby = false
lobby_choice = l
## Land back in the lobby after a win, banking the cleared level so its follow-on gates
## open (clearing the arena unlocks the bear gate).
func return_to_lobby() -> void:
if lobby_choice != null:
cleared[lobby_choice.id] = true
go_to_lobby()
## Whether the bear gate should be open — the arena must be cleared first.
func bear_gate_open() -> bool:
return cleared.has(&"arena")
## Bank bonus health for the rest of the run, clamped to BONUS_HP_CAP. Returns how many
## pips were actually added (0 once the cap is reached) so the HUD only heals the bull when
## there's headroom — a repeat barrel clear still cheers but grants nothing.
func add_bonus_hp(amount: int) -> int:
var before := bonus_hp
bonus_hp = clampi(bonus_hp + amount, 0, BONUS_HP_CAP)
return bonus_hp - before
## Commit the level just won: move the player onto the pending node (or leave them at the