Small update fixes

This commit is contained in:
2026-03-11 16:15:52 -04:00
parent f350aca534
commit 20f1d1789b
5 changed files with 81 additions and 36 deletions
+20 -5
View File
@@ -50,6 +50,7 @@ const BEAM_LENGTH := 500.0
const BEAM_WIDTH := 28.0 const BEAM_WIDTH := 28.0
const PULSE_FORCE := 350.0 const PULSE_FORCE := 350.0
const PULSE_CD := 5.0 const PULSE_CD := 5.0
const PULSE_RADIUS := 200.0
const HOMING_COUNT := 4 const HOMING_COUNT := 4
const HOMING_SPEED := 120.0 const HOMING_SPEED := 120.0
const HOMING_TURN := 1.8 # rad/sec const HOMING_TURN := 1.8 # rad/sec
@@ -747,9 +748,12 @@ func _attack_pulse() -> void:
if _player == null or not is_instance_valid(_player): if _player == null or not is_instance_valid(_player):
return return
var dir := (_player.global_position - global_position).normalized() # Only apply knockback if player is within pulse radius
if _player.has_method("apply_knockback"): var dist := global_position.distance_to(_player.global_position)
_player.apply_knockback(dir * PULSE_FORCE) if dist <= PULSE_RADIUS:
var dir := (_player.global_position - global_position).normalized()
if _player.has_method("apply_knockback"):
_player.apply_knockback(dir * PULSE_FORCE)
# Visual pulse ring # Visual pulse ring
var ring := Polygon2D.new() var ring := Polygon2D.new()
@@ -812,13 +816,24 @@ func _on_homing_hit(body: Node, proj: Node2D) -> void:
func _tick_homing_projs(delta: float) -> void: func _tick_homing_projs(delta: float) -> void:
if _player == null or not is_instance_valid(_player): if _player == null or not is_instance_valid(_player):
# Player died or invalid — clear all homing projectiles
for p in _homing_projs:
var raw_node = p.get("node")
if raw_node != null and is_instance_valid(raw_node):
raw_node.queue_free()
_homing_projs.clear()
return return
var still_valid: Array[Dictionary] = [] var still_valid: Array[Dictionary] = []
for p in _homing_projs: for p in _homing_projs:
var node: Node2D = p["node"] # Check validity on raw value before typed assignment to avoid error
if not is_instance_valid(node): var raw_node = p.get("node")
if raw_node == null or not is_instance_valid(raw_node):
continue
var node := raw_node as Node2D
if node == null:
continue continue
# Age check # Age check
+10 -15
View File
@@ -24,6 +24,7 @@ const DOOR_W := 80.0
const DOOR_H := 80.0 const DOOR_H := 80.0
const WALL_COLOR := Color(0.18, 0.18, 0.25) const WALL_COLOR := Color(0.18, 0.18, 0.25)
const STATION_DOOR_SCRIPT = preload("res://scripts/rooms/station_door.gd")
const ROOM_NAMES := [ const ROOM_NAMES := [
"STAGING AREA", # 0 "STAGING AREA", # 0
@@ -272,33 +273,27 @@ func _build_armory_workbench() -> void:
# Staging Area run portal # Staging Area run portal
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
func _build_run_portal() -> void: func _build_run_portal() -> void:
# Visual — bright green rectangle # Station door with proper frame graphics
var poly := Polygon2D.new() var portal_door := Node2D.new()
poly.polygon = PackedVector2Array([-40, -30, 40, -30, 40, 30, -40, 30]) portal_door.set_script(STATION_DOOR_SCRIPT)
poly.color = Color(0.2, 0.9, 0.4) portal_door.position = Vector2(0, -150)
poly.position = Vector2(0, -150) contents.add_child(portal_door)
contents.add_child(poly) portal_door.setup_hub_portal()
# Label above portal
var lbl := Label.new()
lbl.text = "START RUN"
lbl.position = Vector2(-36, -195)
contents.add_child(lbl)
_portal_prompt = Label.new() _portal_prompt = Label.new()
_portal_prompt.text = "[E] Start Run" _portal_prompt.text = "[E] Start Run"
_portal_prompt.position = Vector2(-50, -210) _portal_prompt.position = Vector2(-50, -85) # below the door label
_portal_prompt.visible = false _portal_prompt.visible = false
contents.add_child(_portal_prompt) contents.add_child(_portal_prompt)
# Trigger zone # Trigger zone for interaction
var area := Area2D.new() var area := Area2D.new()
area.collision_layer = 0 area.collision_layer = 0
area.collision_mask = 2 area.collision_mask = 2
var cs := CollisionShape2D.new() var cs := CollisionShape2D.new()
var rs := RectangleShape2D.new() var rs := RectangleShape2D.new()
rs.size = Vector2(80, 60) rs.size = Vector2(80, 100)
cs.shape = rs cs.shape = rs
area.add_child(cs) area.add_child(cs)
area.position = Vector2(0, -150) area.position = Vector2(0, -150)
+15 -2
View File
@@ -31,6 +31,7 @@ var _slick_count: int = 0
# Knockback — applied by enemies (e.g., Hivemind pulse) # Knockback — applied by enemies (e.g., Hivemind pulse)
var _knockback_velocity: Vector2 = Vector2.ZERO var _knockback_velocity: Vector2 = Vector2.ZERO
var _knockback_flash_timer: float = 0.0
func _ready() -> void: func _ready() -> void:
add_to_group("player") add_to_group("player")
@@ -62,8 +63,9 @@ func _handle_movement() -> void:
input_dir = input_dir.normalized() input_dir = input_dir.normalized()
if _slick_count > 0: if _slick_count > 0:
# Oil slick — momentum-based: velocity slowly drifts toward desired direction # Oil slick — 40% slower + momentum-based movement
velocity = velocity.lerp(input_dir * stats.speed, 0.08) var slick_speed := stats.speed * 0.6
velocity = velocity.lerp(input_dir * slick_speed, 0.12)
else: else:
velocity = input_dir * stats.speed velocity = input_dir * stats.speed
@@ -168,6 +170,12 @@ func _handle_iframes(delta: float) -> void:
body.modulate = Color(1, 1, 1, 1) body.modulate = Color(1, 1, 1, 1)
return return
# Knockback flash — magenta pulse when pushed by enemy ability
if _knockback_flash_timer > 0.0:
_knockback_flash_timer -= delta
body.modulate = Color(1.0, 0.4, 0.7, 1.0)
return
# Shield active — cyan tint # Shield active — cyan tint
if _shield_active: if _shield_active:
body.modulate = Color(0.4, 0.9, 1.0) body.modulate = Color(0.4, 0.9, 1.0)
@@ -216,6 +224,10 @@ func take_damage(amount: float) -> void:
func _die() -> void: func _die() -> void:
if RunManager.run_active: if RunManager.run_active:
# Disable player so they can't move during death screen
set_physics_process(false)
set_process_input(false)
visible = false
RunManager.end_run(false) RunManager.end_run(false)
else: else:
# In hub/practice — signal for practice room handling, then heal # In hub/practice — signal for practice room handling, then heal
@@ -277,6 +289,7 @@ func exit_slick() -> void:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
func apply_knockback(force: Vector2) -> void: func apply_knockback(force: Vector2) -> void:
_knockback_velocity = force _knockback_velocity = force
_knockback_flash_timer = 0.25 # brief magenta flash to show player was pushed
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Room event handlers # Room event handlers
+24 -13
View File
@@ -324,6 +324,9 @@ func _spawn_boss() -> void:
_enemy_death_connections.append({"node": boss, "signal": "tree_exited", "callable": callable}) _enemy_death_connections.append({"node": boss, "signal": "tree_exited", "callable": callable})
_enemy_count = 1 _enemy_count = 1
# Spawn exit door on top wall (visible but closed until boss defeated)
_spawn_floor_exit_door()
func _on_enemy_died() -> void: func _on_enemy_died() -> void:
# Guard against deferred callback running after room is freed # Guard against deferred callback running after room is freed
@@ -335,30 +338,34 @@ func _on_enemy_died() -> void:
AudioManager.play("room_clear") AudioManager.play("room_clear")
if data.type == RoomData.RoomType.BOSS: if data.type == RoomData.RoomType.BOSS:
_spawn_body_part_drop() _spawn_body_part_drop()
_spawn_floor_exit() _open_floor_exit()
room_cleared.emit() room_cleared.emit()
EventBus.room_cleared.emit(data.id) EventBus.room_cleared.emit(data.id)
_update_door_locks() _update_door_locks()
func _spawn_floor_exit() -> void: func _spawn_floor_exit_door() -> void:
# Create the exit door on the top wall (closed until boss defeated)
var is_final_floor := RunManager.current_floor >= 3 var is_final_floor := RunManager.current_floor >= 3
# Station door for floor transition - positioned to the right of body part drop
_floor_exit_door = StationDoor.new() _floor_exit_door = StationDoor.new()
_floor_exit_door.position = Vector2(150, 0) # Position on top wall, centered
_floor_exit_door.position = Vector2(0, -ROOM_H / 2.0 + 80)
if is_final_floor: if is_final_floor:
_floor_exit_door.setup_evacuation_exit() _floor_exit_door.setup_evacuation_exit()
else: else:
# Next sector: floor 1 -> SECTOR 2, floor 2 -> SECTOR 3
_floor_exit_door.setup_sector_exit(RunManager.current_floor + 1) _floor_exit_door.setup_sector_exit(RunManager.current_floor + 1)
_floor_exit_door.door_entered.connect(_on_floor_exit_entered) _floor_exit_door.door_entered.connect(_on_floor_exit_entered)
contents.add_child(_floor_exit_door) contents.add_child(_floor_exit_door)
# Door starts closed (setup functions already call close())
func _open_floor_exit() -> void:
# Open the door now that boss is dead # Open the door now that boss is dead
_floor_exit_door.open() if _floor_exit_door != null:
_floor_exit_door.open()
func _on_floor_exit_entered(body: Node) -> void: func _on_floor_exit_entered(body: Node) -> void:
@@ -426,20 +433,24 @@ func _make_oil_slick(pos: Vector2) -> void:
slick.collision_mask = 2 | 8 # player (2) + enemies (8) slick.collision_mask = 2 | 8 # player (2) + enemies (8)
slick.position = pos slick.position = pos
var radius: float = randf_range(44.0, 68.0) # Oval dimensions — wider than tall
var radius_x: float = randf_range(55.0, 85.0)
var radius_y: float = randf_range(35.0, 50.0)
# Use rectangle shape for collision (approximates oval)
var cs := CollisionShape2D.new() var cs := CollisionShape2D.new()
var sh := CircleShape2D.new() var sh := RectangleShape2D.new()
sh.radius = radius sh.size = Vector2(radius_x * 1.6, radius_y * 1.6) # slightly smaller than visual
cs.shape = sh cs.shape = sh
slick.add_child(cs) slick.add_child(cs)
# Visual — circle matching collision shape # Visual — oval shape
var poly := Polygon2D.new() var poly := Polygon2D.new()
var pts := PackedVector2Array() var pts := PackedVector2Array()
var segs := 14 var segs := 16
for i in segs: for i in segs:
var a := i * TAU / segs var a := i * TAU / segs
pts.append(Vector2(cos(a) * radius, sin(a) * radius)) pts.append(Vector2(cos(a) * radius_x, sin(a) * radius_y))
poly.polygon = pts poly.polygon = pts
poly.color = Color(0.04, 0.07, 0.14, 0.78) poly.color = Color(0.04, 0.07, 0.14, 0.78)
slick.add_child(poly) slick.add_child(poly)
+12 -1
View File
@@ -26,7 +26,7 @@ const EXIT_LIGHT := Color(0.2, 0.95, 0.4) # bright green exit
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Configuration # Configuration
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
enum DoorType { ENTRY, SECTOR_EXIT, EVACUATION_EXIT } enum DoorType { ENTRY, SECTOR_EXIT, EVACUATION_EXIT, HUB_PORTAL }
var door_type: DoorType = DoorType.ENTRY var door_type: DoorType = DoorType.ENTRY
var sector_label: String = "" # "SECTOR 2", "SECTOR 3", or "EXIT" var sector_label: String = "" # "SECTOR 2", "SECTOR 3", or "EXIT"
@@ -78,6 +78,17 @@ func setup_evacuation_exit() -> void:
_build_trigger() _build_trigger()
close() # starts closed until boss dies close() # starts closed until boss dies
func setup_hub_portal() -> void:
# Hub staging room portal - decorative open door with "START RUN" label
door_type = DoorType.HUB_PORTAL
sector_label = "START RUN"
is_functional = false # interaction handled by hub_room.gd
_build_frame()
_build_panels()
_build_indicator()
_build_label()
open() # always open in hub
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Build components # Build components
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------