added blocking doors to paths.

This commit is contained in:
2026-01-10 19:46:55 +01:00
parent 24ea2f3c60
commit 25be2c00bd
33 changed files with 4383 additions and 455 deletions

View File

@@ -11,11 +11,36 @@ var is_closing:bool = false
var is_opening:bool = false
var time_to_move:float = 0.5
var move_timer:float = 0.0
var animation_start_position: Vector2 = Vector2.ZERO # Position when animation started
var initial_position:Vector2 = Vector2.ZERO
var closed_position: Vector2 = Vector2.ZERO # Position when door is closed (local)
var open_offset: Vector2 = Vector2.ZERO # Offset from closed to open position (local)
# Room and puzzle state
var blocking_room: Dictionary = {} # Room this door blocks (the room you enter INTO)
var room1: Dictionary = {} # First room connected by this door (room you leave FROM)
var room2: Dictionary = {} # Second room connected by this door (room you enter INTO - the blocking_room)
var switch_room: Dictionary = {} # Room where the switch is located (before the door)
var room_trigger_area: Area2D = null # Reference to room trigger area for the blocking room
var puzzle_solved: bool = false # True when room puzzle is solved
var enemies_defeated: bool = false # True when all enemies in room are defeated
var switches_activated: bool = false # True when required switches are activated
# Key door state
var key_used: bool = false # True when key has been used
var key_indicator: Sprite2D = null # Visual indicator showing key above door
# Floor switches this door is connected to
var connected_switches: Array = [] # Array of floor switch nodes
var requires_enemies: bool = false # True if door requires defeating enemies to open
var requires_switch: bool = false # True if door requires activating switches to open
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
# Set texture based on door type
_update_door_texture()
# Rotate door first based on direction (original order)
if direction == "Left":
self.rotate(-PI/2)
elif direction == "Right":
@@ -23,66 +48,762 @@ func _ready() -> void:
elif direction == "Down":
self.rotate(PI)
initial_position = global_position
var amount = 16
set_collision_layer_value(7, false)
if is_closed:
set_collision_layer_value(7, true)
amount = 0
# Calculate open offset based on direction (in WORLD space)
# NEW RULES:
# - Open state: door is at specific tile (UP:tile2, RIGHT:tile4, DOWN:tile2, LEFT:tile3)
# - Closed state: door moves 16 pixels offset from open position
# - UP: closed = open + (0, 16) = 16px down (from tile 2 to tile 5)
# - RIGHT: closed = open + (-16, 0) = 16px left (from tile 4 to tile 3)
# - DOWN: closed = open + (0, 16) = 16px down (from tile 2 to tile 5)
# - LEFT: closed = open + (16, 0) = 16px right (from tile 3 to tile 4)
var open_amount = 16.0
open_offset = Vector2.ZERO
$TeleporterIntoClosedRoom.is_enabled = false # disable initially (only enable on closed!)
if direction == "Up":
position.y = initial_position.y - amount
# Door on top wall: closed state is 16px DOWN from open state
# So open_offset is positive Y (door moves down when closing, so open is up)
# Actually wait - if closed is 16px down from open, then open is 16px up from closed
# So open_offset should be negative Y (open position is above closed position)
open_offset = Vector2(0, -open_amount) # Open is 16px UP from closed
elif direction == "Down":
position.y = initial_position.y + amount
# Door on bottom wall:
# For StoneDoor/GateDoor: open is at (col 1, row 1), closed is at (col 1, row 0)
# So closed is 16px UP from open, open_offset = (0, -16) means open is 16px DOWN from closed
# For KeyDoor: closed is at (col 1, row 0), open is at (col 1, row 1)
# So open is 16px DOWN from closed, open_offset = (0, 16)
# NOTE: This is recalculated in _ready_after_setup() based on door type
open_offset = Vector2(0, -open_amount) # For StoneDoor/GateDoor: open is 16px DOWN from closed
elif direction == "Left":
position.x = initial_position.x - amount
# Door on left wall: closed state is 16px RIGHT from open state
# So open_offset is positive X (door moves right when closing, so open is left)
# Actually wait - if closed is 16px right from open, then open is 16px left from closed
# So open_offset should be negative X (open position is left of closed position)
open_offset = Vector2(-open_amount, 0) # Open is 16px LEFT from closed
elif direction == "Right":
position.x = initial_position.x + amount
# Door on right wall: closed state is 16px LEFT from open state
# So open_offset is negative X (door moves left when closing, so open is right)
# Actually wait - if closed is 16px left from open, then open is 16px right from closed
# So open_offset should be positive X (open position is right of closed position)
open_offset = Vector2(open_amount, 0) # Open is 16px RIGHT from closed
# Note: closed_position will be set in _ready_after_setup after door is positioned
# For now, just initialize it
closed_position = position
pass # Replace with function body.
# Connect KeyInteractionArea signal
var key_area = get_node_or_null("KeyInteractionArea")
if key_area:
key_area.body_entered.connect(_on_key_interaction_area_body_entered)
# Call setup after a frame to ensure everything is ready
call_deferred("_ready_after_setup")
func _update_door_texture():
# Update door texture based on door type
var sprite = get_node_or_null("Sprite2D")
if not sprite:
return
match type:
"KeyDoor":
var locked_texture = load("res://assets/gfx/door_locked.png")
if locked_texture:
sprite.texture = locked_texture
print("Door: Set KeyDoor texture to door_locked.png")
else:
push_error("Door: Could not load door_locked.png texture!")
"GateDoor":
var gate_texture = load("res://assets/gfx/door_gate.png")
if gate_texture:
sprite.texture = gate_texture
print("Door: Set GateDoor texture to door_gate.png")
else:
push_error("Door: Could not load door_gate.png texture!")
"StoneDoor":
# Use door_barred.png for stone doors
var barred_texture = load("res://assets/gfx/door_barred.png")
if barred_texture:
sprite.texture = barred_texture
print("Door: Set StoneDoor texture to door_barred.png")
else:
push_error("Door: Could not load door_barred.png texture!")
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
# TODO write code to open/close door here
# when door is open, ofcourse
# Handle door opening/closing animation
if is_opening or is_closing:
move_timer+=delta
#move 16 pixels in direction under 0.5 seconds
var amount = clamp(16*(move_timer/time_to_move),0,16)
if is_closing:
amount = 16-amount
if direction == "Up":
position.y = initial_position.y - amount
elif direction == "Down":
position.y = initial_position.y + amount
elif direction == "Left":
position.x = initial_position.x - amount
elif direction == "Right":
position.x = initial_position.x + amount
if move_timer >= time_to_move:
if is_opening:
is_closed = false
set_collision_layer_value(7, false)
else:
is_closed = true
set_collision_layer_value(7, true)
# Safety check: ensure closed_position is valid before animating
if closed_position == Vector2.ZERO:
print("Door: ERROR - closed_position is zero during animation! Resetting...")
closed_position = position - open_offset if is_opening else position
is_opening = false
is_closing = false
move_timer = 0
pass
move_timer = 0.0
# Only update collision for StoneDoor and GateDoor (KeyDoors handle their own state)
if type == "StoneDoor" or type == "GateDoor":
_update_collision_based_on_position()
return
move_timer += delta
var progress = clamp(move_timer / time_to_move, 0.0, 1.0)
if is_opening:
# Interpolate from closed to open position
# Start at closed_position (or animation_start_position if set), end at closed_position + open_offset (moving AWAY from closed position)
var start_pos = animation_start_position if animation_start_position != Vector2.ZERO else closed_position
var target_pos = closed_position + open_offset
position = start_pos.lerp(target_pos, progress)
global_position = position # Also update global position during animation
# Debug: log for KeyDoors to verify movement
if type == "KeyDoor" and move_timer < 0.1: # Only log once at start of animation
print("Door: KeyDoor opening animation - start: ", start_pos, ", target: ", target_pos, ", offset: ", open_offset, ", direction: ", direction)
# For KeyDoors: disable collision as soon as opening starts (allow passage immediately)
# For StoneDoor/GateDoor: update collision based on position
if type == "KeyDoor":
# KeyDoors: disable collision immediately when opening starts
if get_collision_layer_value(7):
set_collision_layer_value(7, false)
elif type == "StoneDoor" or type == "GateDoor":
# Update collision based on distance to closed position (disable when moving away)
var dist_to_closed = position.distance_to(closed_position)
if dist_to_closed > 5.0:
# Moving away from closed position - disable collision
if get_collision_layer_value(7):
set_collision_layer_value(7, false)
else:
# Still near closed position - keep collision enabled
if not get_collision_layer_value(7):
set_collision_layer_value(7, true)
elif is_closing:
# Interpolate from open to closed position
# NOTE: KeyDoors should NEVER close (only open with key)
# CRITICAL: Use stored starting position (set when animation started in _close())
# If animation_start_position wasn't set, calculate open position from closed_position + open_offset
var start_pos = animation_start_position if animation_start_position != Vector2.ZERO else (closed_position + open_offset)
position = start_pos.lerp(closed_position, progress)
global_position = position # Also update global position during animation
# Update collision for StoneDoor/GateDoor only
if type == "StoneDoor" or type == "GateDoor":
# Update collision based on distance to closed position (enable when approaching closed)
var dist_to_closed = position.distance_to(closed_position)
if dist_to_closed <= 5.0:
# At or near closed position - enable collision
if not get_collision_layer_value(7):
set_collision_layer_value(7, true)
else:
# Still away from closed position - keep collision disabled
if get_collision_layer_value(7):
set_collision_layer_value(7, false)
if move_timer >= time_to_move:
# Animation complete
if is_opening:
is_closed = false
# Move door to open position (away from closed position)
var open_position = closed_position + open_offset
position = open_position
global_position = open_position # Also set global position
# When moved from closed position (open), collision should be DISABLED
set_collision_layer_value(7, false)
print("Door: Opening animation complete - moved to open position: ", open_position, " (closed: ", closed_position, ", offset: ", open_offset, ") - collision DISABLED")
# Animation finished, reset flags
is_opening = false
is_closing = false
move_timer = 0.0
animation_start_position = Vector2.ZERO # Reset animation start position
else:
# Closing animation complete
is_closed = true
position = closed_position
global_position = closed_position # Also set global position
# When at closed position, collision should be ENABLED
set_collision_layer_value(7, true)
print("Door: Closing animation complete - moved to closed position: ", closed_position, " - collision ENABLED")
# Animation finished, reset flags
is_opening = false
is_closing = false
move_timer = 0.0
animation_start_position = Vector2.ZERO # Reset animation start position
# Now that door has finished closing, check if puzzle is solved (to open it immediately if already solved)
if type == "StoneDoor" or type == "GateDoor":
_check_puzzle_state()
# Update collision based on actual position (safety check in case position was changed externally)
# CRITICAL: KeyDoors should NEVER have their position/state changed automatically!
# Only update if not currently animating (to avoid interfering with animation)
# Only update for StoneDoor and GateDoor (NOT KeyDoors)
# IMPORTANT: Only update collision, don't change position - that could interfere with initial setup
if not is_opening and not is_closing and (type == "StoneDoor" or type == "GateDoor"):
# Only update collision based on position, don't change position or is_closed flag
# Position and is_closed should only be changed by explicit _open()/_close() calls or animation
var distance_to_closed = position.distance_to(closed_position) if closed_position != Vector2.ZERO else 999.0
if distance_to_closed <= 1.0:
# At closed position - collision should be ENABLED
if not get_collision_layer_value(7):
set_collision_layer_value(7, true)
else:
# Away from closed position (open) - collision should be DISABLED
if get_collision_layer_value(7):
set_collision_layer_value(7, false)
# For StoneDoor and GateDoor, periodically check puzzle state (only if door is closed and puzzle not solved)
# CRITICAL: Only check puzzle state if door has puzzle elements (switches or enemies)
# If door has no puzzle elements, it should never open
# Check every 10 frames (0.16 seconds at 60fps) to reduce performance impact
var check_puzzle_timer = Engine.get_process_frames() % 10
if check_puzzle_timer == 0 and (type == "StoneDoor" or type == "GateDoor") and is_closed and not puzzle_solved:
# Check if door requires enemies or switches
if requires_enemies or requires_switch:
_check_puzzle_state()
# For KeyDoors, ensure they stay at closed position if not opened
# KeyDoors should NEVER move unless explicitly opened with a key
if type == "KeyDoor" and not is_opening and not is_closing and not key_used:
# Ensure KeyDoor is at closed position and has collision enabled
if closed_position != Vector2.ZERO:
# Snap to closed position if somehow moved (shouldn't happen, but safety check)
var distance_to_closed = position.distance_to(closed_position)
if distance_to_closed > 1.0:
print("Door: KeyDoor was moved incorrectly! Resetting to closed position.")
position = closed_position
is_closed = true
set_collision_layer_value(7, true)
func _update_collision_based_on_position():
# Update collision based on whether door is at closed position or moved away
# CRITICAL: This function should NEVER be called for KeyDoors!
# Only for StoneDoor and GateDoor
# CRITICAL: This function ONLY updates collision, it does NOT change position or is_closed flag
# Position and is_closed should only be changed by explicit _open()/_close() calls or animation
if type == "KeyDoor":
return # Don't update KeyDoors - they handle their own state
# Only update collision, don't change position or is_closed flag
var distance_to_closed = position.distance_to(closed_position) if closed_position != Vector2.ZERO else 999.0
var distance_threshold = 1.0 # Consider "at closed position" if within 1 pixel
if distance_to_closed <= distance_threshold:
# Door is at closed position - collision should be ENABLED
if not get_collision_layer_value(7):
set_collision_layer_value(7, true)
else:
# Door is moved away from closed position (open) - collision should be DISABLED
if get_collision_layer_value(7):
set_collision_layer_value(7, false)
func _open():
$SfxOpenKeyDoor.play()
$TeleporterIntoClosedRoom.is_enabled = false
# CRITICAL: For KeyDoors, ensure they start from closed position before opening
# KeyDoors should ALWAYS start from closed position when opening (never from open position)
if type == "KeyDoor":
# KeyDoors should always be at closed position when opening starts
# If somehow moved, reset to closed position first
if closed_position != Vector2.ZERO:
# Reset to closed position to ensure animation starts from correct position
position = closed_position
global_position = closed_position
is_closed = true
set_collision_layer_value(7, true) # Collision enabled at closed position
print("Door: KeyDoor _open() called - reset to closed position ", closed_position, " before opening")
else:
push_error("Door: KeyDoor _open() called but closed_position is zero!")
return
$SfxOpenKeyDoor.play()
else:
# StoneDoor/GateDoor: Only open if door is currently closed
var distance_to_closed = position.distance_to(closed_position) if closed_position != Vector2.ZERO else 999.0
var is_actually_open = distance_to_closed > 5.0
if is_actually_open:
# Door is already open - don't do anything
print("Door: _open() called but door is already open! Position: ", position, ", closed: ", closed_position, ", distance: ", distance_to_closed)
# Ensure door is at open position and collision is disabled
var open_pos = closed_position + open_offset
position = open_pos
global_position = open_pos
is_closed = false
set_collision_layer_value(7, false)
return # Don't start animation
# Door is closed - ensure it's at closed position before opening
if closed_position != Vector2.ZERO:
position = closed_position
global_position = closed_position
is_closed = true
set_collision_layer_value(7, true)
print("Door: StoneDoor/GateDoor _open() called - ensuring door is at closed position ", closed_position, " before opening")
else:
push_error("Door: StoneDoor/GateDoor _open() called but closed_position is zero!")
return
$SfxOpenStoneDoor.play()
# CRITICAL: Store starting position for animation (should be closed_position)
animation_start_position = position
print("Door: Starting open animation from ", animation_start_position, " to ", closed_position + open_offset, " (offset: ", open_offset, ")")
is_opening = true
is_closing = false
move_timer = 0.0
pass
func _close():
$SfxOpenStoneDoor.play()
# CRITICAL: KeyDoors should NEVER be closed (they only open with a key and stay open)
if type == "KeyDoor":
print("Door: ERROR - _close() called on KeyDoor! KeyDoors should never be closed!")
return
# Ensure closed_position is valid before closing
if closed_position == Vector2.ZERO:
# If closed_position wasn't set correctly, use current position
closed_position = position
print("Door: WARNING - closed_position was zero, using current position: ", closed_position)
# Check both flag and actual position to determine door state
var distance_to_closed = position.distance_to(closed_position) if closed_position != Vector2.ZERO else 999.0
var is_actually_at_closed = distance_to_closed < 5.0 # Within 5 pixels of closed position
print("Door: _close() called - is_closed: ", is_closed, ", is_actually_at_closed: ", is_actually_at_closed, ", position: ", position, ", closed: ", closed_position, ", distance: ", distance_to_closed)
# If door is already at closed position (both visually and by flag), don't do anything
if is_closed and is_actually_at_closed and not is_opening and not is_closing:
print("Door: Already closed (both flag and position match), not closing again")
return # Already closed, don't do anything
# CRITICAL: If door is at closed position but flag says open, just fix the state - don't animate
if is_actually_at_closed and not is_closed:
# Door is visually at closed position but flag says open - fix state only
print("Door: Door is at closed position but flag says open! Fixing state only (no animation)")
position = closed_position # Ensure exact position
is_closed = true
set_collision_layer_value(7, true)
return # Don't start animation
# Door is actually open (position is away from closed position) - start closing animation
# CRITICAL: Store starting position BEFORE starting animation
# Calculate expected open position (closed_position + open_offset)
var expected_open_pos = closed_position + open_offset
var distance_to_open = position.distance_to(expected_open_pos)
# Use current position as start (it should already be at open position)
# If door is significantly away from expected open position, snap to open position first
if distance_to_open > 10.0:
# Door is very far from expected open position - reset to open position first
print("Door: WARNING - Door is far from expected open position! Resetting to open: ", expected_open_pos, " (was at: ", position, ", distance: ", distance_to_open, ")")
animation_start_position = expected_open_pos
position = expected_open_pos
global_position = expected_open_pos
is_closed = false
set_collision_layer_value(7, false)
else:
# Door is at or near open position - use current position as start
animation_start_position = position
print("Door: Starting close animation from ", animation_start_position, " to ", closed_position, " (offset: ", open_offset, ")")
$SfxDoorCloses.play()
is_opening = false
is_closing = true
move_timer = 0.0
$TeleporterIntoClosedRoom.is_enabled = true
func _ready_after_setup():
# Called after door is fully set up with room references and positioned
# NEW LOGIC: Door is positioned at OPEN tile position by game_world
# The position set by game_world is the OPEN position (initial state for blocking doors)
var open_position = position # Current position is the OPEN position (from tile coordinates)
print("Door: _ready_after_setup() called - type: ", type, ", direction: ", direction, ", is_closed: ", is_closed, ", open_position: ", open_position)
# CRITICAL: Calculate closed position based on direction
# For StoneDoor/GateDoor: They start OPEN, then CLOSE when entering room
# For KeyDoor: They start CLOSED, then OPEN when key is used
# - UP: closed = open + (0, 16) = 16px down (from tile 2 to tile 5)
# - RIGHT: closed = open + (-16, 0) = 16px left (from tile 4 to tile 3)
# - DOWN: For StoneDoor/GateDoor: closed = open + (0, -16) = 16px UP (from row 1 to row 0)
# For KeyDoor: open = closed + (0, 16) = 16px DOWN (from row 0 to row 1)
# - LEFT: closed = open + (16, 0) = 16px right (from tile 3 to tile 4)
var closed_offset = Vector2.ZERO
match direction:
"Up":
closed_offset = Vector2(0, 16) # Closed is 16px DOWN from open
"Down":
# CRITICAL: For StoneDoor/GateDoor, they start OPEN at (col 1, row 1) and close to (col 1, row 0)
# So closed is 16px UP from open (negative Y)
# For KeyDoor, they start CLOSED at (col 1, row 0) and open to (col 1, row 1)
# But KeyDoor logic is handled separately in _ready_after_setup()
if type == "KeyDoor":
# KeyDoor: closed is at row 0, open is at row 1 (16px down)
# But we calculate from open_position, so this won't be used for KeyDoor
closed_offset = Vector2(0, -16) # Won't be used - KeyDoor uses different logic
else:
# StoneDoor/GateDoor: open is at row 1, closed is at row 0 (16px up)
closed_offset = Vector2(0, -16) # Closed is 16px UP from open
"Left":
closed_offset = Vector2(16, 0) # Closed is 16px RIGHT from open
"Right":
closed_offset = Vector2(-16, 0) # Closed is 16px LEFT from open
closed_position = open_position + closed_offset
# Update open_offset for animation logic (offset from closed to open)
# This is used when opening from closed position
open_offset = -closed_offset # open_offset = (0, -16) means open is 16px up from closed
print("Door: Calculated positions - open: ", open_position, ", closed: ", closed_position, ", closed_offset: ", closed_offset, ", open_offset: ", open_offset)
# CRITICAL: KeyDoors should ALWAYS start closed, regardless of is_closed value
# KeyDoors should NEVER be moved until opened with a key
# For KeyDoors: game_world positions them at CLOSED position (row 0 for Down doors)
# When opened, they move to OPEN position (row 1 for Down doors) - 16px DOWN
if type == "KeyDoor":
# For KeyDoors, the position from game_world is the CLOSED position
# Calculate open position from closed position
var keydoor_closed_position = position # Current position is CLOSED (from game_world)
# Calculate open position based on direction
var keydoor_open_offset = Vector2.ZERO
match direction:
"Up":
keydoor_open_offset = Vector2(0, -16) # Open is 16px UP from closed
"Down":
keydoor_open_offset = Vector2(0, 16) # Open is 16px DOWN from closed (row 0 to row 1)
"Left":
keydoor_open_offset = Vector2(-16, 0) # Open is 16px LEFT from closed
"Right":
keydoor_open_offset = Vector2(16, 0) # Open is 16px RIGHT from closed
# Set positions correctly for KeyDoor
closed_position = keydoor_closed_position # Closed is where game_world placed it
open_offset = keydoor_open_offset # Offset to move from closed to open
# KeyDoor starts CLOSED
is_closed = true
position = closed_position
global_position = closed_position
set_collision_layer_value(7, true) # Collision enabled when closed
print("Door: KeyDoor starting CLOSED at position ", position, " (direction: ", direction, "), will open to ", closed_position + open_offset, " - collision ENABLED")
# Create key indicator sprite for KeyDoor
_create_key_indicator()
return # Exit early for KeyDoors
elif is_closed:
# StoneDoor/GateDoor starting closed (shouldn't happen for blocking doors, but handle it)
position = closed_position
global_position = closed_position
is_closed = true # Ensure state matches position
set_collision_layer_value(7, true)
print("Door: Starting CLOSED at position ", position, " (type: ", type, ", direction: ", direction, ") - collision ENABLED")
else:
# StoneDoor/GateDoor starting OPEN (default for blocking doors)
# CRITICAL: Door MUST start at open position (which is where game_world placed it)
# Ensure position is EXACTLY at open_position (don't assume game_world set it correctly)
if position.distance_to(open_position) > 1.0:
# Position doesn't match open_position - force it to open position
print("Door: WARNING - Position doesn't match open_position! Forcing to open: ", open_position, " (was: ", position, ")")
position = open_position
global_position = position # Ensure global_position matches position
is_closed = false # CRITICAL: State MUST be false (open) when at open position
set_collision_layer_value(7, false) # CRITICAL: Collision MUST be DISABLED when open
print("Door: Starting OPEN at position ", position, " (closed: ", closed_position, ", open: ", open_position, ", open_offset: ", open_offset, ", type: ", type, ", direction: ", direction, ") - collision DISABLED, is_closed: ", is_closed)
# CRITICAL: Verify the door is actually at open position after setting it
var actual_distance = position.distance_to(closed_position)
var expected_distance = 16.0 # Should be 16 pixels away
if abs(actual_distance - expected_distance) > 2.0:
push_error("Door: ERROR - Door open/closed distance is wrong! Position: ", position, ", closed: ", closed_position, ", distance: ", actual_distance, " (expected: ", expected_distance, ")")
# Force it to correct open position
position = open_position
global_position = open_position
is_closed = false # CRITICAL: Ensure state is false when at open position
set_collision_layer_value(7, false)
print("Door: FORCED door to open position: ", position, " (distance to closed: ", position.distance_to(closed_position), ", is_closed: ", is_closed, ")")
# FINAL VERIFICATION: Double-check state matches position
var distance_to_closed = position.distance_to(closed_position)
var should_be_open = distance_to_closed > 8.0 # If more than 8px from closed, should be open
if should_be_open and is_closed:
push_error("Door: ERROR - Door is at open position but is_closed is true! Fixing state...")
is_closed = false
set_collision_layer_value(7, false)
print("Door: Fixed state - door is now OPEN (is_closed: ", is_closed, ", collision: ", get_collision_layer_value(7), ")")
elif not should_be_open and not is_closed:
push_error("Door: ERROR - Door is at closed position but is_closed is false! Fixing state...")
is_closed = true
set_collision_layer_value(7, true)
print("Door: Fixed state - door is now CLOSED (is_closed: ", is_closed, ", collision: ", get_collision_layer_value(7), ")")
# NOTE: Doors are NOT connected via signals to room triggers
# Instead, room triggers call door._on_room_entered() directly
# This prevents doors from reacting to ALL room entries, only their own blocking room
func _create_key_indicator():
# Create visual indicator for key above door
if key_indicator:
return # Already created
key_indicator = Sprite2D.new()
# Load key texture from loot system
var key_texture = load("res://assets/gfx/pickups/items_n_shit.png")
if key_texture:
key_indicator.texture = key_texture
key_indicator.hframes = 20
key_indicator.vframes = 14
key_indicator.frame = (13 * 20) + 10 # Key frame from loot system
key_indicator.position = Vector2(0, -24) # Above door
key_indicator.visible = false # Hidden until key is used
add_child(key_indicator)
func _on_room_entered(body):
# Player entered the room - close this door if puzzle not solved
# This door is IN the room that was just entered (room1 == entered room OR blocking_room == entered room)
if not body.is_in_group("player"):
return
# Verify this door is in the room we just entered
if not room_trigger_area:
return # No trigger set, don't do anything
var trigger_room = room_trigger_area.room if room_trigger_area.room else {}
var door_room1 = room1 if room1 else {}
var door_blocking_room = blocking_room if blocking_room else {}
# Check if door is IN the trigger room (door starts FROM trigger room OR blocking_room == trigger room)
var door_in_trigger_room = false
if trigger_room and not trigger_room.is_empty():
# Check room1 first (door starts FROM this room)
if door_room1 and not door_room1.is_empty():
door_in_trigger_room = (door_room1.x == trigger_room.x and door_room1.y == trigger_room.y and \
door_room1.w == trigger_room.w and door_room1.h == trigger_room.h)
# Also check blocking_room (should match the puzzle room)
if not door_in_trigger_room and door_blocking_room and not door_blocking_room.is_empty():
door_in_trigger_room = (door_blocking_room.x == trigger_room.x and door_blocking_room.y == trigger_room.y and \
door_blocking_room.w == trigger_room.w and door_blocking_room.h == trigger_room.h)
if not door_in_trigger_room:
# This door is NOT in the trigger room - ignore
return
# This door is IN the room that was just entered - close it if puzzle not solved
if type == "StoneDoor" or type == "GateDoor":
# Close door if puzzle not solved and door is currently open
if not puzzle_solved:
# Check both is_closed flag AND actual position to determine door state
var distance_to_closed = position.distance_to(closed_position) if closed_position != Vector2.ZERO else 999.0
var is_actually_open = distance_to_closed > 5.0 # If door is more than 5 pixels away from closed_position, it's open
print("Door: _on_room_entered() - type: ", type, ", is_closed: ", is_closed, ", is_actually_open: ", is_actually_open, ", position: ", position, ", closed: ", closed_position, ", distance: ", distance_to_closed)
# CRITICAL: Only close if door is actually open (both flag and position must indicate open)
# If door is already closed, don't do anything
if is_actually_open and not is_closing and not is_opening:
# Door is actually open (position is away from closed position) - close it
print("Door: Closing door on room entry - was at position ", position, " (closed: ", closed_position, ", is_closed: ", is_closed, ", distance: ", distance_to_closed, ")")
# Ensure door is at open position before closing
var expected_open_pos = closed_position + open_offset
var dist_to_open = position.distance_to(expected_open_pos)
if dist_to_open > 5.0:
# Door is not at expected open position - reset to open position first
print("Door: WARNING - Door is not at expected open position! Resetting to open: ", expected_open_pos, " (was at: ", position, ")")
position = expected_open_pos
global_position = expected_open_pos
is_closed = false
set_collision_layer_value(7, false)
_close()
# Don't check puzzle state immediately - wait for door to finish closing
# Puzzle state will be checked when closing animation completes (in _process)
return # Exit early, don't check puzzle state yet
elif is_actually_open:
# Door is open but animation already in progress - don't interfere
print("Door: Door is open but animation in progress, not closing")
return
elif not is_actually_open:
# Door is already at closed position - but for StoneDoor/GateDoor, this shouldn't happen on room entry
# They should start OPEN and then CLOSE when entering room
# If door is at closed position, it might have been closed already - don't do anything
print("Door: WARNING - Door is already at closed position when entering room! This shouldn't happen for StoneDoor/GateDoor that start open.")
if closed_position != Vector2.ZERO:
# Ensure exact position and state match
position = closed_position
global_position = closed_position
is_closed = true
set_collision_layer_value(7, true) # Collision ENABLED when closed
print("Door: Door was already closed - ensuring state is correct, position: ", position, ", closed: ", closed_position)
# Now that door is confirmed closed, check if puzzle is already solved
# CRITICAL: Only check puzzle state if door is closed - don't check if puzzle is already solved
if not puzzle_solved:
_check_puzzle_state()
# If door is already closing (animation in progress), don't check puzzle state yet
# Puzzle state will be checked when closing animation completes (in _process)
func _on_room_exited(body):
# Player left the room
if not body.is_in_group("player"):
return
# Doors stay in their current state
func _check_puzzle_state():
# Check if room puzzle is solved
# IMPORTANT: Only check puzzle state if we're in the blocking room
if puzzle_solved:
return # Already solved
# Check if all enemies are defeated (enemies in blocking room)
if requires_enemies and _are_all_enemies_defeated():
print("Door: All enemies defeated! Opening door ", name, " (type: ", type, ", room: ", blocking_room.get("x", "?") if blocking_room and not blocking_room.is_empty() else "?", ",", blocking_room.get("y", "?") if blocking_room and not blocking_room.is_empty() else "?", ")")
enemies_defeated = true
puzzle_solved = true
if is_closed:
_open()
return
# Check if all required switches are activated (switches in switch_room, before the door)
if _are_all_switches_activated():
switches_activated = true
puzzle_solved = true
if is_closed:
_open()
return
func _are_all_enemies_defeated() -> bool:
# Check if all enemies spawned from spawners in the puzzle room are defeated
# CRITICAL: Only check enemies that were SPAWNED from spawners (not pre-spawned enemies)
# Use room1 (the room this door is IN) or blocking_room for checking enemies
var target_room = room1 if room1 and not room1.is_empty() else blocking_room
if target_room.is_empty():
return false
# Find all enemies in the room that were spawned from spawners
var entities_node = get_tree().get_first_node_in_group("game_world")
if not entities_node:
entities_node = get_node("/root/GameWorld/Entities")
if not entities_node:
return false
var room_spawned_enemies = []
for child in entities_node.get_children():
if child.is_in_group("enemy"):
# CRITICAL: Only check enemies that were spawned from spawners (not pre-spawned)
if not child.has_meta("spawned_from_spawner") or not child.get_meta("spawned_from_spawner"):
continue # Skip pre-spawned enemies
# Check if enemy is in this room (use position-based check, more reliable)
var enemy_in_room = false
var tile_size = 16
var enemy_tile_x = int(child.global_position.x / tile_size)
var enemy_tile_y = int(child.global_position.y / tile_size)
var room_min_x = target_room.x + 2
var room_max_x = target_room.x + target_room.w - 2
var room_min_y = target_room.y + 2
var room_max_y = target_room.y + target_room.h - 2
if enemy_tile_x >= room_min_x and enemy_tile_x < room_max_x and \
enemy_tile_y >= room_min_y and enemy_tile_y < room_max_y:
enemy_in_room = true
# Also check spawner metadata - if enemy has spawner_name matching this room's spawners
if child.has_meta("spawner_name"):
var spawner_name = child.get_meta("spawner_name")
# Spawner names are like "EnemySpawner_<room_x>_<room_y>"
if str(target_room.x) in spawner_name and str(target_room.y) in spawner_name:
enemy_in_room = true # Confirmed by spawner name
if enemy_in_room:
room_spawned_enemies.append(child)
print("Door: Found spawned enemy in room: ", child.name, " (spawner: ", child.get_meta("spawner_name") if child.has_meta("spawner_name") else "unknown", ", is_dead: ", child.is_dead if "is_dead" in child else "unknown", ")")
# Check if all spawned enemies are dead
print("Door: _are_all_enemies_defeated() - Found ", room_spawned_enemies.size(), " spawned enemies in room (", target_room.get("x", "?") if target_room and not target_room.is_empty() else "?", ",", target_room.get("y", "?") if target_room and not target_room.is_empty() else "?", ")")
if room_spawned_enemies.size() == 0:
# No spawned enemies found - if door requires enemies, puzzle is not solved
# But if there were never any enemies, this might mean they haven't spawned yet or all are already dead/removed
print("Door: No spawned enemies found in room - puzzle not solved yet (enemies may not have spawned or already removed)")
return false
for enemy in room_spawned_enemies:
if is_instance_valid(enemy):
var enemy_is_dead = false
if "is_dead" in enemy:
enemy_is_dead = enemy.is_dead
else:
# Check if enemy is queued for deletion or removed from scene
enemy_is_dead = enemy.is_queued_for_deletion() or not enemy.is_inside_tree()
if not enemy_is_dead:
print("Door: Enemy ", enemy.name, " is still alive (is_dead: ", enemy_is_dead, ", is_queued: ", enemy.is_queued_for_deletion(), ", in_tree: ", enemy.is_inside_tree(), ")")
return false
else:
# Enemy is no longer valid (removed from scene) - consider it dead
print("Door: Enemy is no longer valid (removed from scene) - counting as dead")
print("Door: All ", room_spawned_enemies.size(), " spawned enemies are dead! Puzzle solved!")
return true # All enemies are dead
func _are_all_switches_activated() -> bool:
# Check if all required switches are activated
# CRITICAL: ONLY check connected_switches - switches are explicitly connected when spawned
# Do NOT use position-based fallback checks - they cause cross-room door triggering!
if connected_switches.size() > 0:
# Check all connected switches (these are the switches in THIS door's puzzle room)
print("Door: _are_all_switches_activated() - Checking ", connected_switches.size(), " connected switches for door ", name, " (room: ", blocking_room.get("x", "?"), ",", blocking_room.get("y", "?"), ")")
for switch in connected_switches:
if not is_instance_valid(switch):
continue
# is_activated is a variable, not a method
if not switch.is_activated:
print("Door: Switch ", switch.name, " is NOT activated")
return false
print("Door: All connected switches are activated!")
return true # All connected switches are activated
# CRITICAL: If no switches are connected, the puzzle is NOT solved!
# Switches should ALWAYS be connected when spawned - if they're not, it's an error
print("Door: WARNING - Door ", name, " has no connected switches! Puzzle cannot be solved!")
return false # No connected switches means puzzle is NOT solved
func _on_key_interaction_area_body_entered(body):
# Player entered key interaction area
if not body.is_in_group("player"):
return
if type == "KeyDoor" and is_closed and not key_used:
# Check if player has a key
if body.has_method("has_key") and body.has_method("use_key"):
if body.has_key():
# Use key and open door
body.use_key()
key_used = true
_show_key_indicator()
_open()
print("KeyDoor opened with key!")
func _show_key_indicator():
# Show key indicator above door
if key_indicator:
key_indicator.visible = true
# Make sure it's on top (higher z-index or add to front)
key_indicator.z_index = 10
move_child(key_indicator, get_child_count() - 1) # Move to front
else:
# Create key indicator if it doesn't exist yet
_create_key_indicator()
if key_indicator:
key_indicator.visible = true
func teleportPlayer(body: Node2D):
var keydoor_open_offset = Vector2.ZERO
match direction:
"Up":
keydoor_open_offset = Vector2(0, 16) # Open is 16px UP from closed
"Down":
keydoor_open_offset = Vector2(0, -16) # Open is 16px DOWN from closed (row 0 to row 1)
"Left":
keydoor_open_offset = Vector2(16, 0) # Open is 16px LEFT from closed
"Right":
keydoor_open_offset = Vector2(-16, 0) # Open is 16px RIGHT from closed
body.position = self.global_position + keydoor_open_offset
pass