added blocking doors to paths.
This commit is contained in:
@@ -22,7 +22,7 @@ var level_exp_collected: float = 0.0
|
||||
var level_coins_collected: int = 0
|
||||
|
||||
# Client ready tracking (server only)
|
||||
var clients_ready: Dictionary = {} # peer_id -> bool
|
||||
var clients_ready: Dictionary = {} # peer_id -> bool
|
||||
|
||||
func _ready():
|
||||
# Add to group for easy access
|
||||
@@ -222,7 +222,7 @@ func _sync_enemy_position(enemy_name: String, enemy_index: int, pos: Vector2, ve
|
||||
# Clients receive enemy position updates from server
|
||||
# Find the enemy by name or index
|
||||
if multiplayer.is_server():
|
||||
return # Server ignores this (it's the sender)
|
||||
return # Server ignores this (it's the sender)
|
||||
|
||||
var entities_node = get_node_or_null("Entities")
|
||||
if not entities_node:
|
||||
@@ -248,7 +248,7 @@ func _sync_enemy_death(enemy_name: String, enemy_index: int):
|
||||
# Clients receive enemy death sync from server
|
||||
# Find the enemy by name or index
|
||||
if multiplayer.is_server():
|
||||
return # Server ignores this (it's the sender)
|
||||
return # Server ignores this (it's the sender)
|
||||
|
||||
var entities_node = get_node_or_null("Entities")
|
||||
if not entities_node:
|
||||
@@ -278,7 +278,7 @@ func _sync_enemy_damage_visual(enemy_name: String, enemy_index: int):
|
||||
# Clients receive enemy damage visual sync from server
|
||||
# Find the enemy by name or index
|
||||
if multiplayer.is_server():
|
||||
return # Server ignores this (it's the sender)
|
||||
return # Server ignores this (it's the sender)
|
||||
|
||||
var entities_node = get_node_or_null("Entities")
|
||||
if not entities_node:
|
||||
@@ -337,7 +337,7 @@ func _request_loot_pickup(loot_id: int, loot_position: Vector2, player_peer_id:
|
||||
func _sync_show_level_complete(enemies_defeated: int, times_downed: int, exp_collected: float, coins_collected: int):
|
||||
# Clients receive level complete UI sync from server
|
||||
if multiplayer.is_server():
|
||||
return # Server ignores this (it's the sender)
|
||||
return # Server ignores this (it's the sender)
|
||||
|
||||
# Update stats before showing
|
||||
level_enemies_defeated = enemies_defeated
|
||||
@@ -352,7 +352,7 @@ func _sync_show_level_complete(enemies_defeated: int, times_downed: int, exp_col
|
||||
func _sync_hide_level_complete():
|
||||
# Clients receive hide level complete UI sync from server
|
||||
if multiplayer.is_server():
|
||||
return # Server ignores this (it's the sender)
|
||||
return # Server ignores this (it's the sender)
|
||||
|
||||
var level_complete_ui = get_node_or_null("LevelCompleteUI")
|
||||
if level_complete_ui:
|
||||
@@ -362,7 +362,7 @@ func _sync_hide_level_complete():
|
||||
func _sync_show_level_number(level: int):
|
||||
# Clients receive level number UI sync from server
|
||||
if multiplayer.is_server():
|
||||
return # Server ignores this (it's the sender)
|
||||
return # Server ignores this (it's the sender)
|
||||
|
||||
current_level = level
|
||||
_show_level_number()
|
||||
@@ -372,7 +372,7 @@ func _sync_loot_remove(loot_id: int, loot_position: Vector2):
|
||||
# Clients receive loot removal sync from server
|
||||
# Find the loot by ID or position
|
||||
if multiplayer.is_server():
|
||||
return # Server ignores this (it's the sender)
|
||||
return # Server ignores this (it's the sender)
|
||||
|
||||
var entities_node = get_node_or_null("Entities")
|
||||
if not entities_node:
|
||||
@@ -469,6 +469,12 @@ func _generate_dungeon():
|
||||
# Spawn interactable objects
|
||||
_spawn_interactable_objects()
|
||||
|
||||
# Spawn blocking doors
|
||||
_spawn_blocking_doors()
|
||||
|
||||
# Spawn room triggers
|
||||
_spawn_room_triggers()
|
||||
|
||||
# Wait a frame to ensure enemies and objects are properly in scene tree before syncing
|
||||
await get_tree().process_frame
|
||||
|
||||
@@ -483,7 +489,7 @@ func _generate_dungeon():
|
||||
_move_all_players_to_start_room()
|
||||
|
||||
# Update camera immediately to ensure it's looking at the players
|
||||
await get_tree().process_frame # Wait a frame for players to be fully in scene tree
|
||||
await get_tree().process_frame # Wait a frame for players to be fully in scene tree
|
||||
_update_camera()
|
||||
|
||||
# Show level number (for initial level generation only - not when called from level completion)
|
||||
@@ -815,7 +821,7 @@ func _is_safe_spawn_position(world_pos: Vector2) -> bool:
|
||||
return false
|
||||
|
||||
# Check if it's a floor tile
|
||||
if grid[tile_x][tile_y] == 1: # Floor
|
||||
if grid[tile_x][tile_y] == 1: # Floor
|
||||
return true
|
||||
|
||||
return false
|
||||
@@ -824,14 +830,13 @@ func _find_nearby_safe_spawn_position(world_pos: Vector2, max_distance: float =
|
||||
# Find a nearby safe spawn position (on a floor tile)
|
||||
# Returns the original position if it's safe, otherwise finds the nearest safe position
|
||||
# max_distance: Maximum distance to search for a safe position
|
||||
|
||||
# First check if the original position is safe
|
||||
if _is_safe_spawn_position(world_pos):
|
||||
return world_pos
|
||||
|
||||
# Search in expanding circles around the position
|
||||
var tile_size = 16
|
||||
var search_radius = 1 # Start with 1 tile radius
|
||||
var search_radius = 1 # Start with 1 tile radius
|
||||
var max_radius = int(max_distance / tile_size) + 1
|
||||
|
||||
while search_radius <= max_radius:
|
||||
@@ -898,7 +903,7 @@ func _sync_dungeon(dungeon_data_sync: Dictionary, seed_value: int, level: int, h
|
||||
|
||||
dungeon_data = dungeon_data_sync
|
||||
dungeon_seed = seed_value
|
||||
current_level = level # Update current_level FIRST before showing level number
|
||||
current_level = level # Update current_level FIRST before showing level number
|
||||
print("GameWorld: Client updated current_level to ", current_level, " from sync")
|
||||
|
||||
# Clear previous level on client
|
||||
@@ -906,7 +911,7 @@ func _sync_dungeon(dungeon_data_sync: Dictionary, seed_value: int, level: int, h
|
||||
|
||||
# Wait for old entities to be fully freed before spawning new ones
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame # Wait an extra frame to ensure cleanup is complete
|
||||
await get_tree().process_frame # Wait an extra frame to ensure cleanup is complete
|
||||
|
||||
# Render dungeon on client
|
||||
_render_dungeon()
|
||||
@@ -920,9 +925,15 @@ func _sync_dungeon(dungeon_data_sync: Dictionary, seed_value: int, level: int, h
|
||||
# Spawn interactable objects on client
|
||||
_spawn_interactable_objects()
|
||||
|
||||
# Spawn blocking doors on client
|
||||
_spawn_blocking_doors()
|
||||
|
||||
# Spawn room triggers on client
|
||||
_spawn_room_triggers()
|
||||
|
||||
# Wait a frame to ensure all enemies and objects are properly added to scene tree and initialized
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame # Wait an extra frame to ensure enemies are fully ready
|
||||
await get_tree().process_frame # Wait an extra frame to ensure enemies are fully ready
|
||||
|
||||
# Update spawn points - use host's room if available, otherwise use start room
|
||||
if not host_room.is_empty():
|
||||
@@ -1072,10 +1083,20 @@ func _spawn_enemies():
|
||||
if "damage" in enemy_data:
|
||||
enemy.damage = enemy_data.damage
|
||||
|
||||
# CRITICAL: Set collision mask BEFORE adding to scene to ensure enemies collide with walls (layer 7 = bit 6 = 64)
|
||||
# This overrides any collision_mask set in the scene file
|
||||
enemy.collision_mask = 1 | 2 | 64 # Collide with players (layer 1), objects (layer 2), and walls (layer 7 = bit 6 = 64)
|
||||
|
||||
# Add to scene tree AFTER setting authority and stats
|
||||
entities_node.add_child(enemy)
|
||||
enemy.global_position = enemy_data.position
|
||||
|
||||
# CRITICAL: Re-verify collision_mask AFTER adding to scene (in case scene file overrides it)
|
||||
# This ensures enemies always collide with walls (layer 7 = bit 6 = 64)
|
||||
if enemy.collision_mask != (1 | 2 | 64):
|
||||
print("GameWorld: WARNING - Enemy ", enemy.name, " collision_mask was ", enemy.collision_mask, ", expected ", (1 | 2 | 64), "! Correcting...")
|
||||
enemy.collision_mask = 1 | 2 | 64
|
||||
|
||||
# Verify authority is still set after adding to tree
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
var auth_after = enemy.get_multiplayer_authority()
|
||||
@@ -1228,7 +1249,7 @@ func _sync_dungeon_enemy_spawn(enemy_data: Dictionary):
|
||||
if child.is_in_group("enemy") and child.has_meta("dungeon_spawned"):
|
||||
# Check if it's a duplicate by position
|
||||
var child_pos = child.global_position
|
||||
if child_pos.distance_to(enemy_data.position) < 1.0: # Same position
|
||||
if child_pos.distance_to(enemy_data.position) < 1.0: # Same position
|
||||
# Also check if it's dead - if so, remove it first
|
||||
if "is_dead" in child and child.is_dead:
|
||||
print("GameWorld: Removing dead duplicate enemy at ", enemy_data.position)
|
||||
@@ -1275,10 +1296,20 @@ func _sync_dungeon_enemy_spawn(enemy_data: Dictionary):
|
||||
if "damage" in enemy_data:
|
||||
enemy.damage = enemy_data.damage
|
||||
|
||||
# CRITICAL: Set collision mask BEFORE adding to scene to ensure enemies collide with walls (layer 7 = bit 6 = 64)
|
||||
# This overrides any collision_mask set in the scene file
|
||||
enemy.collision_mask = 1 | 2 | 64 # Collide with players (layer 1), objects (layer 2), and walls (layer 7 = bit 6 = 64)
|
||||
|
||||
# Add to scene tree AFTER setting authority and stats
|
||||
entities_node.add_child(enemy)
|
||||
enemy.global_position = enemy_data.position
|
||||
|
||||
# CRITICAL: Re-verify collision_mask AFTER adding to scene (in case scene file overrides it)
|
||||
# This ensures enemies always collide with walls (layer 7 = bit 6 = 64)
|
||||
if enemy.collision_mask != (1 | 2 | 64):
|
||||
print("GameWorld: WARNING - Enemy ", enemy.name, " collision_mask was ", enemy.collision_mask, ", expected ", (1 | 2 | 64), "! Correcting...")
|
||||
enemy.collision_mask = 1 | 2 | 64
|
||||
|
||||
# Verify authority is still set
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
var auth_after = enemy.get_multiplayer_authority()
|
||||
@@ -1338,12 +1369,12 @@ func _clear_level():
|
||||
# Free all entities immediately (not queue_free) to ensure they're gone before spawning new ones
|
||||
for entity in entities_to_remove:
|
||||
if is_instance_valid(entity):
|
||||
entity.free() # Use free() instead of queue_free() for immediate removal
|
||||
entity.free() # Use free() instead of queue_free() for immediate removal
|
||||
|
||||
# Remove stairs area
|
||||
var stairs_area = get_node_or_null("StairsArea")
|
||||
if stairs_area:
|
||||
stairs_area.free() # Use free() for immediate removal
|
||||
stairs_area.free() # Use free() for immediate removal
|
||||
|
||||
# Clear dungeon data (but keep it for now until new one is generated)
|
||||
# dungeon_data = {} # Don't clear yet, wait for new generation
|
||||
@@ -1394,7 +1425,7 @@ func _create_stairs_area():
|
||||
|
||||
# Set collision layer/mask BEFORE adding to scene
|
||||
stairs_area.collision_layer = 0
|
||||
stairs_area.collision_mask = 1 # Detect players (layer 1)
|
||||
stairs_area.collision_mask = 1 # Detect players (layer 1)
|
||||
|
||||
# Add script BEFORE adding to scene (so _ready() is called properly)
|
||||
var stairs_script = load("res://scripts/stairs.gd")
|
||||
@@ -1419,7 +1450,7 @@ func _create_stairs_area():
|
||||
func _on_player_reached_stairs(player: Node):
|
||||
# Player reached stairs - trigger level complete
|
||||
if not multiplayer.is_server() and multiplayer.has_multiplayer_peer():
|
||||
return # Only server handles this
|
||||
return # Only server handles this
|
||||
|
||||
print("GameWorld: Player ", player.name, " reached stairs!")
|
||||
|
||||
@@ -1443,7 +1474,7 @@ func _on_player_reached_stairs(player: Node):
|
||||
_sync_show_level_complete.rpc(level_enemies_defeated, level_times_downed, level_exp_collected, level_coins_collected)
|
||||
|
||||
# After delay, hide UI and generate new level
|
||||
await get_tree().create_timer(5.0).timeout # Show stats for 5 seconds
|
||||
await get_tree().create_timer(5.0).timeout # Show stats for 5 seconds
|
||||
|
||||
# Hide level complete UI (server and clients)
|
||||
var level_complete_ui = get_node_or_null("LevelCompleteUI")
|
||||
@@ -1458,7 +1489,7 @@ func _on_player_reached_stairs(player: Node):
|
||||
|
||||
# Wait for old entities to be fully freed before generating new level
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame # Wait an extra frame to ensure cleanup is complete
|
||||
await get_tree().process_frame # Wait an extra frame to ensure cleanup is complete
|
||||
|
||||
# Generate next level
|
||||
current_level += 1
|
||||
@@ -1471,7 +1502,7 @@ func _on_player_reached_stairs(player: Node):
|
||||
# We need to wait for all the async operations in _generate_dungeon() to finish
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame # Extra frame to ensure everything is done
|
||||
await get_tree().process_frame # Extra frame to ensure everything is done
|
||||
|
||||
# Verify current_level is still correct
|
||||
print("GameWorld: After dungeon generation, current_level = ", current_level)
|
||||
@@ -1493,7 +1524,7 @@ func _on_player_reached_stairs(player: Node):
|
||||
# Sync new level to all clients - use start room since all players should be there
|
||||
# IMPORTANT: Wait multiple frames to ensure dungeon generation and enemy spawning is complete before syncing
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame # Wait extra frames to ensure enemies are fully initialized
|
||||
await get_tree().process_frame # Wait extra frames to ensure enemies are fully initialized
|
||||
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
var start_room = dungeon_data.start_room if not dungeon_data.is_empty() and dungeon_data.has("start_room") else {}
|
||||
@@ -1603,21 +1634,27 @@ func _fade_in_player(player: Node):
|
||||
|
||||
for sprite_layer in sprite_layers:
|
||||
if sprite_layer:
|
||||
sprite_layer.modulate.a = 0.0 # Start invisible
|
||||
sprite_layer.modulate.a = 0.0 # Start invisible
|
||||
fade_tween.tween_property(sprite_layer, "modulate:a", 1.0, 1.0)
|
||||
|
||||
func _show_level_complete_ui():
|
||||
# Create or show level complete UI
|
||||
var level_complete_ui = get_node_or_null("LevelCompleteUI")
|
||||
if not level_complete_ui:
|
||||
# Try to load scene, but fall back to programmatic creation if it doesn't exist
|
||||
var level_complete_scene = load("res://scenes/level_complete_ui.tscn")
|
||||
if level_complete_scene:
|
||||
level_complete_ui = level_complete_scene.instantiate()
|
||||
level_complete_ui.name = "LevelCompleteUI"
|
||||
add_child(level_complete_ui)
|
||||
# Try to load scene if it exists, but fall back to programmatic creation if it doesn't
|
||||
var scene_path = "res://scenes/level_complete_ui.tscn"
|
||||
if ResourceLoader.exists(scene_path):
|
||||
var level_complete_scene = load(scene_path)
|
||||
if level_complete_scene:
|
||||
level_complete_ui = level_complete_scene.instantiate()
|
||||
level_complete_ui.name = "LevelCompleteUI"
|
||||
add_child(level_complete_ui)
|
||||
else:
|
||||
# Scene file exists but failed to load - fall back to programmatic creation
|
||||
print("GameWorld: Warning - level_complete_ui.tscn exists but failed to load, creating programmatically")
|
||||
level_complete_ui = _create_level_complete_ui_programmatically()
|
||||
else:
|
||||
# Create UI programmatically if scene doesn't exist
|
||||
# Scene file doesn't exist - create UI programmatically (expected behavior)
|
||||
level_complete_ui = _create_level_complete_ui_programmatically()
|
||||
|
||||
if level_complete_ui:
|
||||
@@ -1634,14 +1671,20 @@ func _show_level_number():
|
||||
print("GameWorld: _show_level_number() called with current_level = ", current_level)
|
||||
var level_text_ui = get_node_or_null("LevelTextUI")
|
||||
if not level_text_ui:
|
||||
# Try to load scene, but fall back to programmatic creation if it doesn't exist
|
||||
var level_text_scene = load("res://scenes/level_text_ui.tscn")
|
||||
if level_text_scene:
|
||||
level_text_ui = level_text_scene.instantiate()
|
||||
level_text_ui.name = "LevelTextUI"
|
||||
add_child(level_text_ui)
|
||||
# Try to load scene if it exists, but fall back to programmatic creation if it doesn't
|
||||
var scene_path = "res://scenes/level_text_ui.tscn"
|
||||
if ResourceLoader.exists(scene_path):
|
||||
var level_text_scene = load(scene_path)
|
||||
if level_text_scene:
|
||||
level_text_ui = level_text_scene.instantiate()
|
||||
level_text_ui.name = "LevelTextUI"
|
||||
add_child(level_text_ui)
|
||||
else:
|
||||
# Scene file exists but failed to load - fall back to programmatic creation
|
||||
print("GameWorld: Warning - level_text_ui.tscn exists but failed to load, creating programmatically")
|
||||
level_text_ui = _create_level_text_ui_programmatically()
|
||||
else:
|
||||
# Create UI programmatically if scene doesn't exist
|
||||
# Scene file doesn't exist - create UI programmatically (expected behavior)
|
||||
level_text_ui = _create_level_text_ui_programmatically()
|
||||
|
||||
if level_text_ui:
|
||||
@@ -1664,7 +1707,7 @@ func _create_level_complete_ui_programmatically() -> Node:
|
||||
|
||||
var vbox = VBoxContainer.new()
|
||||
vbox.set_anchors_preset(Control.PRESET_CENTER)
|
||||
vbox.offset_top = -200 # Position a bit up from center
|
||||
vbox.offset_top = -200 # Position a bit up from center
|
||||
canvas_layer.add_child(vbox)
|
||||
|
||||
# Title
|
||||
@@ -1757,3 +1800,649 @@ func _move_players_to_host_room(host_room: Dictionary):
|
||||
player.position = new_pos
|
||||
print("GameWorld: Moved player ", player.name, " to ", new_pos)
|
||||
spawn_index += 1
|
||||
|
||||
func _spawn_blocking_doors():
|
||||
# Spawn blocking doors from dungeon data
|
||||
if dungeon_data.is_empty() or not dungeon_data.has("blocking_doors"):
|
||||
return
|
||||
|
||||
var blocking_doors = dungeon_data.blocking_doors
|
||||
if blocking_doors == null or not blocking_doors is Array:
|
||||
return
|
||||
|
||||
var door_scene = preload("res://scenes/door.tscn")
|
||||
if not door_scene:
|
||||
push_error("ERROR: Could not load door scene!")
|
||||
return
|
||||
|
||||
var entities_node = get_node_or_null("Entities")
|
||||
if not entities_node:
|
||||
push_error("ERROR: Could not find Entities node!")
|
||||
return
|
||||
|
||||
print("GameWorld: Spawning ", blocking_doors.size(), " blocking doors")
|
||||
|
||||
for i in range(blocking_doors.size()):
|
||||
var door_data = blocking_doors[i]
|
||||
if not door_data is Dictionary:
|
||||
continue
|
||||
|
||||
var door = door_scene.instantiate()
|
||||
door.name = "BlockingDoor_%d" % i
|
||||
door.add_to_group("blocking_door")
|
||||
|
||||
# Set door properties BEFORE adding to scene (so _ready() has correct values)
|
||||
door.type = door_data.type if "type" in door_data else "StoneDoor"
|
||||
door.direction = door_data.direction if "direction" in door_data else "Up"
|
||||
door.is_closed = door_data.is_closed if "is_closed" in door_data else true
|
||||
|
||||
# CRITICAL: Set puzzle requirements based on door_data
|
||||
if "puzzle_type" in door_data:
|
||||
if door_data.puzzle_type == "enemy":
|
||||
door.requires_enemies = true
|
||||
door.requires_switch = false
|
||||
print("GameWorld: Door ", door.name, " requires enemies to open (puzzle_type: enemy)")
|
||||
elif door_data.puzzle_type in ["switch_walk", "switch_pillar"]:
|
||||
door.requires_enemies = false
|
||||
door.requires_switch = true
|
||||
print("GameWorld: Door ", door.name, " requires switch to open (puzzle_type: ", door_data.puzzle_type, ")")
|
||||
door.blocking_room = door_data.blocking_room if "blocking_room" in door_data else {}
|
||||
door.switch_room = door_data.switch_room if "switch_room" in door_data else {}
|
||||
|
||||
# CRITICAL: Verify door has blocking_room set - StoneDoor/GateDoor MUST be in a puzzle room
|
||||
if (door_data.type == "StoneDoor" or door_data.type == "GateDoor"):
|
||||
if not "blocking_room" in door_data or door_data.blocking_room.is_empty():
|
||||
push_error("GameWorld: ERROR - Blocking door ", door.name, " (", door_data.type, ") has no blocking_room! This door should not exist! Removing it.")
|
||||
door.queue_free()
|
||||
continue
|
||||
|
||||
# CRITICAL: Verify door has puzzle_type - StoneDoor/GateDoor MUST have a puzzle
|
||||
if not "puzzle_type" in door_data:
|
||||
push_error("GameWorld: ERROR - Blocking door ", door.name, " (", door_data.type, ") has no puzzle_type! This door should not exist! Removing it.")
|
||||
door.queue_free()
|
||||
continue
|
||||
|
||||
print("GameWorld: Creating blocking door ", door.name, " (", door_data.type, ") for puzzle room (", door_data.blocking_room.x, ", ", door_data.blocking_room.y, "), puzzle_type: ", door_data.puzzle_type)
|
||||
|
||||
# CRITICAL: Store original door connection info from door_data.door
|
||||
# For blocking doors: room1 = puzzle room (where door is IN / leads FROM)
|
||||
# room2 = other room (where door leads TO)
|
||||
# blocking_room = puzzle room (same as room1, where puzzle is)
|
||||
if "door" in door_data and door_data.door is Dictionary:
|
||||
var original_door = door_data.door
|
||||
if "room1" in original_door and original_door.room1:
|
||||
door.room1 = original_door.room1
|
||||
if "room2" in original_door and original_door.room2:
|
||||
door.room2 = original_door.room2
|
||||
|
||||
# CRITICAL: For StoneDoor/GateDoor, verify door.room1 matches blocking_room
|
||||
# The door should be IN the puzzle room (room1 == blocking_room)
|
||||
if (door_data.type == "StoneDoor" or door_data.type == "GateDoor") and door.blocking_room and not door.blocking_room.is_empty():
|
||||
if not door.room1 or door.room1.is_empty():
|
||||
push_error("GameWorld: ERROR - Blocking door ", door.name, " has no room1! Cannot verify it's in puzzle room! Removing it.")
|
||||
door.queue_free()
|
||||
continue
|
||||
|
||||
# Verify room1 (where door is) matches blocking_room (puzzle room)
|
||||
var room1_matches_blocking = (door.room1.x == door.blocking_room.x and \
|
||||
door.room1.y == door.blocking_room.y and \
|
||||
door.room1.w == door.blocking_room.w and \
|
||||
door.room1.h == door.blocking_room.h)
|
||||
|
||||
if not room1_matches_blocking:
|
||||
push_error("GameWorld: ERROR - Blocking door ", door.name, " room1 (", door.room1.x, ",", door.room1.y, ") doesn't match blocking_room (", door.blocking_room.x, ",", door.blocking_room.y, ")! This door is NOT in the puzzle room! Removing it.")
|
||||
door.queue_free()
|
||||
continue
|
||||
|
||||
print("GameWorld: Blocking door ", door.name, " verified - room1 (", door.room1.x, ",", door.room1.y, ") == blocking_room (", door.blocking_room.x, ",", door.blocking_room.y, ") - door is IN puzzle room")
|
||||
|
||||
# Set multiplayer authority BEFORE adding to scene
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
door.set_multiplayer_authority(1)
|
||||
|
||||
# CRITICAL: Set position BEFORE adding to scene tree (so _ready() can use it)
|
||||
door.global_position = door_data.position if "position" in door_data else Vector2.ZERO
|
||||
|
||||
# Add to scene (this triggers _ready() which will use the position we just set)
|
||||
entities_node.add_child(door)
|
||||
|
||||
# NOTE: Doors are connected to room triggers automatically by room_trigger._find_room_entities()
|
||||
# No need to manually connect them here
|
||||
|
||||
# CRITICAL SAFETY CHECK: Verify door is for a puzzle room (StoneDoor/GateDoor should ONLY exist in puzzle rooms)
|
||||
if door_data.type == "StoneDoor" or door_data.type == "GateDoor":
|
||||
if not "blocking_room" in door_data or door_data.blocking_room.is_empty():
|
||||
push_error("GameWorld: ERROR - Blocking door ", door.name, " (", door_data.type, ") has no blocking_room! This door should not exist!")
|
||||
door.queue_free()
|
||||
continue
|
||||
|
||||
if not "puzzle_type" in door_data:
|
||||
push_error("GameWorld: ERROR - Blocking door ", door.name, " (", door_data.type, ") has no puzzle_type! This door should not exist!")
|
||||
door.queue_free()
|
||||
continue
|
||||
|
||||
# CRITICAL: Verify that this door actually has puzzle elements
|
||||
# Puzzle elements should already be created in dungeon_generator, but verify they exist
|
||||
var has_puzzle_element = false
|
||||
|
||||
# Spawn floor switch if this door requires one (puzzle_type is "switch_walk" or "switch_pillar")
|
||||
if "puzzle_type" in door_data and (door_data.puzzle_type == "switch_walk" or door_data.puzzle_type == "switch_pillar"):
|
||||
if "floor_switch_position" in door_data or ("switch_data" in door_data and door_data.switch_data.has("position")):
|
||||
var switch_pos = door_data.floor_switch_position if "floor_switch_position" in door_data else door_data.switch_data.position
|
||||
var switch_tile_x = door_data.switch_tile_x if "switch_tile_x" in door_data else door_data.switch_data.tile_x
|
||||
var switch_tile_y = door_data.switch_tile_y if "switch_tile_y" in door_data else door_data.switch_data.tile_y
|
||||
var switch_type = door_data.switch_type if "switch_type" in door_data else ("walk" if door_data.puzzle_type == "switch_walk" else "pillar")
|
||||
var switch_weight = door_data.switch_required_weight if "switch_required_weight" in door_data else (1.0 if switch_type == "walk" else 5.0)
|
||||
|
||||
# CRITICAL: Check if switch already exists for THIS SPECIFIC ROOM (to avoid duplicates)
|
||||
# Only connect to switches in the SAME blocking_room - never connect across rooms!
|
||||
var existing_switch = null
|
||||
var door_blocking_room = door_data.blocking_room if "blocking_room" in door_data else {}
|
||||
|
||||
# CRITICAL: Verify door has valid blocking_room before searching for switches
|
||||
if door_blocking_room.is_empty():
|
||||
push_error("GameWorld: ERROR - Door ", door.name, " has empty blocking_room! Cannot find switches!")
|
||||
continue
|
||||
|
||||
for existing in get_tree().get_nodes_in_group("floor_switch"):
|
||||
if not is_instance_valid(existing):
|
||||
continue
|
||||
|
||||
# CRITICAL: Check ROOM FIRST (most important), then position
|
||||
# Switches MUST have switch_room metadata set when spawned
|
||||
if not existing.has_meta("switch_room"):
|
||||
continue # Switch has no room metadata - skip it (can't verify it's in the right room)
|
||||
|
||||
var existing_switch_room = existing.get_meta("switch_room")
|
||||
if existing_switch_room.is_empty():
|
||||
continue # Invalid room data
|
||||
|
||||
# CRITICAL: Verify switch is in the SAME room as door (check room FIRST)
|
||||
var room_match = (existing_switch_room.x == door_blocking_room.x and \
|
||||
existing_switch_room.y == door_blocking_room.y and \
|
||||
existing_switch_room.w == door_blocking_room.w and \
|
||||
existing_switch_room.h == door_blocking_room.h)
|
||||
|
||||
if not room_match:
|
||||
# Switch is in a different room - DO NOT connect, skip it
|
||||
continue
|
||||
|
||||
# Room matches - now check position (must be exact match)
|
||||
var pos_match = existing.global_position.distance_to(switch_pos) < 1.0
|
||||
if pos_match:
|
||||
# Both room AND position match - this is the correct switch
|
||||
existing_switch = existing
|
||||
print("GameWorld: Found existing switch ", existing.name, " in room (", door_blocking_room.x, ",", door_blocking_room.y, ") at position ", existing.global_position, " matching door room and position")
|
||||
break
|
||||
|
||||
if existing_switch:
|
||||
# CRITICAL: Double-check room match before connecting
|
||||
var existing_switch_room_final = existing_switch.get_meta("switch_room")
|
||||
var final_room_match = false
|
||||
if existing_switch_room_final and not existing_switch_room_final.is_empty() and door_blocking_room and not door_blocking_room.is_empty():
|
||||
final_room_match = (existing_switch_room_final.x == door_blocking_room.x and \
|
||||
existing_switch_room_final.y == door_blocking_room.y and \
|
||||
existing_switch_room_final.w == door_blocking_room.w and \
|
||||
existing_switch_room_final.h == door_blocking_room.h)
|
||||
|
||||
if final_room_match:
|
||||
# Switch already exists in the SAME room - connect door to existing switch
|
||||
door.connected_switches.append(existing_switch)
|
||||
has_puzzle_element = true
|
||||
print("GameWorld: Connected door ", door.name, " (room: ", door_blocking_room.get("x", "?"), ",", door_blocking_room.get("y", "?"), ") to existing switch ", existing_switch.name, " in SAME room")
|
||||
else:
|
||||
push_error("GameWorld: ERROR - Attempted to connect door ", door.name, " to switch ", existing_switch.name, " but rooms don't match! Door room: (", door_blocking_room.get("x", "?"), ",", door_blocking_room.get("y", "?"), "), Switch room: (", existing_switch_room_final.get("x", "?") if existing_switch_room_final else "?", ",", existing_switch_room_final.get("y", "?") if existing_switch_room_final else "?", ")")
|
||||
# Don't connect - spawn a new switch instead
|
||||
existing_switch = null
|
||||
else:
|
||||
# Spawn new switch - CRITICAL: Only spawn if we have valid room data
|
||||
if not door_blocking_room or door_blocking_room.is_empty():
|
||||
push_error("GameWorld: ERROR - Cannot spawn switch for door ", door.name, " - no blocking_room!")
|
||||
continue
|
||||
|
||||
# CRITICAL: Verify switch position matches door_data switch position exactly
|
||||
# If switch_room in door_data doesn't match blocking_room, it's an error
|
||||
if "switch_room" in door_data:
|
||||
var door_switch_room = door_data.switch_room
|
||||
if door_switch_room and not door_switch_room.is_empty():
|
||||
var switch_room_matches = (door_switch_room.x == door_blocking_room.x and \
|
||||
door_switch_room.y == door_blocking_room.y and \
|
||||
door_switch_room.w == door_blocking_room.w and \
|
||||
door_switch_room.h == door_blocking_room.h)
|
||||
if not switch_room_matches:
|
||||
push_error("GameWorld: ERROR - Door ", door.name, " switch_room (", door_switch_room.x, ",", door_switch_room.y, ") doesn't match blocking_room (", door_blocking_room.x, ",", door_blocking_room.y, ")! This is a bug!")
|
||||
door.queue_free()
|
||||
continue
|
||||
|
||||
var switch = _spawn_floor_switch(switch_pos, switch_weight, switch_tile_x, switch_tile_y, switch_type, door_blocking_room)
|
||||
if switch:
|
||||
# CRITICAL: Verify switch has room metadata set (should be set in _spawn_floor_switch)
|
||||
if not switch.has_meta("switch_room"):
|
||||
push_error("GameWorld: ERROR - Switch ", switch.name, " was spawned without switch_room metadata! Setting it now as fallback.")
|
||||
switch.set_meta("switch_room", door_blocking_room) # Set it now as fallback
|
||||
|
||||
# CRITICAL: Verify switch room matches door blocking_room before connecting
|
||||
# This ensures switches are ONLY connected to doors in the SAME room
|
||||
var switch_room_check = switch.get_meta("switch_room")
|
||||
if switch_room_check and not switch_room_check.is_empty() and door_blocking_room and not door_blocking_room.is_empty():
|
||||
var room_match_before_connect = (switch_room_check.x == door_blocking_room.x and \
|
||||
switch_room_check.y == door_blocking_room.y and \
|
||||
switch_room_check.w == door_blocking_room.w and \
|
||||
switch_room_check.h == door_blocking_room.h)
|
||||
|
||||
if room_match_before_connect:
|
||||
# Connect switch to door ONLY if rooms match exactly
|
||||
door.connected_switches.append(switch)
|
||||
has_puzzle_element = true
|
||||
print("GameWorld: Spawned switch ", switch.name, " in room (", door_blocking_room.x, ",", door_blocking_room.y, ") and connected to door ", door.name, " in SAME room")
|
||||
|
||||
# If this is a pillar switch, place a pillar in the same room
|
||||
if switch_type == "pillar":
|
||||
_place_pillar_in_room(door_blocking_room, switch_pos)
|
||||
else:
|
||||
push_error("GameWorld: ERROR - Switch ", switch.name, " room (", switch_room_check.x, ",", switch_room_check.y, ") doesn't match door blocking_room (", door_blocking_room.x, ",", door_blocking_room.y, ")! NOT connecting! Removing switch.")
|
||||
switch.queue_free() # Remove the switch since it's in wrong room
|
||||
has_puzzle_element = false # Don't count this as puzzle element
|
||||
else:
|
||||
push_error("GameWorld: ERROR - Switch ", switch.name, " or door ", door.name, " has invalid room data! Switch room: ", switch_room_check, ", Door room: ", door_blocking_room)
|
||||
switch.queue_free() # Remove invalid switch
|
||||
has_puzzle_element = false
|
||||
else:
|
||||
push_warning("GameWorld: WARNING - Failed to spawn floor switch for door ", door.name, "!")
|
||||
|
||||
# Place key in room if this is a KeyDoor
|
||||
if door_data.type == "KeyDoor" and "key_room" in door_data:
|
||||
_place_key_in_room(door_data.key_room)
|
||||
has_puzzle_element = true # KeyDoors are always valid
|
||||
|
||||
# Spawn enemy spawners if this door requires enemies (puzzle_type is "enemy")
|
||||
if "puzzle_type" in door_data and door_data.puzzle_type == "enemy":
|
||||
print("GameWorld: ===== Door ", door.name, " has puzzle_type 'enemy' - checking for enemy_spawners =====")
|
||||
if "enemy_spawners" in door_data and door_data.enemy_spawners is Array:
|
||||
print("GameWorld: Door has enemy_spawners array with ", door_data.enemy_spawners.size(), " spawners")
|
||||
var spawner_created = false
|
||||
for spawner_data in door_data.enemy_spawners:
|
||||
if spawner_data is Dictionary and spawner_data.has("position"):
|
||||
# Check if spawner already exists for this room (to avoid duplicates)
|
||||
var existing_spawner = null
|
||||
for existing in get_tree().get_nodes_in_group("enemy_spawner"):
|
||||
if existing.global_position.distance_to(spawner_data.position) < 1.0:
|
||||
existing_spawner = existing
|
||||
break
|
||||
|
||||
if existing_spawner:
|
||||
# Spawner already exists - just verify it's set up correctly
|
||||
existing_spawner.set_meta("blocking_room", door_data.blocking_room)
|
||||
spawner_created = true
|
||||
print("GameWorld: Found existing spawner ", existing_spawner.name, " for door ", door.name)
|
||||
else:
|
||||
# Spawn new spawner
|
||||
var spawner = _spawn_enemy_spawner(
|
||||
spawner_data.position,
|
||||
spawner_data.room if spawner_data.has("room") else door_data.blocking_room,
|
||||
spawner_data # Pass spawner_data to access spawn_once flag
|
||||
)
|
||||
if spawner:
|
||||
# Store reference to door for spawner (optional - spawner will be found by room trigger)
|
||||
spawner.set_meta("blocking_room", door_data.blocking_room)
|
||||
spawner_created = true
|
||||
print("GameWorld: Spawned enemy spawner ", spawner.name, " for door ", door.name, " at ", spawner_data.position)
|
||||
if spawner_created:
|
||||
has_puzzle_element = true
|
||||
else:
|
||||
push_warning("GameWorld: WARNING - Failed to spawn enemy spawner for door ", door.name, "!")
|
||||
if "enemy_spawners" not in door_data:
|
||||
push_warning("GameWorld: Reason: door_data has no 'enemy_spawners' key!")
|
||||
elif not door_data.enemy_spawners is Array:
|
||||
push_warning("GameWorld: Reason: door_data.enemy_spawners is not an Array! Type: ", typeof(door_data.enemy_spawners))
|
||||
elif door_data.enemy_spawners.size() == 0:
|
||||
push_warning("GameWorld: Reason: door_data.enemy_spawners array is empty!")
|
||||
else:
|
||||
if "puzzle_type" in door_data:
|
||||
print("GameWorld: Door ", door.name, " has puzzle_type '", door_data.puzzle_type, "' (not 'enemy')")
|
||||
|
||||
# CRITICAL: If door has no puzzle elements (neither switch nor spawner), this is an error
|
||||
# This should never happen if dungeon_generator logic is correct, but add safety check
|
||||
if door_data.type != "KeyDoor" and not has_puzzle_element:
|
||||
push_error("GameWorld: ERROR - Blocking door ", door.name, " (type: ", door_data.type, ") has no puzzle elements! This door should not exist!")
|
||||
print("GameWorld: Door data keys: ", door_data.keys())
|
||||
print("GameWorld: Door puzzle_type: ", door_data.get("puzzle_type", "MISSING"))
|
||||
print("GameWorld: Door has requires_switch: ", door_data.get("requires_switch", false))
|
||||
print("GameWorld: Door has requires_enemies: ", door_data.get("requires_enemies", false))
|
||||
print("GameWorld: Door has floor_switch_position: ", "floor_switch_position" in door_data)
|
||||
print("GameWorld: Door has enemy_spawners: ", "enemy_spawners" in door_data)
|
||||
# Remove the door since it's invalid - it was created without puzzle elements
|
||||
door.queue_free()
|
||||
print("GameWorld: Removed invalid blocking door ", door.name, " - it had no puzzle elements!")
|
||||
continue # Skip to next door
|
||||
|
||||
print("GameWorld: Spawned ", blocking_doors.size(), " blocking doors")
|
||||
|
||||
func _spawn_floor_switch(i_position: Vector2, required_weight: float, tile_x: int, tile_y: int, switch_type: String = "walk", switch_room: Dictionary = {}) -> Node:
|
||||
# Spawn a floor switch
|
||||
# switch_type: "walk" for walk-on switch (weight 1), "pillar" for pillar switch (weight 5)
|
||||
var switch_script = load("res://scripts/floor_switch.gd")
|
||||
if not switch_script:
|
||||
push_error("ERROR: Could not load floor_switch script!")
|
||||
return null
|
||||
|
||||
var switch = Area2D.new()
|
||||
switch.set_script(switch_script)
|
||||
switch.name = "FloorSwitch_%d_%d" % [tile_x, tile_y]
|
||||
switch.add_to_group("floor_switch")
|
||||
|
||||
# Set properties
|
||||
switch.switch_type = switch_type if switch_type == "walk" or switch_type == "pillar" else "walk"
|
||||
switch.required_weight = required_weight # Will be overridden in _ready() based on switch_type, but set it here too
|
||||
switch.switch_tile_position = Vector2i(tile_x, tile_y)
|
||||
|
||||
# Create collision shape
|
||||
var collision_shape = CollisionShape2D.new()
|
||||
var circle_shape = CircleShape2D.new()
|
||||
circle_shape.radius = 8.0 # 16 pixel diameter
|
||||
collision_shape.shape = circle_shape
|
||||
switch.add_child(collision_shape)
|
||||
|
||||
# Set multiplayer authority
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
switch.set_multiplayer_authority(1)
|
||||
|
||||
# CRITICAL: Store switch_room metadata BEFORE adding to scene
|
||||
# This ensures switches can be matched to doors in the same room
|
||||
if switch_room and not switch_room.is_empty():
|
||||
switch.set_meta("switch_room", switch_room)
|
||||
print("GameWorld: Set switch_room metadata for switch - room (", switch_room.x, ", ", switch_room.y, ")")
|
||||
else:
|
||||
push_warning("GameWorld: WARNING - Spawning switch without switch_room metadata! This may cause cross-room connections!")
|
||||
|
||||
# Add to scene
|
||||
var entities_node = get_node_or_null("Entities")
|
||||
if entities_node:
|
||||
entities_node.add_child(switch)
|
||||
switch.global_position = i_position
|
||||
|
||||
# Update tilemap to show switch tile (initial inactive state)
|
||||
if dungeon_tilemap_layer:
|
||||
var initial_tile: Vector2i
|
||||
if switch_type == "pillar":
|
||||
initial_tile = Vector2i(16, 9) # Pillar switch inactive
|
||||
else:
|
||||
initial_tile = Vector2i(11, 9) # Walk-on switch inactive
|
||||
dungeon_tilemap_layer.set_cell(Vector2i(tile_x, tile_y), 0, initial_tile)
|
||||
|
||||
print("GameWorld: Spawned ", switch_type, " floor switch at ", i_position, " tile (", tile_x, ", ", tile_y, "), room: (", switch_room.get("x", "?") if switch_room and not switch_room.is_empty() else "?", ", ", switch_room.get("y", "?") if switch_room and not switch_room.is_empty() else "?", ")")
|
||||
return switch
|
||||
|
||||
return null
|
||||
|
||||
func _spawn_enemy_spawner(i_position: Vector2, room: Dictionary, spawner_data: Dictionary = {}) -> Node:
|
||||
# Spawn an enemy spawner for a blocking room
|
||||
var spawner_script = load("res://scripts/enemy_spawner.gd")
|
||||
if not spawner_script:
|
||||
push_error("ERROR: Could not load enemy_spawner script!")
|
||||
return null
|
||||
|
||||
var spawner = Node2D.new()
|
||||
spawner.set_script(spawner_script)
|
||||
spawner.name = "EnemySpawner_%d_%d" % [room.x, room.y] if room and not room.is_empty() else "EnemySpawner_%d_%d" % [int(i_position.x), int(i_position.y)]
|
||||
spawner.add_to_group("enemy_spawner")
|
||||
|
||||
# Set spawner properties - IMPORTANT: spawn_on_ready = false so enemies only spawn when player enters room
|
||||
spawner.spawn_on_ready = false # Don't spawn on ready - wait for room trigger
|
||||
spawner.respawn_time = 0.0 # Don't respawn - enemies spawn once when entering room
|
||||
spawner.max_enemies = 1 # One enemy per spawner
|
||||
|
||||
# Check if this spawner should be destroyed after spawning once
|
||||
if spawner_data.has("spawn_once") and spawner_data.spawn_once:
|
||||
spawner.set_meta("spawn_once", true) # Mark spawner for destruction after spawning
|
||||
|
||||
# Set enemy scenes (use default enemy types)
|
||||
# enemy_scenes is Array[PackedScene], so we need to properly type it
|
||||
var enemy_scenes: Array[PackedScene] = []
|
||||
var scene_paths = [
|
||||
"res://scenes/enemy_rat.tscn",
|
||||
"res://scenes/enemy_humanoid.tscn",
|
||||
"res://scenes/enemy_slime.tscn",
|
||||
"res://scenes/enemy_bat.tscn"
|
||||
]
|
||||
|
||||
# Load scenes and add to typed array
|
||||
for path in scene_paths:
|
||||
var scene = load(path) as PackedScene
|
||||
if scene:
|
||||
enemy_scenes.append(scene)
|
||||
|
||||
spawner.enemy_scenes = enemy_scenes
|
||||
|
||||
# Set multiplayer authority
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
spawner.set_multiplayer_authority(1)
|
||||
|
||||
# Store room reference
|
||||
if room and not room.is_empty():
|
||||
spawner.set_meta("room", room)
|
||||
|
||||
# Add to scene
|
||||
var entities_node = get_node_or_null("Entities")
|
||||
if entities_node:
|
||||
entities_node.add_child(spawner)
|
||||
spawner.global_position = i_position
|
||||
print("GameWorld: ✓✓✓ Successfully spawned enemy spawner '", spawner.name, "' at ", i_position, " for room at (", room.x if room and not room.is_empty() else "unknown", ", ", room.y if room and not room.is_empty() else "unknown", ")")
|
||||
print("GameWorld: Spawner has room metadata: ", spawner.has_meta("room"))
|
||||
if spawner.has_meta("room"):
|
||||
var spawner_room = spawner.get_meta("room")
|
||||
print("GameWorld: Spawner room metadata: (", spawner_room.x if spawner_room and not spawner_room.is_empty() else "none", ", ", spawner_room.y if spawner_room and not spawner_room.is_empty() else "none", ", ", spawner_room.w if spawner_room and not spawner_room.is_empty() else "none", "x", spawner_room.h if spawner_room and not spawner_room.is_empty() else "none", ")")
|
||||
print("GameWorld: Spawner in group 'enemy_spawner': ", spawner.is_in_group("enemy_spawner"))
|
||||
print("GameWorld: Spawner enemy_scenes.size(): ", spawner.enemy_scenes.size() if "enemy_scenes" in spawner else "N/A")
|
||||
return spawner
|
||||
|
||||
return null
|
||||
|
||||
func _spawn_room_triggers():
|
||||
# Spawn room trigger areas for all rooms
|
||||
if dungeon_data.is_empty() or not dungeon_data.has("rooms"):
|
||||
return
|
||||
|
||||
var rooms = dungeon_data.rooms
|
||||
if rooms == null or not rooms is Array:
|
||||
return
|
||||
|
||||
var trigger_script = load("res://scripts/room_trigger.gd")
|
||||
if not trigger_script:
|
||||
push_error("ERROR: Could not load room_trigger script!")
|
||||
return
|
||||
|
||||
var entities_node = get_node_or_null("Entities")
|
||||
if not entities_node:
|
||||
push_error("ERROR: Could not find Entities node!")
|
||||
return
|
||||
|
||||
print("GameWorld: Spawning ", rooms.size(), " room triggers")
|
||||
|
||||
for i in range(rooms.size()):
|
||||
var room = rooms[i]
|
||||
if not room is Dictionary:
|
||||
continue
|
||||
|
||||
var trigger = Area2D.new()
|
||||
trigger.set_script(trigger_script)
|
||||
trigger.name = "RoomTrigger_%d" % i
|
||||
trigger.add_to_group("room_trigger")
|
||||
|
||||
# Set room data
|
||||
trigger.room = room
|
||||
|
||||
# Create collision shape covering ONLY the room interior (no overlap with adjacent rooms)
|
||||
var collision_shape = CollisionShape2D.new()
|
||||
var rect_shape = RectangleShape2D.new()
|
||||
var tile_size = 16
|
||||
# Room interior is from room.x + 2 to room.x + room.w - 2 (excluding 2-tile walls)
|
||||
# This ensures the trigger only covers THIS room, not adjacent rooms or doorways
|
||||
var room_world_x = (room.x + 2) * tile_size
|
||||
var room_world_y = (room.y + 2) * tile_size
|
||||
var room_world_w = (room.w - 4) * tile_size # Width excluding 2-tile walls on each side
|
||||
var room_world_h = (room.h - 4) * tile_size # Height excluding 2-tile walls on each side
|
||||
rect_shape.size = Vector2(room_world_w, room_world_h)
|
||||
collision_shape.shape = rect_shape
|
||||
# Position collision shape at center of room (relative to Area2D)
|
||||
collision_shape.position = Vector2(room_world_w / 2.0, room_world_h / 2.0)
|
||||
trigger.add_child(collision_shape)
|
||||
|
||||
# Set Area2D global position to the top-left corner of the room interior
|
||||
# This ensures the trigger ONLY covers this specific room
|
||||
trigger.global_position = Vector2(room_world_x, room_world_y)
|
||||
|
||||
# Set multiplayer authority
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
trigger.set_multiplayer_authority(1)
|
||||
|
||||
# Add to scene
|
||||
entities_node.add_child(trigger)
|
||||
|
||||
print("GameWorld: Spawned ", rooms.size(), " room triggers")
|
||||
|
||||
func _place_key_in_room(room: Dictionary):
|
||||
# Place a key in the specified room (as loot)
|
||||
if room.is_empty():
|
||||
return
|
||||
|
||||
var loot_scene = preload("res://scenes/loot.tscn")
|
||||
if not loot_scene:
|
||||
return
|
||||
|
||||
var entities_node = get_node_or_null("Entities")
|
||||
if not entities_node:
|
||||
return
|
||||
|
||||
# Find a valid floor position in the room
|
||||
var tile_size = 16
|
||||
var valid_positions = []
|
||||
|
||||
# Room interior is from room.x + 2 to room.x + room.w - 2
|
||||
for x in range(room.x + 2, room.x + room.w - 2):
|
||||
for y in range(room.y + 2, room.y + room.h - 2):
|
||||
if x >= 0 and x < dungeon_data.map_size.x and y >= 0 and y < dungeon_data.map_size.y:
|
||||
if dungeon_data.grid[x][y] == 1: # Floor
|
||||
var world_x = x * tile_size + tile_size / 2.0
|
||||
var world_y = y * tile_size + tile_size / 2.0
|
||||
valid_positions.append(Vector2(world_x, world_y))
|
||||
|
||||
if valid_positions.size() > 0:
|
||||
# Pick a random position
|
||||
var rng = RandomNumberGenerator.new()
|
||||
rng.randomize()
|
||||
var key_pos = valid_positions[rng.randi() % valid_positions.size()]
|
||||
|
||||
# Spawn key loot
|
||||
var key_loot = loot_scene.instantiate()
|
||||
key_loot.name = "KeyLoot_%d_%d" % [int(key_pos.x), int(key_pos.y)]
|
||||
key_loot.loot_type = key_loot.LootType.KEY
|
||||
|
||||
# Set multiplayer authority
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
key_loot.set_multiplayer_authority(1)
|
||||
|
||||
entities_node.add_child(key_loot)
|
||||
key_loot.global_position = key_pos
|
||||
|
||||
print("GameWorld: Placed key in room at ", key_pos)
|
||||
|
||||
func _place_pillar_in_room(room: Dictionary, switch_position: Vector2):
|
||||
# Place a pillar in the specified room (needed for pillar switches)
|
||||
if room.is_empty():
|
||||
return
|
||||
|
||||
var interactable_object_scene = preload("res://scenes/interactable_object.tscn")
|
||||
if not interactable_object_scene:
|
||||
push_error("ERROR: Could not load interactable_object scene for pillar!")
|
||||
return
|
||||
|
||||
var entities_node = get_node_or_null("Entities")
|
||||
if not entities_node:
|
||||
push_error("ERROR: Could not find Entities node for pillar placement!")
|
||||
return
|
||||
|
||||
# Find a valid floor position in the room (away from the switch)
|
||||
var tile_size = 16
|
||||
var valid_positions = []
|
||||
|
||||
# Room interior is from room.x + 2 to room.x + room.w - 2 (excluding 2-tile walls)
|
||||
# CRITICAL: Also exclude last row/column of floor tiles to prevent objects from spawning in walls
|
||||
# Objects are 16x16, so we need at least 1 tile buffer from walls
|
||||
# Floor tiles are at: room.x+2, room.x+3, ..., room.x+room.w-3 (last floor tile before wall)
|
||||
# To exclude last tile, we want: room.x+2, room.x+3, ..., room.x+room.w-4
|
||||
var min_x = room.x + 2
|
||||
var max_x = room.x + room.w - 4 # Exclude last 2 floor tiles (room.w-3 is last floor, room.w-4 is second-to-last)
|
||||
var min_y = room.y + 2
|
||||
var max_y = room.y + room.h - 4 # Exclude last 2 floor tiles (room.h-3 is last floor, room.h-4 is second-to-last)
|
||||
|
||||
for x in range(min_x, max_x + 1): # +1 because range is exclusive at end
|
||||
for y in range(min_y, max_y + 1):
|
||||
if x >= 0 and x < dungeon_data.map_size.x and y >= 0 and y < dungeon_data.map_size.y:
|
||||
if dungeon_data.grid[x][y] == 1: # Floor
|
||||
# CRITICAL: Interactable objects are 16x16 pixels with origin at (0,0) (top-left)
|
||||
# To center a 16x16 sprite in a 16x16 tile, we need to offset by 8 pixels into the tile
|
||||
# Tile (x,y) spans from (x*16, y*16) to ((x+1)*16, (y+1)*16)
|
||||
# Position sprite 8 pixels into the tile from top-left: (x*16 + 8, y*16 + 8)
|
||||
var world_x = x * tile_size + 8
|
||||
var world_y = y * tile_size + 8
|
||||
var world_pos = Vector2(world_x, world_y)
|
||||
|
||||
# Ensure pillar is at least 2 tiles away from the switch
|
||||
var distance_to_switch = world_pos.distance_to(switch_position)
|
||||
if distance_to_switch >= tile_size * 2: # At least 2 tiles away
|
||||
valid_positions.append(world_pos)
|
||||
|
||||
if valid_positions.size() > 0:
|
||||
# Pick a random position
|
||||
var rng = RandomNumberGenerator.new()
|
||||
rng.randomize()
|
||||
var pillar_pos = valid_positions[rng.randi() % valid_positions.size()]
|
||||
|
||||
# Spawn pillar interactable object
|
||||
var pillar = interactable_object_scene.instantiate()
|
||||
pillar.name = "Pillar_%d_%d" % [int(pillar_pos.x), int(pillar_pos.y)]
|
||||
pillar.set_meta("dungeon_spawned", true)
|
||||
pillar.set_meta("room", room)
|
||||
|
||||
# Set multiplayer authority
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
pillar.set_multiplayer_authority(1)
|
||||
|
||||
# Add to scene tree
|
||||
entities_node.add_child(pillar)
|
||||
pillar.global_position = pillar_pos
|
||||
|
||||
# Call setup function to configure as pillar
|
||||
if pillar.has_method("setup_pillar"):
|
||||
pillar.call("setup_pillar")
|
||||
else:
|
||||
push_error("ERROR: Pillar does not have setup_pillar method!")
|
||||
|
||||
# Add to group for easy access
|
||||
pillar.add_to_group("interactable_object")
|
||||
|
||||
print("GameWorld: Placed pillar in room at ", pillar_pos, " (switch at ", switch_position, ")")
|
||||
else:
|
||||
push_warning("GameWorld: Could not find valid position for pillar in room! Room might be too small.")
|
||||
|
||||
func _connect_door_to_room_trigger(door: Node):
|
||||
# Connect a door to its room trigger area
|
||||
# blocking_room is a variable in door.gd, so it should exist
|
||||
var blocking_room = door.blocking_room
|
||||
if not blocking_room or blocking_room.is_empty():
|
||||
return
|
||||
|
||||
# Find the room trigger for this room
|
||||
for trigger in get_tree().get_nodes_in_group("room_trigger"):
|
||||
if is_instance_valid(trigger):
|
||||
# room is a variable in room_trigger.gd, compare by values
|
||||
var trigger_room = trigger.room
|
||||
if trigger_room and not trigger_room.is_empty() and \
|
||||
trigger_room.x == blocking_room.x and trigger_room.y == blocking_room.y and \
|
||||
trigger_room.w == blocking_room.w and trigger_room.h == blocking_room.h:
|
||||
# Connect door to trigger
|
||||
door.room_trigger_area = trigger
|
||||
# Add door to trigger's doors list (doors_in_room is a variable in room_trigger.gd)
|
||||
trigger.doors_in_room.append(door)
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user