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:
@@ -1,8 +1,13 @@
|
||||
extends Node3D
|
||||
## Lobby geometry — watches the run state and raises the bear-gate grid once the arena has
|
||||
## been cleared. The DP flag stays as a designer override for testing the animation.
|
||||
|
||||
var bear_gate_open := false
|
||||
|
||||
func _process(_delta):
|
||||
if DP.b("gate_unlocked_bear") and not bear_gate_open:
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if bear_gate_open:
|
||||
return
|
||||
if Run.bear_gate_open() or DP.b("gate_unlocked_bear"):
|
||||
bear_gate_open = true
|
||||
$AnimationPlayer.play("Gate_opening")
|
||||
|
||||
@@ -42,6 +42,13 @@ var _ability_bar: HBoxContainer
|
||||
var _ability_panels: Array[PanelContainer] = []
|
||||
var _bull_bg: TextureRect
|
||||
|
||||
# Lobby barrel-smash reward — tally the barrels present on load; when the last one bursts,
|
||||
# throw confetti + a "WOooOoW!" and grant the bull two yellow bonus pips. Latched so it
|
||||
# fires once per lobby visit.
|
||||
var _barrels_remaining: int = 0
|
||||
var _barrel_reward_given: bool = false
|
||||
const _BARREL_BONUS_HP: int = 2
|
||||
|
||||
# Red damage vignette — a full-screen shader ColorRect flashed on every hit.
|
||||
const _VIGNETTE_SHADER := "res://damage_vignette.gdshader"
|
||||
var _vignette: ColorRect
|
||||
@@ -90,6 +97,7 @@ func _ready() -> void:
|
||||
_build_touch_controls()
|
||||
_update_control_hints()
|
||||
_find_spawner.call_deferred()
|
||||
_watch_barrels.call_deferred()
|
||||
|
||||
|
||||
# On-screen joystick + ability buttons for touch devices. Self-gating: the overlay
|
||||
@@ -213,6 +221,104 @@ func _find_spawner() -> void:
|
||||
_spawner.all_defeated.connect(_on_all_defeated)
|
||||
|
||||
|
||||
# ── Lobby barrel-smash reward ───────────────────────────────────────────────────
|
||||
|
||||
# Tally the barrels the current level loaded with and listen for each one bursting. Only
|
||||
# the lobby has barrels, so on every other level this simply finds none and stands down.
|
||||
func _watch_barrels() -> void:
|
||||
var barrels := get_tree().get_nodes_in_group(&"barrel")
|
||||
_barrels_remaining = barrels.size()
|
||||
if _barrels_remaining == 0:
|
||||
return
|
||||
for barrel: Node in barrels:
|
||||
barrel.destroyed.connect(_on_barrel_destroyed)
|
||||
|
||||
|
||||
func _on_barrel_destroyed() -> void:
|
||||
_barrels_remaining -= 1
|
||||
if _barrels_remaining > 0 or _barrel_reward_given:
|
||||
return
|
||||
_barrel_reward_given = true
|
||||
# Bank the reward run-wide (capped at +2). Only heal the bull for the pips that were
|
||||
# actually added — if it already carried the cap over from an earlier lobby, it keeps
|
||||
# the cheer but no new health.
|
||||
var granted := Run.add_bonus_hp(_BARREL_BONUS_HP)
|
||||
if granted > 0 and _player != null and _player.has_method(&"grant_bonus_hp"):
|
||||
_player.call(&"grant_bonus_hp", granted)
|
||||
_celebrate()
|
||||
|
||||
|
||||
# The "cleared the lobby" flourish: a burst of confetti and a big springy "WOooOoW!".
|
||||
func _celebrate() -> void:
|
||||
_spawn_confetti()
|
||||
_show_wow()
|
||||
|
||||
|
||||
func _show_wow() -> void:
|
||||
var wow := Label.new()
|
||||
wow.text = "WOooOoW!"
|
||||
wow.set_anchors_preset(Control.PRESET_CENTER)
|
||||
wow.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||
wow.grow_vertical = Control.GROW_DIRECTION_BOTH
|
||||
wow.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
wow.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
wow.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
wow.add_theme_font_override("font", UiFonts.title())
|
||||
wow.add_theme_font_size_override("font_size", 96)
|
||||
wow.add_theme_color_override("font_color", _HP_BONUS)
|
||||
wow.add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 0.9))
|
||||
wow.add_theme_constant_override("outline_size", 8)
|
||||
wow.pivot_offset = Vector2.ZERO
|
||||
wow.scale = Vector2(0.2, 0.2)
|
||||
add_child(wow)
|
||||
# Keep the pop growing from the label's centre once it has a real size.
|
||||
await get_tree().process_frame
|
||||
wow.pivot_offset = wow.size * 0.5
|
||||
|
||||
var tw := create_tween()
|
||||
tw.tween_property(wow, "scale", Vector2.ONE, 0.35).set_trans(Tween.TRANS_BACK).set_ease(
|
||||
Tween.EASE_OUT)
|
||||
tw.tween_interval(1.1)
|
||||
tw.parallel().tween_property(wow, "modulate:a", 0.0, 0.6)
|
||||
tw.parallel().tween_property(wow, "position:y", wow.position.y - 40.0, 0.6)
|
||||
tw.tween_callback(wow.queue_free)
|
||||
|
||||
|
||||
func _spawn_confetti() -> void:
|
||||
const _CONFETTI_COLORS: Array[Color] = [
|
||||
Color(0.98, 0.24, 0.28), # red
|
||||
Color(1.00, 0.78, 0.20), # gold
|
||||
Color(0.30, 0.80, 0.40), # green
|
||||
Color(0.30, 0.62, 1.00), # blue
|
||||
Color(0.85, 0.40, 0.95), # violet
|
||||
Color(1.00, 1.00, 1.00), # white
|
||||
]
|
||||
var vp := get_viewport().get_visible_rect().size
|
||||
# One emitter per colour, raining down across the top of the screen.
|
||||
for col: Color in _CONFETTI_COLORS:
|
||||
var p := CPUParticles2D.new()
|
||||
p.position = Vector2(vp.x * 0.5, -20.0)
|
||||
p.emission_shape = CPUParticles2D.EMISSION_SHAPE_RECTANGLE
|
||||
p.emission_rect_extents = Vector2(vp.x * 0.5, 6.0)
|
||||
p.amount = 40
|
||||
p.lifetime = 2.6
|
||||
p.one_shot = true
|
||||
p.explosiveness = 0.35
|
||||
p.direction = Vector2(0.0, 1.0)
|
||||
p.spread = 25.0
|
||||
p.gravity = Vector2(0.0, 420.0)
|
||||
p.initial_velocity_min = 120.0
|
||||
p.initial_velocity_max = 260.0
|
||||
p.angular_velocity_min = -360.0
|
||||
p.angular_velocity_max = 360.0
|
||||
p.scale_amount_min = 4.0
|
||||
p.scale_amount_max = 8.0
|
||||
p.color = col
|
||||
add_child(p)
|
||||
# Self-clean once the burst has fully fallen.
|
||||
get_tree().create_timer(p.lifetime + 0.5).timeout.connect(p.queue_free)
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed and not event.echo:
|
||||
if _game_over and event.physical_keycode in [KEY_ENTER, KEY_KP_ENTER]:
|
||||
@@ -238,14 +344,12 @@ func _restart() -> void:
|
||||
get_tree().reload_current_scene()
|
||||
|
||||
|
||||
# A cleared level advances the run and drops the player into the lobby hub: commit the
|
||||
# win, flag the lobby as the active level, then reload the shell so it builds the lobby.
|
||||
# (Choosing the next map node happens from the lobby via trigger areas, added later.)
|
||||
# A cleared level drops the player back into the lobby hub: bank the win (which opens the
|
||||
# gates it unlocks) and reload the shell so it rebuilds the lobby.
|
||||
func _continue_to_lobby() -> void:
|
||||
get_tree().paused = false
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
Run.complete_current_level()
|
||||
Run.go_to_lobby()
|
||||
Run.return_to_lobby()
|
||||
get_tree().change_scene_to_file(Run.active_level().scene_path)
|
||||
|
||||
|
||||
@@ -422,6 +526,9 @@ func _build_game_over() -> void:
|
||||
const _HP_FULL: Color = Color(0.82, 0.16, 0.12)
|
||||
const _HP_EMPTY: Color = Color(0.35, 0.05, 0.05)
|
||||
const _HP_SOCKET: Color = Color(0.05, 0.02, 0.02, 0.92)
|
||||
# Bonus pips (barrel-smash reward) read as bright gold-yellow so they stand apart from the
|
||||
# red life bar.
|
||||
const _HP_BONUS: Color = Color(1.00, 0.84, 0.18)
|
||||
|
||||
const _HP_PIP_W: float = 22.0
|
||||
const _HP_PIP_H: float = 14.0
|
||||
@@ -500,10 +607,15 @@ func _on_health_changed(current: int, max_hp: int) -> void:
|
||||
var cur := maxi(current, 0)
|
||||
var frac := 0.0 if max_hp <= 0 else clampf(float(cur) / float(max_hp), 0.0, 1.0)
|
||||
var lit := _HP_FULL.lerp(_HP_EMPTY, 1.0 - frac)
|
||||
# Bonus pips sit at the high end of the bar; paint those top pips yellow when lit.
|
||||
var bonus := 0
|
||||
if _player != null and _player.has_method(&"get_bonus_hp"):
|
||||
bonus = _player.call(&"get_bonus_hp")
|
||||
var base_max := max_hp - bonus
|
||||
for i: int in _hp_seg_fills.size():
|
||||
var fill := _hp_seg_fills[i]
|
||||
fill.visible = i < cur
|
||||
fill.color = lit
|
||||
fill.color = _HP_BONUS if i >= base_max else lit
|
||||
|
||||
|
||||
const _SLOT_SEPARATION: int = 10
|
||||
|
||||
+54
-7
@@ -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
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
extends Area3D
|
||||
|
||||
## Lobby gate into the arena fight. Always open — the arena is the run's first door.
|
||||
|
||||
func _ready() -> void:
|
||||
# Connect the signal via code
|
||||
body_entered.connect(_on_body_entered)
|
||||
|
||||
|
||||
func _on_body_entered(body: Node3D) -> void:
|
||||
print("Something entered the trigger: ", body.name)
|
||||
# Check if the colliding object is the player
|
||||
if body.is_in_group("player"):
|
||||
#CHANGE TO BEAR LEVEL
|
||||
print("go to arena level")
|
||||
if not body.is_in_group("player"):
|
||||
return
|
||||
Run.choose_level(&"arena")
|
||||
get_tree().change_scene_to_file(Run.active_level().scene_path)
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
extends Area3D
|
||||
|
||||
## Lobby gate into the Bear's Den. Shut until the arena is cleared (Run.bear_gate_open()),
|
||||
## which is also what plays the grid-opening animation — so the trigger only fires once the
|
||||
## gate has actually risen.
|
||||
|
||||
func _ready() -> void:
|
||||
# Connect the signal via code
|
||||
body_entered.connect(_on_body_entered)
|
||||
|
||||
|
||||
func _on_body_entered(body: Node3D) -> void:
|
||||
print("Something entered the trigger: ", body.name)
|
||||
# Check if the colliding object is the player
|
||||
if body.is_in_group("player"):
|
||||
#CHANGE TO BEAR LEVEL
|
||||
print("go to bear level")
|
||||
if not body.is_in_group("player"):
|
||||
return
|
||||
if not Run.bear_gate_open():
|
||||
return
|
||||
Run.choose_level(&"bear")
|
||||
get_tree().change_scene_to_file(Run.active_level().scene_path)
|
||||
|
||||
@@ -67,6 +67,9 @@ signal health_changed(current: int, max_hp: int)
|
||||
signal hit_taken(cause: String)
|
||||
var _max_hp: int = 10
|
||||
var _hp: int = 10
|
||||
# Bonus pips granted by lobby rewards (e.g. smashing every barrel). Tacked on top of the
|
||||
# base bar; the HUD paints these top pips yellow instead of red.
|
||||
var _bonus_hp: int = 0
|
||||
var _hit_iframes: float = 0.0
|
||||
|
||||
var _huff_player: AudioStreamPlayer = null
|
||||
@@ -93,6 +96,11 @@ func _ready() -> void:
|
||||
RigidSkin.convert_tree(self)
|
||||
_max_hp = maxi(1, int(DP.f("bull_max_hp")))
|
||||
_hp = _max_hp
|
||||
# Carry any run-long bonus pips (barrel reward) into this level so they survive the
|
||||
# scene reload between fights; the HUD paints these top pips yellow.
|
||||
_bonus_hp = Run.bonus_hp
|
||||
_max_hp += _bonus_hp
|
||||
_hp += _bonus_hp
|
||||
_cube_scale = cube_guy.scale
|
||||
_setup_hoof_dust()
|
||||
_setup_legs()
|
||||
@@ -511,6 +519,7 @@ func _tick_roll(delta: float, _steer: Vector3) -> void:
|
||||
|
||||
move_and_slide()
|
||||
_was_on_floor = is_on_floor()
|
||||
_smash_barrels_in_radius(DP.f("roll_hit_radius"))
|
||||
_roll_try_pop(speed)
|
||||
|
||||
|
||||
@@ -549,22 +558,37 @@ func _roll_try_pop(speed: float) -> bool:
|
||||
return false
|
||||
|
||||
|
||||
# Closest active matador we haven't already popped this roll, or null if none left.
|
||||
# Closest thing the roll should home on — an active matador (arena) or an intact barrel
|
||||
# (lobby) we haven't already smashed this roll. Null if nothing is left to hit.
|
||||
func _roll_pick_target() -> Node3D:
|
||||
var best: Node3D = null
|
||||
var best_d := INF
|
||||
for mat: Node in get_tree().get_nodes_in_group(&"matador"):
|
||||
if _roll_hit.has(mat) or not _mat_active(mat):
|
||||
continue
|
||||
var d: float = (mat as Node3D).global_position.distance_squared_to(global_position)
|
||||
for cand: Node3D in _roll_targets():
|
||||
var d: float = cand.global_position.distance_squared_to(global_position)
|
||||
if d < best_d:
|
||||
best_d = d
|
||||
best = mat as Node3D
|
||||
best = cand
|
||||
return best
|
||||
|
||||
|
||||
# Live roll targets: active matadors plus standing barrels, minus anything already popped.
|
||||
func _roll_targets() -> Array[Node3D]:
|
||||
var out: Array[Node3D] = []
|
||||
for mat: Node in get_tree().get_nodes_in_group(&"matador"):
|
||||
if not _roll_hit.has(mat) and _mat_active(mat):
|
||||
out.append(mat as Node3D)
|
||||
for barrel: Node in get_tree().get_nodes_in_group(&"barrel"):
|
||||
if _barrel_alive(barrel):
|
||||
out.append(barrel as Node3D)
|
||||
return out
|
||||
|
||||
|
||||
func _roll_target_valid() -> bool:
|
||||
return is_instance_valid(_roll_target) and _mat_active(_roll_target)
|
||||
if not is_instance_valid(_roll_target):
|
||||
return false
|
||||
if _roll_target.is_in_group(&"barrel"):
|
||||
return _barrel_alive(_roll_target)
|
||||
return _mat_active(_roll_target)
|
||||
|
||||
|
||||
# A matador still in play — valid, not ragdolled/consumed. Ragdolled bodies report
|
||||
@@ -573,6 +597,25 @@ func _mat_active(mat: Node) -> bool:
|
||||
return is_instance_valid(mat) and mat.has_method(&"is_active") and mat.call(&"is_active")
|
||||
|
||||
|
||||
# A lobby barrel still standing — valid and not already queued for removal by an earlier hit.
|
||||
func _barrel_alive(barrel: Node) -> bool:
|
||||
return is_instance_valid(barrel) and not barrel.is_queued_for_deletion()
|
||||
|
||||
|
||||
# Burst every barrel whose centre sits within range_m on the ground plane. Shared by the
|
||||
# boulder roll (which ploughs straight through them) and the slam shockwave. Each barrel
|
||||
# self-removes and leaves the &"barrel" group, so the HUD's clear-the-lobby tally counts it
|
||||
# exactly once.
|
||||
func _smash_barrels_in_radius(range_m: float) -> void:
|
||||
for barrel: Node in get_tree().get_nodes_in_group(&"barrel"):
|
||||
if not _barrel_alive(barrel):
|
||||
continue
|
||||
var to_b: Vector3 = (barrel as Node3D).global_position - global_position
|
||||
to_b.y = 0.0
|
||||
if to_b.length() <= range_m:
|
||||
barrel.call(&"destroy_barrel")
|
||||
|
||||
|
||||
# True while the boulder roll is live — matadors defer their charge-gore to the roll's own pop
|
||||
# (is_rolling guard in matador._take_bull_charge) so the ball pops instead of quietly goring.
|
||||
func is_rolling() -> bool:
|
||||
@@ -1084,6 +1127,7 @@ func _physics_process(delta: float) -> void:
|
||||
_play_idle()
|
||||
_spawn_slam_fx()
|
||||
_hit_matadors_radius(DP.f("slam_range"), DP.f("slam_strength"), DP.f("slam_launch_up"))
|
||||
_smash_barrels_in_radius(DP.f("slam_range"))
|
||||
|
||||
# ── Dust ──────────────────────────────────────────────────────────────────
|
||||
if flat_speed > DP.f("dust_charge_spd") and on_floor:
|
||||
@@ -1110,6 +1154,22 @@ func get_max_hp() -> int:
|
||||
return _max_hp
|
||||
|
||||
|
||||
# Bonus pips currently on the bar — the HUD colours this many top pips yellow.
|
||||
func get_bonus_hp() -> int:
|
||||
return _bonus_hp
|
||||
|
||||
|
||||
# Award extra health (from a lobby reward): grows both the cap and the current bar so the
|
||||
# new pips arrive full, and marks them as bonus so the HUD paints them yellow.
|
||||
func grant_bonus_hp(amount: int) -> void:
|
||||
if amount <= 0:
|
||||
return
|
||||
_bonus_hp += amount
|
||||
_max_hp += amount
|
||||
_hp += amount
|
||||
health_changed.emit(_hp, _max_hp)
|
||||
|
||||
|
||||
# A clean hit — thrown or swung blade — costs the bull one HP pip; the run ends only
|
||||
# when the last pip is gone. `cause` ("gored" / "thrown") tags how it happened for the
|
||||
# death notice. I-frames absorb a flurry so several blades in one pass drop one pip,
|
||||
|
||||
@@ -46,6 +46,13 @@ func _run() -> void:
|
||||
quit(1)
|
||||
return
|
||||
|
||||
# The run now opens in the fightless lobby; force the matador arena so the scene loads
|
||||
# with live matadors for the checks below.
|
||||
var run: Node = root.get_node_or_null("/root/Run")
|
||||
if run != null:
|
||||
run.ensure_run() # build the run first — the shell's own ensure_run would wipe the override
|
||||
run.debug_level_override = run.get_level(&"arena")
|
||||
|
||||
var scene: Node = scene_res.instantiate()
|
||||
root.add_child(scene)
|
||||
current_scene = scene # match runtime: game code (sword throw, overlays) uses current_scene
|
||||
|
||||
@@ -38,6 +38,13 @@ func _run() -> void:
|
||||
if dp:
|
||||
dp.set_value("mat_spawn_count", STRESS_MATADORS)
|
||||
|
||||
# The run now opens in the fightless lobby; force the matador arena so the stress scene
|
||||
# loads with matadors.
|
||||
var run: Node = root.get_node_or_null("/root/Run")
|
||||
if run != null:
|
||||
run.ensure_run() # build the run first — the shell's own ensure_run would wipe the override
|
||||
run.debug_level_override = run.get_level(&"arena")
|
||||
|
||||
var scene_res := load("res://scene.tscn")
|
||||
if not scene_res:
|
||||
push_error("performance_test: failed to load scene.tscn")
|
||||
|
||||
+25
-7
@@ -1,23 +1,41 @@
|
||||
extends StaticBody3D
|
||||
extends StaticBody3D
|
||||
## A smashable lobby barrel. Joins the &"barrel" group so the HUD can tally how many
|
||||
## remain and fire the "cleared the lobby" reward once the last one bursts.
|
||||
|
||||
signal destroyed
|
||||
|
||||
# Preload the broken barrel scene
|
||||
const BARREL_BROKEN_SCENE = preload("res://tscn_s/BarrelBroken.tscn")
|
||||
|
||||
var _destroyed := false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group(&"barrel")
|
||||
|
||||
|
||||
func destroy_barrel():
|
||||
# Guard against a double hit in the same frame counting the same barrel twice.
|
||||
if _destroyed:
|
||||
return
|
||||
_destroyed = true
|
||||
|
||||
# 1. Instantiate the broken pieces
|
||||
var broken_instance = BARREL_BROKEN_SCENE.instantiate()
|
||||
|
||||
|
||||
# 2. Place the fragments exactly where the intact barrel currently is
|
||||
broken_instance.global_transform = self.global_transform
|
||||
|
||||
|
||||
# 3. Add the fragments to the main game tree
|
||||
get_parent().add_child(broken_instance)
|
||||
|
||||
|
||||
# 4. (Optional) Apply a physics push to the fragments if you want an explosive effect
|
||||
for child in broken_instance.get_children():
|
||||
if child is RigidBody3D:
|
||||
# Pushes pieces slightly outward and upward
|
||||
var random_direction = Vector3(randf_range(-1, 1), randf_range(0.5, 1.5), randf_range(-1, 1)).normalized()
|
||||
child.apply_central_impulse(random_direction * 5.0)
|
||||
|
||||
# 5. Delete the intact barrel
|
||||
child.apply_central_impulse(random_direction * 5.0)
|
||||
|
||||
# 5. Announce the smash (the HUD counts down remaining barrels) and remove the barrel
|
||||
destroyed.emit()
|
||||
queue_free()
|
||||
|
||||
Reference in New Issue
Block a user