diff --git a/addons/enhanced_gridmap/enhanced_gridmap.gd b/addons/enhanced_gridmap/enhanced_gridmap.gd index 5bf83da..99dbe34 100644 --- a/addons/enhanced_gridmap/enhanced_gridmap.gd +++ b/addons/enhanced_gridmap/enhanced_gridmap.gd @@ -16,6 +16,7 @@ signal grid_updated @export var hover_item: int = 1 @export var start_item: int = 2 @export var end_item: int = 3 +@export var immutable_items: Array[int] = [4] # Items that cannot be randomized/reset var current_mesh_library: MeshLibrary var grid_data: Array = [] # 3D array [floor][row][column] @@ -270,6 +271,11 @@ func randomize_floor(floor_index: int, custom_rng_callable: Callable = Callable( for x in range(columns): for z in range(rows): + # Check if current item on this floor is immutable + var current_item_on_floor = get_cell_item(Vector3i(x, floor_index, z)) + if current_item_on_floor in immutable_items: + continue + # IMPORTANT: Only place items if Floor 0 has a walkable tile var floor_0_item = get_cell_item(Vector3i(x, 0, z)) var is_ground = (floor_0_item == normal_items[0]) # Assuming 0 is ground diff --git a/scenes/main.gd b/scenes/main.gd index 9cb3a8e..c912bac 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -603,6 +603,9 @@ func _assign_random_spawn_positions(): # Tekton NPC Management # ============================================================================= +const StaticTektonManager = preload("res://scripts/managers/static_tekton_manager.gd") +var static_tekton_manager + func spawn_tekton_npc(): """Spawn a Tekton NPC at a random location.""" if not multiplayer.is_server(): return @@ -611,7 +614,7 @@ func spawn_tekton_npc(): var enhanced_gridmap = $EnhancedGridMap if not enhanced_gridmap: return - # Spawn Static Tektons (Bottom Right, Mid Right) + # Spawn Static Tektons (Using Manager) spawn_static_tektons() # Spawn 3 Roaming Tektons @@ -629,6 +632,10 @@ func spawn_tekton_npc(): # Check if walkable and no existing Tekton nearby? if enhanced_gridmap.get_cell_item(cell) == 0: # Walkable floor + # Ensure not occupied by static tekton stand (Item 4) + var item_id = enhanced_gridmap.get_cell_item(Vector3i(x, 1, y)) + if item_id == 4: continue # Wall/Stand + valid_pos = Vector2i(x, y) # Generate a consistent ID/Name for sync (add index to ensure uniqueness) @@ -673,31 +680,81 @@ func _create_tekton(pos: Vector2i, tekton_id: int, is_static: bool = false): print("[Main] Spawned Tekton at %s (ID: %d)" % [pos, tekton_id]) func spawn_static_tektons(): - """Spawn fixed static tektons (Bottom Right, Mid Right).""" + """Spawn fixed static tektons using StaticTektonManager.""" if not multiplayer.is_server(): return var enhanced_gridmap = $EnhancedGridMap if not enhanced_gridmap: return - # Bottom Right (Max X, Max Y) - if "columns" in enhanced_gridmap and "rows" in enhanced_gridmap: - var cols = enhanced_gridmap.columns - var rows = enhanced_gridmap.rows + print("[Main] Initializing StaticTektonManager...") + if not static_tekton_manager: + static_tekton_manager = StaticTektonManager.new() + # Keeping manager instance in memory + + # Calculate Spawn Points (Server Logic) + var spawn_points = static_tekton_manager.calculate_spawn_points(3, enhanced_gridmap) + print("[Main] Static Tekton Points: %s" % str(spawn_points)) + + for i in range(spawn_points.size()): + var pos = spawn_points[i] + # ID: 99000 + i (Consistent IDs for Static Tektons) + var id = 99000 + i - # Bottom Right (Corner - 1) - var pos_br = Vector2i(cols - 2, rows - 2) - var id_br = 99001 - _create_tekton(pos_br, id_br, true) - rpc("sync_spawn_static_tekton", pos_br, id_br) + # Spawn on Server + _create_static_setup(pos, id) - # Mid Right - var pos_mr = Vector2i(cols - 2, rows / 2) - var id_mr = 99002 - _create_tekton(pos_mr, id_mr, true) - rpc("sync_spawn_static_tekton", pos_mr, id_mr) + # Sync to Clients + rpc("sync_spawn_static_setup", pos, id) @rpc("call_remote", "reliable") -func sync_spawn_static_tekton(pos: Vector2i, tekton_id: int): +func sync_spawn_static_setup(pos: Vector2i, tekton_id: int): + _create_static_setup(pos, tekton_id) + +func _create_static_setup(pos: Vector2i, tekton_id: int): + """Creates both the Stand and the Static Tekton at the position.""" + var enhanced_gridmap = $EnhancedGridMap + + # 1. Create Stand + var stand_name = "StaticStand_%d" % tekton_id + if not has_node(stand_name): + var stand_scene = load("res://scenes/static_tekton_stand.tscn") + if stand_scene: + var stand = stand_scene.instantiate() + stand.name = stand_name + add_child(stand) + + # Position Stand + if enhanced_gridmap: + # Convert grid to world + var world_pos = Vector3(pos.x + 0.5, 0, pos.y + 0.5) + if "cell_size" in enhanced_gridmap: + world_pos = Vector3( + pos.x * enhanced_gridmap.cell_size.x + enhanced_gridmap.cell_size.x/2, + 0, + pos.y * enhanced_gridmap.cell_size.z + enhanced_gridmap.cell_size.z/2 + ) + stand.global_position = world_pos + + # Update GridMap to block pathfinding (Item 4 = Wall) + # Mark entire 3x3 area as immutable obstacles on FLOOR 0 (Ground Level) + # This overwrites the ground tile to ensure PlayerMovementManager sees it as blocked. + for dx in range(-1, 2): + for dy in range(-1, 2): + var tile_pos = Vector3i(pos.x + dx, 0, pos.y + dy) + enhanced_gridmap.set_cell_item(tile_pos, 4) + + # CRITICAL: Force AStar update so Bots and Pathfinding know about the new walls + if enhanced_gridmap.has_method("update_astar_costs"): + enhanced_gridmap.update_astar_costs() + + # 2. Create Tekton + # Reuse _create_tekton logic but force params _create_tekton(pos, tekton_id, true) + + # Force Tekton height UP to sit on stand + var tekton = get_node_or_null("Tekton_%d" % tekton_id) + if tekton: + tekton.position.y += 0.6 # Stand Height + # ============================================================================= diff --git a/scenes/main.tscn b/scenes/main.tscn index 0977cc2..a34db1d 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -97,6 +97,7 @@ columns = 14 rows = 14 floors = 2 auto_randomize = true +immutable_items = null metadata/_editor_floor_ = Vector3(0, 1, 0) [node name="Camera3D" type="Camera3D" parent="." unique_id=1200003163] diff --git a/scenes/static_tekton_stand.tscn b/scenes/static_tekton_stand.tscn new file mode 100644 index 0000000..60336e0 --- /dev/null +++ b/scenes/static_tekton_stand.tscn @@ -0,0 +1,27 @@ +[gd_scene load_steps=3 format=3 uid="uid://static_tekton_stand_001"] + +[sub_resource type="CylinderMesh" id="CylinderMesh_stand"] +top_radius = 1.4 +bottom_radius = 1.4 +height = 0.6 + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_stand"] +albedo_color = Color(0.15, 0.15, 0.2, 1) +metallic = 0.6 +roughness = 0.4 + +[sub_resource type="CylinderShape3D" id="CylinderShape3D_stand"] +height = 0.6 +radius = 1.4 + +[node name="StaticTektonStand" type="StaticBody3D"] +collision_mask = 0 + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.3, 0) +mesh = SubResource("CylinderMesh_stand") +surface_material_override/0 = SubResource("StandardMaterial3D_stand") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.3, 0) +shape = SubResource("CylinderShape3D_stand") diff --git a/scripts/managers/special_tiles_manager.gd b/scripts/managers/special_tiles_manager.gd index e535d25..38eb535 100644 --- a/scripts/managers/special_tiles_manager.gd +++ b/scripts/managers/special_tiles_manager.gd @@ -382,6 +382,21 @@ func _execute_block_floor(target_pos: Vector2i): var pos = n.position var block_pos = Vector3i(pos.x, 0, pos.y) + # Check current item first + var current_item = enhanced_gridmap.get_cell_item(block_pos) + + # Skip if already a wall or immutable + # We assume Item 4 is the wall/stand. + # Also check enhanced_gridmap.immutable_items if available + var is_immutable = false + if "immutable_items" in enhanced_gridmap: + if current_item in enhanced_gridmap.immutable_items: + is_immutable = true + + if current_item == 4 or is_immutable: + # Don't overwrite existing walls/stands, and don't schedule them for "restoration" (deletion) + continue + if player.is_multiplayer_authority(): var main = player.get_tree().get_root().get_node_or_null("Main") if main: @@ -390,7 +405,7 @@ func _execute_block_floor(target_pos: Vector2i): # Record for restoration blocked_tiles.append({ "position": block_pos, - "original_item": 0, + "original_item": current_item, # Restore the ACTUAL item that was there (e.g. ground 0 or maybe a dropped item?) "timer": BLOCK_DURATION }) diff --git a/scripts/managers/static_tekton_manager.gd b/scripts/managers/static_tekton_manager.gd new file mode 100644 index 0000000..086e97b --- /dev/null +++ b/scripts/managers/static_tekton_manager.gd @@ -0,0 +1,90 @@ +extends Node + +# StaticTektonManager +# Handles spawning and placement of Static Tektons in restricted zones. + +const STAND_SCENE_PATH = "res://scenes/static_tekton_stand.tscn" +const TEKTON_SCENE_PATH = "res://scenes/tekton.tscn" +const STATIC_CONTROLLER_SCRIPT = "res://scripts/static_tekton_controller.gd" + +# Zone Definitions based on CameraContextManager logic +# 9 Zones in a 3x3 grid (approximate for 14x14 map) +# Top-Left, Top-Mid, Top-Right +# Mid-Left, Mid-Mid, Mid-Right +# Bot-Left, Bot-Mid, Bot-Right + +func calculate_spawn_points(count: int, gridmap: Node) -> Array: + """ + Calculates random spawn positions for static tektons. + Returns an Array of Vector2i positions. + """ + if count <= 0 or not gridmap: return [] + + print("[StaticTektonManager] Calculating %d static tekton positions..." % count) + + # 1. Define Zones + var width = gridmap.columns + var depth = gridmap.rows + + # Simple 3x3 grid partition + var zone_w = width / 3 + var zone_d = depth / 3 + + var all_zones = [] + for z in range(3): + for x in range(3): + # Create Rect2i for each zone (x, y, w, h) + var zone_rect = Rect2i(x * zone_w, z * zone_d, zone_w, zone_d) + all_zones.append(zone_rect) + + # 2. Select Zones (Random Distinct) + all_zones.shuffle() + var selected_zones = all_zones.slice(0, count) + + var spawn_points = [] + + # 3. Pick Point in each Selected Zone + for i in range(selected_zones.size()): + var zone = selected_zones[i] + var pos = _pick_spot_in_zone(zone, gridmap) + if pos != Vector2i(-1, -1): + spawn_points.append(pos) + + return spawn_points + +func _pick_spot_in_zone(zone: Rect2i, gridmap: Node) -> Vector2i: + # Find a valid 3x3 spot in the zone + # The returned position is the CENTER of the 3x3 area + var attempts = 0 + + while attempts < 30: + attempts += 1 + # Ensure center is at least 1 tile away from edges of the map to fit 3x3 + # zone.position might be 0,0. + var min_x = max(1, zone.position.x + 1) + var max_x = min(gridmap.columns - 2, zone.position.x + zone.size.x - 2) + var min_y = max(1, zone.position.y + 1) + var max_y = min(gridmap.rows - 2, zone.position.y + zone.size.y - 2) + + if min_x > max_x or min_y > max_y: + break # Zone too small + + var x = randi_range(min_x, max_x) + var y = randi_range(min_y, max_y) + var center = Vector2i(x, y) + + # Check 3x3 area validity + var valid_area = true + for dx in range(-1, 2): + for dy in range(-1, 2): + var check_pos = Vector3i(center.x + dx, 0, center.y + dy) + if gridmap.get_cell_item(check_pos) == -1: + valid_area = false # Void/Hole + break + # Optionally check for other obstacles? + + if valid_area: + return center + + print("[StaticTektonManager] Failed to find 3x3 spot in zone %s" % zone) + return Vector2i(-1, -1) diff --git a/scripts/managers/static_tekton_manager.gd.uid b/scripts/managers/static_tekton_manager.gd.uid new file mode 100644 index 0000000..6851064 --- /dev/null +++ b/scripts/managers/static_tekton_manager.gd.uid @@ -0,0 +1 @@ +uid://dqgkrld6qe54a