delete files in nickes
This commit is contained in:
@@ -86,7 +86,12 @@ func _on_player_connected(peer_id: int, player_info: Dictionary):
|
||||
_sync_spawn_player.rpc(peer_id, player_info.local_player_count)
|
||||
|
||||
# Sync existing enemies (from spawners) to the new client
|
||||
_sync_existing_enemies_to_client(peer_id)
|
||||
# Wait a bit after dungeon sync to ensure spawners are spawned first
|
||||
call_deferred("_sync_existing_enemies_to_client", peer_id)
|
||||
|
||||
# Sync existing chest open states to the new client
|
||||
# Wait a bit after dungeon sync to ensure objects are spawned first
|
||||
call_deferred("_sync_existing_chest_states_to_client", peer_id)
|
||||
|
||||
# Note: Dungeon-spawned enemies are already synced via _sync_dungeon RPC
|
||||
# which includes dungeon_data.enemies and calls _spawn_enemies() on the client.
|
||||
@@ -94,12 +99,10 @@ func _on_player_connected(peer_id: int, player_info: Dictionary):
|
||||
|
||||
# Note: Interactable objects are also synced via _sync_dungeon RPC
|
||||
# which includes dungeon_data.interactable_objects and calls _spawn_interactable_objects() on the client.
|
||||
|
||||
# Note: Interactable objects are also synced via _sync_dungeon RPC
|
||||
# which includes dungeon_data.interactable_objects and calls _spawn_interactable_objects() on the client.
|
||||
# However, chest open states need to be synced separately since they change during gameplay.
|
||||
|
||||
# Sync existing torches to the new client
|
||||
_sync_existing_torches_to_client(peer_id)
|
||||
call_deferred("_sync_existing_torches_to_client", peer_id)
|
||||
else:
|
||||
# Clients spawn directly when they receive this signal
|
||||
print("GameWorld: Client spawning players for peer ", peer_id)
|
||||
@@ -107,21 +110,31 @@ func _on_player_connected(peer_id: int, player_info: Dictionary):
|
||||
|
||||
func _sync_existing_enemies_to_client(client_peer_id: int):
|
||||
# Find all enemy spawners and sync their spawned enemies to the new client
|
||||
# Spawners are children of the Entities node, not GameWorld directly
|
||||
var spawners = []
|
||||
for child in get_children():
|
||||
if child.has_method("get_spawned_enemy_positions") and child.has_method("spawn_enemy_at_position"):
|
||||
spawners.append(child)
|
||||
var entities_node = get_node_or_null("Entities")
|
||||
if entities_node:
|
||||
# Find spawners in Entities node
|
||||
for child in entities_node.get_children():
|
||||
if child.is_in_group("enemy_spawner") or (child.has_method("get_spawned_enemy_positions") and child.has_method("spawn_enemy_at_position")):
|
||||
spawners.append(child)
|
||||
else:
|
||||
# Fallback: search all children (shouldn't happen, but just in case)
|
||||
for child in get_children():
|
||||
if child.is_in_group("enemy_spawner") or (child.has_method("get_spawned_enemy_positions") and child.has_method("spawn_enemy_at_position")):
|
||||
spawners.append(child)
|
||||
|
||||
print("GameWorld: Syncing existing enemies to client ", client_peer_id, " from ", spawners.size(), " spawners")
|
||||
|
||||
for spawner in spawners:
|
||||
var enemy_data = spawner.get_spawned_enemy_positions()
|
||||
for data in enemy_data:
|
||||
# Use the stored scene_index for each enemy
|
||||
# Use the stored scene_index and humanoid_type for each enemy
|
||||
var pos = data.position
|
||||
var scene_index = data.scene_index if "scene_index" in data else -1
|
||||
_sync_enemy_spawn.rpc_id(client_peer_id, spawner.name, pos, scene_index)
|
||||
print("GameWorld: Sent enemy spawn sync to client ", client_peer_id, ": spawner=", spawner.name, " pos=", pos, " scene_index=", scene_index)
|
||||
var humanoid_type = data.humanoid_type if "humanoid_type" in data else -1
|
||||
_sync_enemy_spawn.rpc_id(client_peer_id, spawner.name, pos, scene_index, humanoid_type)
|
||||
print("GameWorld: Sent enemy spawn sync to client ", client_peer_id, ": spawner=", spawner.name, " pos=", pos, " scene_index=", scene_index, " humanoid_type=", humanoid_type)
|
||||
|
||||
func _on_player_disconnected(peer_id: int):
|
||||
print("GameWorld: Player disconnected - ", peer_id)
|
||||
@@ -169,23 +182,53 @@ func _reset_server_players_ready_flag():
|
||||
print("GameWorld: Reset all_clients_ready for server player ", child.name)
|
||||
|
||||
@rpc("authority", "reliable")
|
||||
func _sync_enemy_spawn(spawner_name: String, spawn_position: Vector2, scene_index: int = -1):
|
||||
func _sync_smoke_puffs(_spawner_name: String, puff_positions: Array):
|
||||
# Clients spawn smoke puffs when server tells them to
|
||||
if not multiplayer.is_server():
|
||||
var smoke_puff_scene = preload("res://scenes/smoke_puff.tscn")
|
||||
if not smoke_puff_scene:
|
||||
return
|
||||
|
||||
var entities_node = get_node_or_null("Entities")
|
||||
if not entities_node:
|
||||
return
|
||||
|
||||
for pos in puff_positions:
|
||||
if pos is Vector2:
|
||||
var puff = smoke_puff_scene.instantiate()
|
||||
if puff:
|
||||
puff.global_position = pos
|
||||
puff.z_index = 10
|
||||
if puff.has_node("Sprite2D"):
|
||||
puff.get_node("Sprite2D").z_index = 10
|
||||
entities_node.add_child(puff)
|
||||
|
||||
@rpc("authority", "reliable")
|
||||
func _sync_enemy_spawn(spawner_name: String, spawn_position: Vector2, scene_index: int = -1, humanoid_type: int = -1):
|
||||
# Clients spawn enemy when server tells them to
|
||||
if not multiplayer.is_server():
|
||||
print("GameWorld: Client received RPC to spawn enemy at spawner: ", spawner_name, " position: ", spawn_position, " scene_index: ", scene_index)
|
||||
print("GameWorld: Client received RPC to spawn enemy at spawner: ", spawner_name, " position: ", spawn_position, " scene_index: ", scene_index, " humanoid_type: ", humanoid_type)
|
||||
|
||||
# Find the spawner node by name (it's a direct child of GameWorld)
|
||||
var spawner = get_node_or_null(spawner_name)
|
||||
# Find the spawner node by name (spawners are children of Entities node, not GameWorld)
|
||||
var spawner = null
|
||||
var entities_node = get_node_or_null("Entities")
|
||||
if entities_node:
|
||||
spawner = entities_node.get_node_or_null(spawner_name)
|
||||
|
||||
# Fallback: try as direct child of GameWorld (for backwards compatibility)
|
||||
if not spawner:
|
||||
push_error("ERROR: Could not find spawner with name: ", spawner_name)
|
||||
spawner = get_node_or_null(spawner_name)
|
||||
|
||||
if not spawner:
|
||||
push_error("ERROR: Could not find spawner with name: ", spawner_name, " in Entities node or GameWorld")
|
||||
return
|
||||
|
||||
if not spawner.has_method("spawn_enemy_at_position"):
|
||||
push_error("ERROR: Spawner does not have spawn_enemy_at_position method!")
|
||||
return
|
||||
|
||||
# Call spawn method on the spawner with scene index
|
||||
spawner.spawn_enemy_at_position(spawn_position, scene_index)
|
||||
# Call spawn method on the spawner with scene index and humanoid_type
|
||||
spawner.spawn_enemy_at_position(spawn_position, scene_index, humanoid_type)
|
||||
|
||||
# Loot ID counter (server only)
|
||||
var loot_id_counter: int = 0
|
||||
@@ -334,19 +377,18 @@ func _request_loot_pickup(loot_id: int, loot_position: Vector2, player_peer_id:
|
||||
print("GameWorld: Could not find loot for pickup request: id=", loot_id, " pos=", loot_position)
|
||||
|
||||
@rpc("authority", "reliable")
|
||||
func _sync_show_level_complete(enemies_defeated: int, times_downed: int, exp_collected: float, coins_collected: int):
|
||||
func _sync_show_level_complete(level_time: float):
|
||||
# Clients receive level complete UI sync from server
|
||||
if multiplayer.is_server():
|
||||
return # Server ignores this (it's the sender)
|
||||
|
||||
# Update stats before showing
|
||||
level_enemies_defeated = enemies_defeated
|
||||
level_times_downed = times_downed
|
||||
level_exp_collected = exp_collected
|
||||
level_coins_collected = coins_collected
|
||||
# Stop HUD timer when level completes (on clients too)
|
||||
var hud = get_node_or_null("IngameHUD")
|
||||
if hud and hud.has_method("stop_timer"):
|
||||
hud.stop_timer()
|
||||
|
||||
# Show level complete UI
|
||||
_show_level_complete_ui()
|
||||
# Show level complete UI (each client will show their own local player's stats)
|
||||
_show_level_complete_ui(level_time)
|
||||
|
||||
@rpc("authority", "reliable")
|
||||
func _sync_hide_level_complete():
|
||||
@@ -501,6 +543,9 @@ func _generate_dungeon():
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
_sync_show_level_number.rpc(current_level)
|
||||
|
||||
# Load HUD after dungeon generation completes (non-blocking)
|
||||
call_deferred("_load_hud")
|
||||
|
||||
# Sync dungeon to all clients
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
# Get host's current room for spawning new players near host
|
||||
@@ -513,6 +558,8 @@ func _generate_dungeon():
|
||||
print("GameWorld: WARNING: Server dungeon_data has NO 'enemies' key before sync!")
|
||||
|
||||
_sync_dungeon.rpc(dungeon_data, dungeon_seed, current_level, host_room)
|
||||
|
||||
print("GameWorld: Dungeon generation completed successfully")
|
||||
|
||||
func _render_dungeon():
|
||||
if dungeon_data.is_empty():
|
||||
@@ -1083,6 +1130,10 @@ func _spawn_enemies():
|
||||
if "damage" in enemy_data:
|
||||
enemy.damage = enemy_data.damage
|
||||
|
||||
# If it's a humanoid enemy, set the humanoid_type
|
||||
if enemy_type.ends_with("enemy_humanoid.tscn") and "humanoid_type" in enemy_data:
|
||||
enemy.humanoid_type = enemy_data.humanoid_type
|
||||
|
||||
# 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)
|
||||
@@ -1156,7 +1207,7 @@ func _spawn_interactable_objects():
|
||||
|
||||
print("GameWorld: Spawning ", objects.size(), " interactable objects (is_server: ", is_server, ")")
|
||||
|
||||
var interactable_object_scene = preload("res://scenes/interactable_object.tscn")
|
||||
var interactable_object_scene = load("res://scenes/interactable_object.tscn")
|
||||
if not interactable_object_scene:
|
||||
push_error("ERROR: Could not load interactable_object scene!")
|
||||
return
|
||||
@@ -1290,7 +1341,10 @@ func _sync_dungeon_enemy_spawn(enemy_data: Dictionary):
|
||||
# Set enemy stats BEFORE adding to scene
|
||||
if "max_health" in enemy_data:
|
||||
enemy.max_health = enemy_data.max_health
|
||||
enemy.current_health = enemy_data.max_health
|
||||
|
||||
# If it's a humanoid enemy, set the humanoid_type
|
||||
if enemy_type.ends_with("enemy_humanoid.tscn") and "humanoid_type" in enemy_data:
|
||||
enemy.humanoid_type = enemy_data.humanoid_type
|
||||
if "move_speed" in enemy_data:
|
||||
enemy.move_speed = enemy_data.move_speed
|
||||
if "damage" in enemy_data:
|
||||
@@ -1319,6 +1373,42 @@ func _sync_dungeon_enemy_spawn(enemy_data: Dictionary):
|
||||
|
||||
print("GameWorld: Client spawned dungeon enemy via RPC: ", enemy.name, " at ", enemy_data.position, " (type: ", enemy_type, ") authority: ", enemy.get_multiplayer_authority())
|
||||
|
||||
func _sync_existing_chest_states_to_client(client_peer_id: int):
|
||||
# Sync chest open states to new client
|
||||
var entities_node = get_node_or_null("Entities")
|
||||
if not entities_node:
|
||||
return
|
||||
|
||||
var opened_chest_count = 0
|
||||
for child in entities_node.get_children():
|
||||
if child.is_in_group("interactable_object"):
|
||||
if "object_type" in child and child.object_type == "Chest":
|
||||
if "is_chest_opened" in child and child.is_chest_opened:
|
||||
# Found an opened chest - sync it to the client
|
||||
var obj_name = child.name
|
||||
_sync_chest_state.rpc_id(client_peer_id, obj_name, true)
|
||||
opened_chest_count += 1
|
||||
print("GameWorld: Syncing opened chest ", obj_name, " to client ", client_peer_id)
|
||||
|
||||
print("GameWorld: Synced ", opened_chest_count, " opened chests to client ", client_peer_id)
|
||||
|
||||
@rpc("authority", "reliable")
|
||||
func _sync_chest_state(obj_name: String, is_opened: bool):
|
||||
# Client receives chest state sync
|
||||
if not multiplayer.is_server():
|
||||
var entities_node = get_node_or_null("Entities")
|
||||
if entities_node:
|
||||
var chest = entities_node.get_node_or_null(obj_name)
|
||||
if chest and "is_chest_opened" in chest:
|
||||
chest.is_chest_opened = is_opened
|
||||
if chest.has_method("_sync_chest_open"):
|
||||
# Call the sync function to update visuals (loot type doesn't matter for visual sync)
|
||||
chest._sync_chest_open("coin")
|
||||
elif "sprite" in chest and "chest_opened_frame" in chest:
|
||||
if chest.sprite and chest.chest_opened_frame >= 0:
|
||||
chest.sprite.frame = chest.chest_opened_frame
|
||||
print("GameWorld: Client received chest state sync for ", obj_name, " - opened: ", is_opened)
|
||||
|
||||
func _sync_existing_torches_to_client(client_peer_id: int):
|
||||
# Sync existing torches to newly connected client
|
||||
if dungeon_data.is_empty() or not dungeon_data.has("torches"):
|
||||
@@ -1461,17 +1551,23 @@ func _on_player_reached_stairs(player: Node):
|
||||
if child.is_in_group("player") and child.has_method("_force_drop_held_object"):
|
||||
child._force_drop_held_object()
|
||||
|
||||
# Collect stats from all players
|
||||
_collect_level_stats()
|
||||
# Stop HUD timer when level completes
|
||||
var hud = get_node_or_null("IngameHUD")
|
||||
var level_time: float = 0.0
|
||||
if hud and hud.has_method("stop_timer"):
|
||||
hud.stop_timer()
|
||||
# Get the level time before stopping
|
||||
if hud.has_method("get_level_time"):
|
||||
level_time = hud.get_level_time()
|
||||
|
||||
# Fade out player
|
||||
_fade_out_player(player)
|
||||
|
||||
# Show level complete UI (server and clients)
|
||||
_show_level_complete_ui()
|
||||
# Sync to all clients
|
||||
# Show level complete UI (server and clients) with per-player stats
|
||||
_show_level_complete_ui(level_time)
|
||||
# Sync to all clients (each client will show their own local player's stats)
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
_sync_show_level_complete.rpc(level_enemies_defeated, level_times_downed, level_exp_collected, level_coins_collected)
|
||||
_sync_show_level_complete.rpc(level_time)
|
||||
|
||||
# After delay, hide UI and generate new level
|
||||
await get_tree().create_timer(5.0).timeout # Show stats for 5 seconds
|
||||
@@ -1515,6 +1611,11 @@ func _on_player_reached_stairs(player: Node):
|
||||
print("GameWorld: Syncing level number ", current_level, " to all clients")
|
||||
_sync_show_level_number.rpc(current_level)
|
||||
|
||||
# Restart HUD timer for new level
|
||||
hud = get_node_or_null("IngameHUD")
|
||||
if hud and hud.has_method("start_timer"):
|
||||
hud.start_timer()
|
||||
|
||||
# Move all players to start room (server side)
|
||||
_move_all_players_to_start_room()
|
||||
|
||||
@@ -1537,32 +1638,40 @@ func _on_player_reached_stairs(player: Node):
|
||||
|
||||
_sync_dungeon.rpc(dungeon_data, dungeon_seed, current_level, start_room)
|
||||
|
||||
func _collect_level_stats():
|
||||
# Reset stats
|
||||
level_enemies_defeated = 0
|
||||
level_times_downed = 0
|
||||
level_exp_collected = 0.0
|
||||
level_coins_collected = 0
|
||||
func _get_local_player_stats() -> Dictionary:
|
||||
# Get stats for the local player (for level complete screen)
|
||||
# Returns a dictionary with: enemies_defeated, coins_collected
|
||||
var stats = {
|
||||
"enemies_defeated": 0,
|
||||
"coins_collected": 0
|
||||
}
|
||||
|
||||
# Collect from all players
|
||||
# Find local player(s) - in multiplayer, find player with authority matching this client
|
||||
# In single-player, just use the first player
|
||||
var players = get_tree().get_nodes_in_group("player")
|
||||
for player in players:
|
||||
if player.character_stats:
|
||||
# Count enemies defeated (kills)
|
||||
if "kills" in player.character_stats:
|
||||
level_enemies_defeated += player.character_stats.kills
|
||||
|
||||
# Count times downed (deaths)
|
||||
if "deaths" in player.character_stats:
|
||||
level_times_downed += player.character_stats.deaths
|
||||
|
||||
# Collect exp
|
||||
if "xp" in player.character_stats:
|
||||
level_exp_collected += player.character_stats.xp
|
||||
|
||||
# Collect coins
|
||||
if "coin" in player.character_stats:
|
||||
level_coins_collected += player.character_stats.coin
|
||||
var local_player = null
|
||||
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
# Multiplayer: find player with matching authority
|
||||
for player in players:
|
||||
if player.has_method("is_multiplayer_authority") and player.is_multiplayer_authority():
|
||||
local_player = player
|
||||
break
|
||||
else:
|
||||
# Single-player: use first player
|
||||
if players.size() > 0:
|
||||
local_player = players[0]
|
||||
|
||||
if local_player and local_player.character_stats:
|
||||
# Get enemies defeated (kills)
|
||||
if "kills" in local_player.character_stats:
|
||||
stats.enemies_defeated = local_player.character_stats.kills
|
||||
|
||||
# Get coins collected
|
||||
if "coin" in local_player.character_stats:
|
||||
stats.coins_collected = local_player.character_stats.coin
|
||||
|
||||
return stats
|
||||
|
||||
func _fade_out_player(player: Node):
|
||||
# Fade out all sprite layers
|
||||
@@ -1637,7 +1746,7 @@ func _fade_in_player(player: Node):
|
||||
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():
|
||||
func _show_level_complete_ui(level_time: float = 0.0):
|
||||
# Create or show level complete UI
|
||||
var level_complete_ui = get_node_or_null("LevelCompleteUI")
|
||||
if not level_complete_ui:
|
||||
@@ -1659,11 +1768,17 @@ func _show_level_complete_ui():
|
||||
|
||||
if level_complete_ui:
|
||||
if level_complete_ui.has_method("show_stats"):
|
||||
# Get stats for local player
|
||||
var local_stats = _get_local_player_stats()
|
||||
# Use current_level directly (matches what HUD shows)
|
||||
# current_level hasn't been incremented yet when this is called
|
||||
level_complete_ui.show_stats(
|
||||
level_enemies_defeated,
|
||||
level_times_downed,
|
||||
level_exp_collected,
|
||||
level_coins_collected
|
||||
local_stats.enemies_defeated,
|
||||
0, # times_downed - not shown per user request
|
||||
0.0, # exp_collected - not shown per user request
|
||||
local_stats.coins_collected,
|
||||
level_time,
|
||||
current_level
|
||||
)
|
||||
|
||||
func _show_level_number():
|
||||
@@ -1699,47 +1814,167 @@ func _show_level_number():
|
||||
else:
|
||||
print("GameWorld: ERROR - Could not create or find LevelTextUI!")
|
||||
|
||||
func _load_hud():
|
||||
# Check if HUD already exists - only load it once
|
||||
var existing_hud = get_node_or_null("IngameHUD")
|
||||
if existing_hud and is_instance_valid(existing_hud):
|
||||
print("GameWorld: HUD already exists, skipping load (will just reset timer)")
|
||||
# Reset timer for new level if method exists
|
||||
if existing_hud.has_method("reset_level_timer"):
|
||||
existing_hud.reset_level_timer()
|
||||
return
|
||||
|
||||
# Load HUD dynamically to avoid scene loading errors
|
||||
# This is optional - don't crash if HUD scene doesn't exist or fails to load
|
||||
# Use a try-catch-like approach by checking for errors
|
||||
var hud_scene_path = "res://scenes/ingame_hud.tscn"
|
||||
|
||||
# Check if scene exists
|
||||
if not ResourceLoader.exists(hud_scene_path):
|
||||
print("GameWorld: HUD scene not found at ", hud_scene_path, " - HUD disabled")
|
||||
return
|
||||
|
||||
# Try to load the scene
|
||||
var hud_scene = load(hud_scene_path)
|
||||
if not hud_scene:
|
||||
print("GameWorld: Warning - Failed to load HUD scene from ", hud_scene_path)
|
||||
return
|
||||
|
||||
# Try to instantiate
|
||||
var hud = null
|
||||
if hud_scene.has_method("instantiate"):
|
||||
hud = hud_scene.instantiate()
|
||||
else:
|
||||
print("GameWorld: Warning - HUD scene is not a PackedScene")
|
||||
return
|
||||
|
||||
if not hud:
|
||||
print("GameWorld: Warning - Failed to instantiate HUD scene")
|
||||
return
|
||||
|
||||
# Add to scene tree
|
||||
hud.name = "IngameHUD"
|
||||
|
||||
# Ensure HUD is visible and on top layer
|
||||
hud.visible = true
|
||||
hud.layer = 100 # High layer to ensure HUD is on top of everything
|
||||
|
||||
add_child(hud)
|
||||
|
||||
# Reset timer if method exists
|
||||
if hud.has_method("reset_level_timer"):
|
||||
hud.reset_level_timer()
|
||||
|
||||
print("GameWorld: HUD loaded successfully and added to scene tree")
|
||||
print("GameWorld: HUD visible: ", hud.visible, ", layer: ", hud.layer)
|
||||
|
||||
func _initialize_hud():
|
||||
# Find or get the HUD and reset its level timer
|
||||
# This is optional - don't crash if HUD doesn't exist
|
||||
var hud = get_node_or_null("IngameHUD")
|
||||
if hud and is_instance_valid(hud) and hud.has_method("reset_level_timer"):
|
||||
hud.reset_level_timer()
|
||||
else:
|
||||
print("GameWorld: HUD not found or not ready - this is OK if HUD scene is missing")
|
||||
|
||||
func _create_level_complete_ui_programmatically() -> Node:
|
||||
# Create level complete UI programmatically
|
||||
var canvas_layer = CanvasLayer.new()
|
||||
canvas_layer.name = "LevelCompleteUI"
|
||||
add_child(canvas_layer)
|
||||
|
||||
# Load standard font (as FontFile)
|
||||
var standard_font = load("res://assets/fonts/standard_font.png") as FontFile
|
||||
if not standard_font:
|
||||
print("GameWorld: Warning - Could not load standard_font.png as FontFile")
|
||||
|
||||
# Create theme with standard font
|
||||
var theme = Theme.new()
|
||||
if standard_font:
|
||||
theme.default_font = standard_font
|
||||
theme.default_font_size = 10
|
||||
|
||||
var vbox = VBoxContainer.new()
|
||||
vbox.set_anchors_preset(Control.PRESET_CENTER)
|
||||
vbox.offset_top = -200 # Position a bit up from center
|
||||
vbox.theme = theme
|
||||
canvas_layer.add_child(vbox)
|
||||
|
||||
# Title
|
||||
# Center the VBoxContainer properly
|
||||
# Use PRESET_CENTER to anchor at center, then set offsets to center horizontally
|
||||
var screen_size = get_viewport().get_visible_rect().size
|
||||
vbox.set_anchors_preset(Control.PRESET_CENTER)
|
||||
# Set offsets so container is centered horizontally (equal left/right offsets)
|
||||
# and positioned vertically (offset from center)
|
||||
vbox.offset_left = - screen_size.x / 2
|
||||
vbox.offset_right = screen_size.x / 2
|
||||
vbox.offset_top = -200 # Position a bit up from center
|
||||
vbox.offset_bottom = screen_size.y / 2 - 200 # Balance to maintain vertical position
|
||||
vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL # Expand to fill available width
|
||||
|
||||
# Title - "LEVEL COMPLETE!" in large size
|
||||
var title = Label.new()
|
||||
title.name = "TitleLabel"
|
||||
title.text = "LEVEL COMPLETE!"
|
||||
title.add_theme_font_size_override("font_size", 48)
|
||||
title.theme = theme
|
||||
title.add_theme_font_size_override("font_size", 72) # Large size
|
||||
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL # Expand to fill available width
|
||||
vbox.add_child(title)
|
||||
|
||||
# Stats header - "stats" in smaller size
|
||||
var stats_header = Label.new()
|
||||
stats_header.name = "StatsHeaderLabel"
|
||||
stats_header.text = "stats"
|
||||
stats_header.theme = theme
|
||||
stats_header.add_theme_font_size_override("font_size", 32) # Smaller than title
|
||||
stats_header.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
stats_header.size_flags_horizontal = Control.SIZE_EXPAND_FILL # Expand to fill available width
|
||||
vbox.add_child(stats_header)
|
||||
|
||||
# Stats container
|
||||
var stats_container = VBoxContainer.new()
|
||||
stats_container.theme = theme
|
||||
stats_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL # Expand to fill available width
|
||||
vbox.add_child(stats_container)
|
||||
|
||||
# Stats labels
|
||||
# Stats labels - enemies defeated and level time
|
||||
var enemies_label = Label.new()
|
||||
enemies_label.name = "EnemiesLabel"
|
||||
enemies_label.theme = theme
|
||||
enemies_label.add_theme_font_size_override("font_size", 24)
|
||||
enemies_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
enemies_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL # Expand to fill available width
|
||||
stats_container.add_child(enemies_label)
|
||||
|
||||
var time_label = Label.new()
|
||||
time_label.name = "TimeLabel"
|
||||
time_label.theme = theme
|
||||
time_label.add_theme_font_size_override("font_size", 24)
|
||||
time_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
time_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL # Expand to fill available width
|
||||
stats_container.add_child(time_label)
|
||||
|
||||
var downed_label = Label.new()
|
||||
downed_label.name = "DownedLabel"
|
||||
downed_label.theme = theme
|
||||
downed_label.add_theme_font_size_override("font_size", 24)
|
||||
downed_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
downed_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL # Expand to fill available width
|
||||
stats_container.add_child(downed_label)
|
||||
|
||||
var exp_label = Label.new()
|
||||
exp_label.name = "ExpLabel"
|
||||
exp_label.theme = theme
|
||||
exp_label.add_theme_font_size_override("font_size", 24)
|
||||
exp_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
exp_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL # Expand to fill available width
|
||||
stats_container.add_child(exp_label)
|
||||
|
||||
var coins_label = Label.new()
|
||||
coins_label.name = "CoinsLabel"
|
||||
coins_label.theme = theme
|
||||
coins_label.add_theme_font_size_override("font_size", 24)
|
||||
coins_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
coins_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL # Expand to fill available width
|
||||
stats_container.add_child(coins_label)
|
||||
|
||||
# Add script
|
||||
@@ -1810,7 +2045,7 @@ func _spawn_blocking_doors():
|
||||
if blocking_doors == null or not blocking_doors is Array:
|
||||
return
|
||||
|
||||
var door_scene = preload("res://scenes/door.tscn")
|
||||
var door_scene = load("res://scenes/door.tscn")
|
||||
if not door_scene:
|
||||
push_error("ERROR: Could not load door scene!")
|
||||
return
|
||||
@@ -1822,6 +2057,9 @@ func _spawn_blocking_doors():
|
||||
|
||||
print("GameWorld: Spawning ", blocking_doors.size(), " blocking doors")
|
||||
|
||||
# Track pillar placement per room to avoid duplicates
|
||||
var rooms_with_pillars: Dictionary = {} # Key: room string "x,y", Value: true if pillar exists
|
||||
|
||||
for i in range(blocking_doors.size()):
|
||||
var door_data = blocking_doors[i]
|
||||
if not door_data is Dictionary:
|
||||
@@ -1864,14 +2102,24 @@ func _spawn_blocking_doors():
|
||||
|
||||
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
|
||||
# CRITICAL: Store original door connection info from door_data
|
||||
# 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:
|
||||
# Use original_room1 and original_room2 from door_data (which may have been swapped)
|
||||
if "original_room1" in door_data and door_data.original_room1:
|
||||
door.room1 = door_data.original_room1 # This is always the puzzle room
|
||||
elif "door" in door_data and door_data.door is Dictionary:
|
||||
# Fallback to original door if original_room1 not set
|
||||
var original_door = door_data.door
|
||||
if "room1" in original_door and original_door.room1:
|
||||
door.room1 = original_door.room1
|
||||
|
||||
if "original_room2" in door_data and door_data.original_room2:
|
||||
door.room2 = door_data.original_room2 # This is always the other room
|
||||
elif "door" in door_data and door_data.door is Dictionary:
|
||||
# Fallback to original door if original_room2 not set
|
||||
var original_door = door_data.door
|
||||
if "room2" in original_door and original_door.room2:
|
||||
door.room2 = original_door.room2
|
||||
|
||||
@@ -1990,6 +2238,42 @@ func _spawn_blocking_doors():
|
||||
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")
|
||||
|
||||
# If this is a pillar switch, ensure a pillar exists in the room
|
||||
# Check if switch is a pillar switch (check both door_data and existing switch)
|
||||
var is_pillar_switch = (switch_type == "pillar")
|
||||
if not is_pillar_switch and "switch_type" in existing_switch:
|
||||
is_pillar_switch = (existing_switch.switch_type == "pillar")
|
||||
|
||||
if is_pillar_switch:
|
||||
# Check if we've already verified/placed a pillar for this room
|
||||
var room_key = str(door_blocking_room.x) + "," + str(door_blocking_room.y)
|
||||
if not rooms_with_pillars.has(room_key):
|
||||
# First time checking this room - check if pillar exists
|
||||
var pillar_exists_in_room = false
|
||||
for obj in get_tree().get_nodes_in_group("interactable_object"):
|
||||
if is_instance_valid(obj):
|
||||
var obj_type = obj.object_type if "object_type" in obj else ""
|
||||
if obj_type == "Pillar":
|
||||
if obj.has_meta("room"):
|
||||
var obj_room = obj.get_meta("room")
|
||||
if obj_room and not obj_room.is_empty():
|
||||
if obj_room.x == door_blocking_room.x and obj_room.y == door_blocking_room.y and \
|
||||
obj_room.w == door_blocking_room.w and obj_room.h == door_blocking_room.h:
|
||||
pillar_exists_in_room = true
|
||||
print("GameWorld: Found existing pillar in room (", door_blocking_room.x, ",", door_blocking_room.y, ")")
|
||||
break
|
||||
|
||||
# If no pillar exists, place one
|
||||
if not pillar_exists_in_room:
|
||||
print("GameWorld: Pillar switch found but no pillar in room (", door_blocking_room.x, ",", door_blocking_room.y, ") - placing pillar now")
|
||||
_place_pillar_in_room(door_blocking_room, existing_switch.global_position)
|
||||
# Mark room as checked after attempting to place pillar
|
||||
# Note: Even if placement fails, mark as checked to avoid repeated attempts
|
||||
rooms_with_pillars[room_key] = true
|
||||
else:
|
||||
# Pillar exists - mark room as checked
|
||||
rooms_with_pillars[room_key] = true
|
||||
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
|
||||
@@ -2038,7 +2322,16 @@ func _spawn_blocking_doors():
|
||||
|
||||
# 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)
|
||||
# Check if we've already placed a pillar for this room
|
||||
var room_key = str(door_blocking_room.x) + "," + str(door_blocking_room.y)
|
||||
if not rooms_with_pillars.has(room_key):
|
||||
print("GameWorld: Placing pillar for new pillar switch in room (", door_blocking_room.x, ",", door_blocking_room.y, ")")
|
||||
_place_pillar_in_room(door_blocking_room, switch_pos)
|
||||
# Mark room as checked after attempting to place pillar
|
||||
# Note: Even if placement fails, mark as checked to avoid repeated attempts
|
||||
rooms_with_pillars[room_key] = true
|
||||
else:
|
||||
print("GameWorld: Pillar already exists/placed for room (", door_blocking_room.x, ",", door_blocking_room.y, ") - skipping placement")
|
||||
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
|
||||
@@ -2119,15 +2412,18 @@ func _spawn_blocking_doors():
|
||||
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
|
||||
# Spawn a floor switch using the scene file
|
||||
# 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!")
|
||||
var switch_scene = preload("res://scenes/floor_switch.tscn")
|
||||
if not switch_scene:
|
||||
push_error("ERROR: Could not load floor_switch scene!")
|
||||
return null
|
||||
|
||||
var switch = switch_scene.instantiate()
|
||||
if not switch:
|
||||
push_error("ERROR: Could not instantiate floor_switch scene!")
|
||||
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")
|
||||
|
||||
@@ -2136,13 +2432,6 @@ func _spawn_floor_switch(i_position: Vector2, required_weight: float, tile_x: in
|
||||
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)
|
||||
@@ -2259,9 +2548,11 @@ func _spawn_room_triggers():
|
||||
|
||||
print("GameWorld: Spawning ", rooms.size(), " room triggers")
|
||||
|
||||
var triggers_spawned = 0
|
||||
for i in range(rooms.size()):
|
||||
var room = rooms[i]
|
||||
if not room is Dictionary:
|
||||
print("GameWorld: WARNING - Room at index ", i, " is not a Dictionary, skipping")
|
||||
continue
|
||||
|
||||
var trigger = Area2D.new()
|
||||
@@ -2298,8 +2589,10 @@ func _spawn_room_triggers():
|
||||
|
||||
# Add to scene
|
||||
entities_node.add_child(trigger)
|
||||
triggers_spawned += 1
|
||||
print("GameWorld: Added room trigger ", trigger.name, " for room (", room.x, ", ", room.y, ") - ", triggers_spawned, "/", rooms.size())
|
||||
|
||||
print("GameWorld: Spawned ", rooms.size(), " room triggers")
|
||||
print("GameWorld: Spawned ", triggers_spawned, " room triggers (out of ", rooms.size(), " rooms)")
|
||||
|
||||
func _place_key_in_room(room: Dictionary):
|
||||
# Place a key in the specified room (as loot)
|
||||
@@ -2352,7 +2645,7 @@ func _place_pillar_in_room(room: Dictionary, switch_position: Vector2):
|
||||
if room.is_empty():
|
||||
return
|
||||
|
||||
var interactable_object_scene = preload("res://scenes/interactable_object.tscn")
|
||||
var interactable_object_scene = load("res://scenes/interactable_object.tscn")
|
||||
if not interactable_object_scene:
|
||||
push_error("ERROR: Could not load interactable_object scene for pillar!")
|
||||
return
|
||||
@@ -2366,15 +2659,22 @@ func _place_pillar_in_room(room: Dictionary, switch_position: Vector2):
|
||||
var tile_size = 16
|
||||
var valid_positions = []
|
||||
|
||||
# Room interior is from room.x + 2 to room.x + room.w - 2 (excluding 2-tile walls)
|
||||
# Room interior floor tiles: from room.x + 2 to room.x + room.w - 3 (excluding 2-tile walls on each side)
|
||||
# Floor tiles are at: room.x+2, room.x+3, ..., room.x+room.w-3 (last floor tile before right wall)
|
||||
# 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 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)
|
||||
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)
|
||||
|
||||
var interior_width = room.w - 4 # Exclude 2-tile walls on each side
|
||||
var interior_height = room.h - 4 # Exclude 2-tile walls on each side
|
||||
print("GameWorld: _place_pillar_in_room - Searching for valid positions in room (", room.x, ",", room.y, ",", room.w, "x", room.h, ")")
|
||||
print("GameWorld: Interior size: ", interior_width, "x", interior_height, ", Checking tiles: x[", min_x, " to ", max_x, "], y[", min_y, " to ", max_y, "]")
|
||||
print("GameWorld: Switch position: ", switch_position)
|
||||
|
||||
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):
|
||||
@@ -2388,15 +2688,22 @@ func _place_pillar_in_room(room: Dictionary, switch_position: Vector2):
|
||||
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
|
||||
# Ensure pillar is at least 1 tile 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
|
||||
if distance_to_switch >= tile_size * 1: # At least 1 tiles away
|
||||
valid_positions.append(world_pos)
|
||||
print("GameWorld: Valid position found at (", x, ",", y, ") -> world (", world_x, ",", world_y, "), distance to switch: ", distance_to_switch)
|
||||
else:
|
||||
print("GameWorld: Position at (", x, ",", y, ") -> world (", world_x, ",", world_y, ") too close to switch (distance: ", distance_to_switch, " < ", tile_size, ")")
|
||||
|
||||
print("GameWorld: Found ", valid_positions.size(), " valid positions for pillar")
|
||||
if valid_positions.size() > 0:
|
||||
# Pick a random position
|
||||
# Pick a deterministic random position using dungeon seed
|
||||
# This ensures server and clients place pillars in the same positions
|
||||
var rng = RandomNumberGenerator.new()
|
||||
rng.randomize()
|
||||
# Use dungeon seed + room position + switch position as seed for deterministic randomness
|
||||
var pillar_seed = dungeon_seed + (room.x * 1000) + (room.y * 100) + int(switch_position.x) + int(switch_position.y)
|
||||
rng.seed = pillar_seed
|
||||
var pillar_pos = valid_positions[rng.randi() % valid_positions.size()]
|
||||
|
||||
# Spawn pillar interactable object
|
||||
|
||||
Reference in New Issue
Block a user