extends Node3D const PLAYER_SCENE: PackedScene = preload("res://scenes/entities/player/player.tscn") const ENEMY_SCENE: PackedScene = preload("res://scenes/entities/enemy/enemy.tscn") const PORTAL_SCENE: PackedScene = preload("res://scenes/entities/portal/portal.tscn") const GENERATOR: GDScript = preload("res://scenes/dungeon/dungeon_generator.gd") @onready var players_root: Node3D = $EntityRoot/Players @onready var dungeon_root: Node3D = $DungeonGeometry @onready var spawn_system: Node = $Systems/SpawnSystem var generator: Node var data: Dictionary func _ready() -> void: add_to_group("dungeon") generator = GENERATOR.new() add_child(generator) data = generator.generate(dungeon_root, GameState.dungeon_seed, GameState.current_wave * (5.0 if GameState.dungeon_red else 1.0)) EventBus.boss_defeated.connect(_on_boss_defeated) Net.world_ready.connect(_on_world_ready) Net.peer_world_loaded.connect(_on_peer_world_loaded) if Net.is_host(): multiplayer.peer_connected.connect(_on_peer_connected) multiplayer.peer_disconnected.connect(_on_peer_disconnected) Net.reset_world_ready() Net.mark_world_loaded() func _exit_tree() -> void: if Net.world_ready.is_connected(_on_world_ready): Net.world_ready.disconnect(_on_world_ready) if Net.peer_world_loaded.is_connected(_on_peer_world_loaded): Net.peer_world_loaded.disconnect(_on_peer_world_loaded) func _on_world_ready() -> void: if not Net.is_host(): return _spawn_player(1) for id in multiplayer.get_peers(): _spawn_player(id) _populate_dungeon() func _populate_dungeon() -> void: var difficulty: float = GameState.current_wave * (3.0 if GameState.dungeon_red else 1.0) for i in range(data.rooms.size() - 1): var room: Dictionary = data.rooms[i] var n: int = 2 + (1 if GameState.dungeon_red else 0) for j in range(n): var off := Vector3(randf_range(-room.size.x * 0.3, room.size.x * 0.3), 1.0, randf_range(-room.size.z * 0.3, room.size.z * 0.3)) spawn_system.spawn_enemy_at(room.pos + off, GameState.dungeon_red, difficulty * 0.5) spawn_system.spawn_boss_at(data.boss + Vector3(0, 2.0, 0), difficulty) func _spawn_player(peer_id: int) -> void: if players_root.get_node_or_null(str(peer_id)) != null: return var p: CharacterBody3D = PLAYER_SCENE.instantiate() p.name = str(peer_id) players_root.add_child(p, true) p.global_position = data.spawn + Vector3(randf_range(-1, 1), 0, randf_range(-1, 1)) func _on_peer_connected(id: int) -> void: Net.tell_peer_to_load_scene(id, GameState.SCENE_DUNGEON) func _on_peer_world_loaded(peer_id: int) -> void: if not Net.is_host(): return _spawn_player(peer_id) func _on_peer_disconnected(id: int) -> void: var node := players_root.get_node_or_null(str(id)) if node: node.queue_free() func _on_boss_defeated(b: Node) -> void: if not Net.is_host(): return var portal: StaticBody3D = PORTAL_SCENE.instantiate() portal.is_return = true portal.name = "ReturnPortal" var portals_root: Node3D = $EntityRoot/Portals portals_root.add_child(portal, true) var spawn_pos: Vector3 = (b as Node3D).global_position if b is Node3D else data.boss portal.global_position = spawn_pos + Vector3(0, 1, 0) EventBus.portal_spawned.emit(portal) var t := get_tree().create_timer(3.0) t.timeout.connect(_auto_return.bind(portal)) func _auto_return(portal: Node) -> void: if is_instance_valid(portal): portal.queue_free() GameState.change_scene(GameState.SCENE_WORLD)