Tileset, Audio Changes, and Portal changes
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
@@ -64,6 +64,9 @@ func apply_slow(duration: float, factor: float) -> void:
|
|||||||
# Stack slows by adding new effect - factors multiply together
|
# Stack slows by adding new effect - factors multiply together
|
||||||
_slow_effects.append({"factor": factor, "timer": duration})
|
_slow_effects.append({"factor": factor, "timer": duration})
|
||||||
|
|
||||||
|
func clear_slows() -> void:
|
||||||
|
_slow_effects.clear()
|
||||||
|
|
||||||
func _tick_slow(delta: float) -> void:
|
func _tick_slow(delta: float) -> void:
|
||||||
# Tick down all slow effects and remove expired ones
|
# Tick down all slow effects and remove expired ones
|
||||||
var i := _slow_effects.size() - 1
|
var i := _slow_effects.size() - 1
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ func _fire(direction: Vector2) -> void:
|
|||||||
|
|
||||||
func _spawn_projectile(dir: Vector2, zero_damage: bool = false) -> void:
|
func _spawn_projectile(dir: Vector2, zero_damage: bool = false) -> void:
|
||||||
var proj: Area2D = PROJECTILE.instantiate()
|
var proj: Area2D = PROJECTILE.instantiate()
|
||||||
proj.global_position = shoot_origin.global_position
|
proj.global_position = global_position # Spawn at player center to avoid wall clipping
|
||||||
proj.direction = dir
|
proj.direction = dir
|
||||||
proj.speed = stats.proj_speed
|
proj.speed = stats.proj_speed
|
||||||
proj.max_range = stats.range_
|
proj.max_range = stats.range_
|
||||||
|
|||||||
+38
-33
@@ -25,6 +25,7 @@ const HIVEMIND_SCENE = preload("res://scenes/enemies/hivemind.tscn")
|
|||||||
const BODY_PART_PICKUP = preload("res://scenes/upgrades/body_part_pickup.tscn")
|
const BODY_PART_PICKUP = preload("res://scenes/upgrades/body_part_pickup.tscn")
|
||||||
const RUN_ITEM_PICKUP = preload("res://scenes/items/run_item_pickup.tscn")
|
const RUN_ITEM_PICKUP = preload("res://scenes/items/run_item_pickup.tscn")
|
||||||
const SHOP_PEDESTAL_SCR = preload("res://scripts/items/shop_item_pedestal.gd")
|
const SHOP_PEDESTAL_SCR = preload("res://scripts/items/shop_item_pedestal.gd")
|
||||||
|
const STATION_DOOR_SCR = preload("res://scripts/rooms/station_door.gd")
|
||||||
|
|
||||||
const ALL_PART_PATHS: Array[String] = [
|
const ALL_PART_PATHS: Array[String] = [
|
||||||
"res://data/body_parts/head_wide_angle_lens.tres",
|
"res://data/body_parts/head_wide_angle_lens.tres",
|
||||||
@@ -56,6 +57,7 @@ var _floor: Node = null # reference to floor.gd node
|
|||||||
var _enemy_count: int = 0
|
var _enemy_count: int = 0
|
||||||
var _doors: Dictionary = {} # direction -> Area2D trigger
|
var _doors: Dictionary = {} # direction -> Area2D trigger
|
||||||
var _physical_doors: Dictionary = {} # direction -> RoomDoor
|
var _physical_doors: Dictionary = {} # direction -> RoomDoor
|
||||||
|
var _floor_exit_door: StationDoor = null # boss room exit door
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Signal tracking for explicit cleanup (safer than relying on auto-cleanup)
|
# Signal tracking for explicit cleanup (safer than relying on auto-cleanup)
|
||||||
@@ -104,6 +106,9 @@ func activate() -> void:
|
|||||||
RoomData.RoomType.SHOP:
|
RoomData.RoomType.SHOP:
|
||||||
_spawn_shop()
|
_spawn_shop()
|
||||||
data.cleared = true
|
data.cleared = true
|
||||||
|
RoomData.RoomType.START:
|
||||||
|
_spawn_entry_door()
|
||||||
|
data.cleared = true
|
||||||
_:
|
_:
|
||||||
data.cleared = true
|
data.cleared = true
|
||||||
|
|
||||||
@@ -339,39 +344,39 @@ func _on_enemy_died() -> void:
|
|||||||
func _spawn_floor_exit() -> void:
|
func _spawn_floor_exit() -> void:
|
||||||
var is_final_floor := RunManager.current_floor >= 3
|
var is_final_floor := RunManager.current_floor >= 3
|
||||||
|
|
||||||
# Visual — bright teal portal offset from body part drop (which is at center)
|
# Station door for floor transition - positioned to the right of body part drop
|
||||||
var poly := Polygon2D.new()
|
_floor_exit_door = StationDoor.new()
|
||||||
poly.polygon = PackedVector2Array([-35, -50, 35, -50, 35, 50, -35, 50])
|
_floor_exit_door.position = Vector2(150, 0)
|
||||||
poly.color = Color(0.2, 0.85, 0.9)
|
|
||||||
poly.position = Vector2(120, 0)
|
|
||||||
contents.add_child(poly)
|
|
||||||
|
|
||||||
var lbl := Label.new()
|
if is_final_floor:
|
||||||
lbl.text = "EXTRACT" if is_final_floor else "NEXT FLOOR"
|
_floor_exit_door.setup_evacuation_exit()
|
||||||
lbl.position = Vector2(85, -75)
|
else:
|
||||||
contents.add_child(lbl)
|
# Next sector: floor 1 -> SECTOR 2, floor 2 -> SECTOR 3
|
||||||
|
_floor_exit_door.setup_sector_exit(RunManager.current_floor + 1)
|
||||||
|
|
||||||
var area := Area2D.new()
|
_floor_exit_door.door_entered.connect(_on_floor_exit_entered)
|
||||||
area.collision_layer = 0
|
contents.add_child(_floor_exit_door)
|
||||||
area.collision_mask = 2
|
|
||||||
|
|
||||||
var cs := CollisionShape2D.new()
|
# Open the door now that boss is dead
|
||||||
var rs := RectangleShape2D.new()
|
_floor_exit_door.open()
|
||||||
rs.size = Vector2(70, 100)
|
|
||||||
cs.shape = rs
|
|
||||||
area.add_child(cs)
|
|
||||||
area.position = Vector2(120, 0)
|
|
||||||
|
|
||||||
area.body_entered.connect(func(body: Node) -> void:
|
|
||||||
if body.is_in_group("player"):
|
func _on_floor_exit_entered(body: Node) -> void:
|
||||||
RunManager.player_health_carry = body.stats.current_health
|
RunManager.player_health_carry = body.stats.current_health
|
||||||
if RunManager.current_floor >= 3:
|
if RunManager.current_floor >= 3:
|
||||||
RunManager.end_run(true)
|
RunManager.end_run(true)
|
||||||
else:
|
else:
|
||||||
RunManager.advance_floor()
|
RunManager.advance_floor()
|
||||||
get_tree().call_deferred("change_scene_to_file", "res://scenes/run/floor.tscn")
|
get_tree().call_deferred("change_scene_to_file", "res://scenes/run/floor.tscn")
|
||||||
)
|
|
||||||
contents.add_child(area)
|
|
||||||
|
func _spawn_entry_door() -> void:
|
||||||
|
# Decorative open door behind spawn point - shows "you entered from here"
|
||||||
|
# Positioned at bottom of room (player spawns in center, door behind them)
|
||||||
|
var entry_door := StationDoor.new()
|
||||||
|
entry_door.position = Vector2(0, ROOM_H / 2.0 - 80) # near bottom wall
|
||||||
|
entry_door.setup_entry_door()
|
||||||
|
contents.add_child(entry_door)
|
||||||
|
|
||||||
|
|
||||||
func _spawn_body_part_drop() -> void:
|
func _spawn_body_part_drop() -> void:
|
||||||
@@ -424,13 +429,13 @@ func _make_oil_slick(pos: Vector2) -> void:
|
|||||||
cs.shape = sh
|
cs.shape = sh
|
||||||
slick.add_child(cs)
|
slick.add_child(cs)
|
||||||
|
|
||||||
# Visual — flattened dark ellipse (puddle)
|
# Visual — circle matching collision shape
|
||||||
var poly := Polygon2D.new()
|
var poly := Polygon2D.new()
|
||||||
var pts := PackedVector2Array()
|
var pts := PackedVector2Array()
|
||||||
var segs := 14
|
var segs := 14
|
||||||
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 * 0.55))
|
pts.append(Vector2(cos(a) * radius, sin(a) * radius))
|
||||||
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)
|
||||||
@@ -443,8 +448,8 @@ func _make_oil_slick(pos: Vector2) -> void:
|
|||||||
slick.body_exited.connect(func(b: Node) -> void:
|
slick.body_exited.connect(func(b: Node) -> void:
|
||||||
if b.is_in_group("player") and b.has_method("exit_slick"):
|
if b.is_in_group("player") and b.has_method("exit_slick"):
|
||||||
b.exit_slick()
|
b.exit_slick()
|
||||||
elif b.is_in_group("enemies") and b.has_method("apply_slow"):
|
elif b.is_in_group("enemies") and b.has_method("clear_slows"):
|
||||||
b.apply_slow(0.0, 1.0)) # clear slow immediately
|
b.clear_slows())
|
||||||
|
|
||||||
contents.add_child(slick)
|
contents.add_child(slick)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,251 @@
|
|||||||
|
extends Node2D
|
||||||
|
class_name StationDoor
|
||||||
|
|
||||||
|
# Station door for floor transitions - bulkhead/airlock style visuals
|
||||||
|
# Can be decorative (entry doors) or functional (exit doors with triggers)
|
||||||
|
|
||||||
|
signal door_entered(body: Node)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Constants
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
const DOOR_WIDTH := 100.0
|
||||||
|
const DOOR_HEIGHT := 120.0
|
||||||
|
const FRAME_THICKNESS := 16.0
|
||||||
|
const PANEL_GAP := 4.0 # gap between door panels when open
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
const FRAME_COLOR := Color(0.25, 0.25, 0.32) # dark metal frame
|
||||||
|
const PANEL_COLOR := Color(0.35, 0.32, 0.28) # door panel (closed)
|
||||||
|
const PANEL_OPEN_COLOR := Color(0.2, 0.2, 0.25) # recessed panel (open)
|
||||||
|
const LIGHT_OFF := Color(0.3, 0.15, 0.15) # indicator off
|
||||||
|
const LIGHT_LOCKED := Color(0.9, 0.2, 0.2) # red locked
|
||||||
|
const LIGHT_UNLOCKED := Color(0.2, 0.9, 0.3) # green unlocked
|
||||||
|
const EXIT_LIGHT := Color(0.2, 0.95, 0.4) # bright green exit
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Configuration
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
enum DoorType { ENTRY, SECTOR_EXIT, EVACUATION_EXIT }
|
||||||
|
|
||||||
|
var door_type: DoorType = DoorType.ENTRY
|
||||||
|
var sector_label: String = "" # "SECTOR 2", "SECTOR 3", or "EXIT"
|
||||||
|
var is_functional: bool = false # if true, has trigger area
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# State
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
var _is_open: bool = false
|
||||||
|
var _left_panel: Polygon2D = null
|
||||||
|
var _right_panel: Polygon2D = null
|
||||||
|
var _indicator_light: Polygon2D = null
|
||||||
|
var _label: Label = null
|
||||||
|
var _trigger_area: Area2D = null
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Setup
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
func setup_entry_door() -> void:
|
||||||
|
# Decorative open door - shows "you came from here"
|
||||||
|
door_type = DoorType.ENTRY
|
||||||
|
is_functional = false
|
||||||
|
_build_frame()
|
||||||
|
_build_panels()
|
||||||
|
_build_indicator()
|
||||||
|
open() # entry doors are always open
|
||||||
|
|
||||||
|
func setup_sector_exit(sector_num: int) -> void:
|
||||||
|
# Sector transition door - "SECTOR 2" or "SECTOR 3"
|
||||||
|
door_type = DoorType.SECTOR_EXIT
|
||||||
|
sector_label = "SECTOR %d" % sector_num
|
||||||
|
is_functional = true
|
||||||
|
_build_frame()
|
||||||
|
_build_panels()
|
||||||
|
_build_indicator()
|
||||||
|
_build_label()
|
||||||
|
_build_trigger()
|
||||||
|
close() # starts closed until boss dies
|
||||||
|
|
||||||
|
func setup_evacuation_exit() -> void:
|
||||||
|
# Final exit after Hivemind - green EXIT lighting
|
||||||
|
door_type = DoorType.EVACUATION_EXIT
|
||||||
|
sector_label = "EXIT"
|
||||||
|
is_functional = true
|
||||||
|
_build_frame()
|
||||||
|
_build_panels()
|
||||||
|
_build_indicator()
|
||||||
|
_build_label()
|
||||||
|
_build_trigger()
|
||||||
|
close() # starts closed until boss dies
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Build components
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
func _build_frame() -> void:
|
||||||
|
# Outer frame - bulkhead style
|
||||||
|
var frame := Polygon2D.new()
|
||||||
|
var hw := DOOR_WIDTH / 2.0 + FRAME_THICKNESS
|
||||||
|
var hh := DOOR_HEIGHT / 2.0 + FRAME_THICKNESS
|
||||||
|
var inner_hw := DOOR_WIDTH / 2.0
|
||||||
|
var inner_hh := DOOR_HEIGHT / 2.0
|
||||||
|
|
||||||
|
# Outer rectangle with inner cutout (using polygon)
|
||||||
|
# Top frame
|
||||||
|
var top := Polygon2D.new()
|
||||||
|
top.polygon = PackedVector2Array([
|
||||||
|
Vector2(-hw, -hh), Vector2(hw, -hh),
|
||||||
|
Vector2(hw, -inner_hh), Vector2(-hw, -inner_hh)
|
||||||
|
])
|
||||||
|
top.color = FRAME_COLOR
|
||||||
|
add_child(top)
|
||||||
|
|
||||||
|
# Bottom frame
|
||||||
|
var bottom := Polygon2D.new()
|
||||||
|
bottom.polygon = PackedVector2Array([
|
||||||
|
Vector2(-hw, inner_hh), Vector2(hw, inner_hh),
|
||||||
|
Vector2(hw, hh), Vector2(-hw, hh)
|
||||||
|
])
|
||||||
|
bottom.color = FRAME_COLOR
|
||||||
|
add_child(bottom)
|
||||||
|
|
||||||
|
# Left frame
|
||||||
|
var left := Polygon2D.new()
|
||||||
|
left.polygon = PackedVector2Array([
|
||||||
|
Vector2(-hw, -inner_hh), Vector2(-inner_hw, -inner_hh),
|
||||||
|
Vector2(-inner_hw, inner_hh), Vector2(-hw, inner_hh)
|
||||||
|
])
|
||||||
|
left.color = FRAME_COLOR
|
||||||
|
add_child(left)
|
||||||
|
|
||||||
|
# Right frame
|
||||||
|
var right := Polygon2D.new()
|
||||||
|
right.polygon = PackedVector2Array([
|
||||||
|
Vector2(inner_hw, -inner_hh), Vector2(hw, -inner_hh),
|
||||||
|
Vector2(hw, inner_hh), Vector2(inner_hw, inner_hh)
|
||||||
|
])
|
||||||
|
right.color = FRAME_COLOR
|
||||||
|
add_child(right)
|
||||||
|
|
||||||
|
func _build_panels() -> void:
|
||||||
|
# Two sliding door panels
|
||||||
|
var panel_w := DOOR_WIDTH / 2.0 - PANEL_GAP
|
||||||
|
var panel_h := DOOR_HEIGHT
|
||||||
|
var hh := panel_h / 2.0
|
||||||
|
|
||||||
|
_left_panel = Polygon2D.new()
|
||||||
|
_left_panel.polygon = PackedVector2Array([
|
||||||
|
Vector2(-panel_w, -hh), Vector2(0, -hh),
|
||||||
|
Vector2(0, hh), Vector2(-panel_w, hh)
|
||||||
|
])
|
||||||
|
_left_panel.color = PANEL_COLOR
|
||||||
|
add_child(_left_panel)
|
||||||
|
|
||||||
|
_right_panel = Polygon2D.new()
|
||||||
|
_right_panel.polygon = PackedVector2Array([
|
||||||
|
Vector2(0, -hh), Vector2(panel_w, -hh),
|
||||||
|
Vector2(panel_w, hh), Vector2(0, hh)
|
||||||
|
])
|
||||||
|
_right_panel.color = PANEL_COLOR
|
||||||
|
add_child(_right_panel)
|
||||||
|
|
||||||
|
func _build_indicator() -> void:
|
||||||
|
# Small indicator light above the door
|
||||||
|
_indicator_light = Polygon2D.new()
|
||||||
|
var light_size := 8.0
|
||||||
|
_indicator_light.polygon = PackedVector2Array([
|
||||||
|
Vector2(-light_size, -light_size / 2.0),
|
||||||
|
Vector2(light_size, -light_size / 2.0),
|
||||||
|
Vector2(light_size, light_size / 2.0),
|
||||||
|
Vector2(-light_size, light_size / 2.0)
|
||||||
|
])
|
||||||
|
_indicator_light.position = Vector2(0, -DOOR_HEIGHT / 2.0 - FRAME_THICKNESS - 6)
|
||||||
|
_indicator_light.color = LIGHT_OFF
|
||||||
|
add_child(_indicator_light)
|
||||||
|
|
||||||
|
func _build_label() -> void:
|
||||||
|
_label = Label.new()
|
||||||
|
_label.text = sector_label
|
||||||
|
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
_label.position = Vector2(-40, -DOOR_HEIGHT / 2.0 - FRAME_THICKNESS - 30)
|
||||||
|
|
||||||
|
# Style the label
|
||||||
|
if door_type == DoorType.EVACUATION_EXIT:
|
||||||
|
_label.add_theme_color_override("font_color", EXIT_LIGHT)
|
||||||
|
else:
|
||||||
|
_label.add_theme_color_override("font_color", Color(0.8, 0.8, 0.9))
|
||||||
|
|
||||||
|
add_child(_label)
|
||||||
|
|
||||||
|
func _build_trigger() -> void:
|
||||||
|
_trigger_area = Area2D.new()
|
||||||
|
_trigger_area.collision_layer = 0
|
||||||
|
_trigger_area.collision_mask = 2 # player layer
|
||||||
|
_trigger_area.monitoring = false # disabled until door opens
|
||||||
|
|
||||||
|
var cs := CollisionShape2D.new()
|
||||||
|
var rs := RectangleShape2D.new()
|
||||||
|
rs.size = Vector2(DOOR_WIDTH - 20, DOOR_HEIGHT - 20)
|
||||||
|
cs.shape = rs
|
||||||
|
_trigger_area.add_child(cs)
|
||||||
|
|
||||||
|
_trigger_area.body_entered.connect(_on_body_entered)
|
||||||
|
add_child(_trigger_area)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Open / Close
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
func open() -> void:
|
||||||
|
if _is_open:
|
||||||
|
return
|
||||||
|
_is_open = true
|
||||||
|
|
||||||
|
# Slide panels outward
|
||||||
|
var slide_dist := DOOR_WIDTH / 2.0 + PANEL_GAP
|
||||||
|
if _left_panel:
|
||||||
|
_left_panel.position.x = -slide_dist
|
||||||
|
_left_panel.color = PANEL_OPEN_COLOR
|
||||||
|
if _right_panel:
|
||||||
|
_right_panel.position.x = slide_dist
|
||||||
|
_right_panel.color = PANEL_OPEN_COLOR
|
||||||
|
|
||||||
|
# Update indicator
|
||||||
|
if _indicator_light:
|
||||||
|
if door_type == DoorType.EVACUATION_EXIT:
|
||||||
|
_indicator_light.color = EXIT_LIGHT
|
||||||
|
else:
|
||||||
|
_indicator_light.color = LIGHT_UNLOCKED
|
||||||
|
|
||||||
|
# Enable trigger
|
||||||
|
if _trigger_area:
|
||||||
|
_trigger_area.monitoring = true
|
||||||
|
|
||||||
|
func close() -> void:
|
||||||
|
if not _is_open and _left_panel != null:
|
||||||
|
return
|
||||||
|
_is_open = false
|
||||||
|
|
||||||
|
# Center panels
|
||||||
|
if _left_panel:
|
||||||
|
_left_panel.position.x = 0
|
||||||
|
_left_panel.color = PANEL_COLOR
|
||||||
|
if _right_panel:
|
||||||
|
_right_panel.position.x = 0
|
||||||
|
_right_panel.color = PANEL_COLOR
|
||||||
|
|
||||||
|
# Update indicator
|
||||||
|
if _indicator_light:
|
||||||
|
_indicator_light.color = LIGHT_LOCKED
|
||||||
|
|
||||||
|
# Disable trigger
|
||||||
|
if _trigger_area:
|
||||||
|
_trigger_area.monitoring = false
|
||||||
|
|
||||||
|
func is_open() -> bool:
|
||||||
|
return _is_open
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Trigger callback
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
func _on_body_entered(body: Node) -> void:
|
||||||
|
if body.is_in_group("player"):
|
||||||
|
door_entered.emit(body)
|
||||||
Reference in New Issue
Block a user