feat: Introduce EnhancedGridMap addon for advanced grid management and procedural generation, and StaticTektonManager for tekton spawning and placement.
This commit is contained in:
@@ -16,6 +16,7 @@ signal grid_updated
|
|||||||
@export var hover_item: int = 1
|
@export var hover_item: int = 1
|
||||||
@export var start_item: int = 2
|
@export var start_item: int = 2
|
||||||
@export var end_item: int = 3
|
@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 current_mesh_library: MeshLibrary
|
||||||
var grid_data: Array = [] # 3D array [floor][row][column]
|
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 x in range(columns):
|
||||||
for z in range(rows):
|
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
|
# IMPORTANT: Only place items if Floor 0 has a walkable tile
|
||||||
var floor_0_item = get_cell_item(Vector3i(x, 0, z))
|
var floor_0_item = get_cell_item(Vector3i(x, 0, z))
|
||||||
var is_ground = (floor_0_item == normal_items[0]) # Assuming 0 is ground
|
var is_ground = (floor_0_item == normal_items[0]) # Assuming 0 is ground
|
||||||
|
|||||||
+74
-17
@@ -603,6 +603,9 @@ func _assign_random_spawn_positions():
|
|||||||
# Tekton NPC Management
|
# Tekton NPC Management
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
const StaticTektonManager = preload("res://scripts/managers/static_tekton_manager.gd")
|
||||||
|
var static_tekton_manager
|
||||||
|
|
||||||
func spawn_tekton_npc():
|
func spawn_tekton_npc():
|
||||||
"""Spawn a Tekton NPC at a random location."""
|
"""Spawn a Tekton NPC at a random location."""
|
||||||
if not multiplayer.is_server(): return
|
if not multiplayer.is_server(): return
|
||||||
@@ -611,7 +614,7 @@ func spawn_tekton_npc():
|
|||||||
var enhanced_gridmap = $EnhancedGridMap
|
var enhanced_gridmap = $EnhancedGridMap
|
||||||
if not enhanced_gridmap: return
|
if not enhanced_gridmap: return
|
||||||
|
|
||||||
# Spawn Static Tektons (Bottom Right, Mid Right)
|
# Spawn Static Tektons (Using Manager)
|
||||||
spawn_static_tektons()
|
spawn_static_tektons()
|
||||||
|
|
||||||
# Spawn 3 Roaming Tektons
|
# Spawn 3 Roaming Tektons
|
||||||
@@ -629,6 +632,10 @@ func spawn_tekton_npc():
|
|||||||
|
|
||||||
# Check if walkable and no existing Tekton nearby?
|
# Check if walkable and no existing Tekton nearby?
|
||||||
if enhanced_gridmap.get_cell_item(cell) == 0: # Walkable floor
|
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)
|
valid_pos = Vector2i(x, y)
|
||||||
|
|
||||||
# Generate a consistent ID/Name for sync (add index to ensure uniqueness)
|
# 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])
|
print("[Main] Spawned Tekton at %s (ID: %d)" % [pos, tekton_id])
|
||||||
|
|
||||||
func spawn_static_tektons():
|
func spawn_static_tektons():
|
||||||
"""Spawn fixed static tektons (Bottom Right, Mid Right)."""
|
"""Spawn fixed static tektons using StaticTektonManager."""
|
||||||
if not multiplayer.is_server(): return
|
if not multiplayer.is_server(): return
|
||||||
var enhanced_gridmap = $EnhancedGridMap
|
var enhanced_gridmap = $EnhancedGridMap
|
||||||
if not enhanced_gridmap: return
|
if not enhanced_gridmap: return
|
||||||
|
|
||||||
# Bottom Right (Max X, Max Y)
|
print("[Main] Initializing StaticTektonManager...")
|
||||||
if "columns" in enhanced_gridmap and "rows" in enhanced_gridmap:
|
if not static_tekton_manager:
|
||||||
var cols = enhanced_gridmap.columns
|
static_tekton_manager = StaticTektonManager.new()
|
||||||
var rows = enhanced_gridmap.rows
|
# 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)
|
# Spawn on Server
|
||||||
var pos_br = Vector2i(cols - 2, rows - 2)
|
_create_static_setup(pos, id)
|
||||||
var id_br = 99001
|
|
||||||
_create_tekton(pos_br, id_br, true)
|
|
||||||
rpc("sync_spawn_static_tekton", pos_br, id_br)
|
|
||||||
|
|
||||||
# Mid Right
|
# Sync to Clients
|
||||||
var pos_mr = Vector2i(cols - 2, rows / 2)
|
rpc("sync_spawn_static_setup", pos, id)
|
||||||
var id_mr = 99002
|
|
||||||
_create_tekton(pos_mr, id_mr, true)
|
|
||||||
rpc("sync_spawn_static_tekton", pos_mr, id_mr)
|
|
||||||
|
|
||||||
@rpc("call_remote", "reliable")
|
@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)
|
_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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ columns = 14
|
|||||||
rows = 14
|
rows = 14
|
||||||
floors = 2
|
floors = 2
|
||||||
auto_randomize = true
|
auto_randomize = true
|
||||||
|
immutable_items = null
|
||||||
metadata/_editor_floor_ = Vector3(0, 1, 0)
|
metadata/_editor_floor_ = Vector3(0, 1, 0)
|
||||||
|
|
||||||
[node name="Camera3D" type="Camera3D" parent="." unique_id=1200003163]
|
[node name="Camera3D" type="Camera3D" parent="." unique_id=1200003163]
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -382,6 +382,21 @@ func _execute_block_floor(target_pos: Vector2i):
|
|||||||
var pos = n.position
|
var pos = n.position
|
||||||
var block_pos = Vector3i(pos.x, 0, pos.y)
|
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():
|
if player.is_multiplayer_authority():
|
||||||
var main = player.get_tree().get_root().get_node_or_null("Main")
|
var main = player.get_tree().get_root().get_node_or_null("Main")
|
||||||
if main:
|
if main:
|
||||||
@@ -390,7 +405,7 @@ func _execute_block_floor(target_pos: Vector2i):
|
|||||||
# Record for restoration
|
# Record for restoration
|
||||||
blocked_tiles.append({
|
blocked_tiles.append({
|
||||||
"position": block_pos,
|
"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
|
"timer": BLOCK_DURATION
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dqgkrld6qe54a
|
||||||
Reference in New Issue
Block a user