extends Node3D # Tekton - Roaming NPC # Moves around the grid and spawns tiles when attacked. signal movement_finished @export var current_position: Vector2i = Vector2i(0, 0) @export var health: int = 3 @export var movement_speed: float = 0.4 # Seconds per step var enhanced_gridmap: Node var is_moving: bool = false var tween: Tween func initialize(start_pos: Vector2i, p_gridmap: Node): current_position = start_pos enhanced_gridmap = p_gridmap # Snap to grid visual (Center on tile) # User wanted Z=1.0. X should ideally be 0.5 (center). # Ensuring consistent offset across Init, Sync, and Move. position = Vector3(current_position.x + 0.5, 1.0, current_position.y + 1.0) # NO RPC HERE - Spawn RPC handles initial position sync @rpc("any_peer", "call_local", "reliable") func sync_position(pos: Vector2i): current_position = pos position = Vector3(current_position.x + 0.5, 1.0, current_position.y + 1.0) func move_to(target_pos: Vector2i): if is_moving: return # Validate if not enhanced_gridmap.is_position_valid(target_pos): return # Check simple collision (optional, can be expanded) # For now, Tekton walks through things or we check elsewhere? # Controller should check validity. is_moving = true var world_pos = Vector3(target_pos.x + 0.5, 1.0, target_pos.y + 1.0) # Rotation var dir = world_pos - position if dir.length_squared() > 0.1: var target_rot = atan2(dir.x, dir.z) rotation.y = target_rot # Tween Movement (Match Z+0.5 offset for symmetry during movement as requested) var final_world_pos = Vector3(target_pos.x + 0.5, 1.0, target_pos.y + 0.5) tween = create_tween() tween.tween_property(self, "position", final_world_pos, movement_speed).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_callback(func(): current_position = target_pos is_moving = false emit_signal("movement_finished") ) if is_multiplayer_authority(): rpc("sync_movement", target_pos) @rpc("any_peer", "call_remote", "reliable") func sync_movement(target_pos: Vector2i): # Clients execute the move visually move_to(target_pos) # --- COMBAT / INTERACTION --- func on_hit(attacker: Node = null): """Called when hit by a player attack.""" print("[Tekton] Hit by %s!" % (attacker.name if attacker else "Unknown")) # Visual Reaction (Flash red) _flash_damage() # Spawn Tiles if is_multiplayer_authority(): spawn_tiles_around() func _flash_damage(): var meshes = find_children("*", "MeshInstance3D", true) for mesh in meshes: var original_modulate = mesh.transparency # Quick flash hack or shader param? # If standard material, maybe just modulate visibility or scale var t = create_tween() t.tween_property(mesh, "scale", Vector3(1.2, 1.2, 1.2), 0.1) t.tween_property(mesh, "scale", Vector3(1.0, 1.0, 1.0), 0.1) func spawn_tiles_around(): """Spawns a mix of normal and special tiles in a radius.""" if not enhanced_gridmap: return var radius = 2 var rng = RandomNumberGenerator.new() rng.randomize() print("[Tekton] Spawning tiles around %s" % current_position) for x in range(-radius, radius + 1): for y in range(-radius, radius + 1): var pos = current_position + Vector2i(x, y) # Don't overwrite the Tekton's own cell? Or do? # Maybe avoid center. if x == 0 and y == 0: continue if enhanced_gridmap.is_position_valid(pos): # 50% chance to spawn something if rng.randf() > 0.5: continue # Determine Type var item_id: int var roll = rng.randf() if roll < 0.6: # 60% Normal Tile (7-10) item_id = rng.randi_range(7, 10) elif roll < 0.9: # 30% PowerUp (11-14) item_id = rng.randi_range(11, 14) else: # 10% Obstacle/Trap (optional) item_id = -1 # Clear? if item_id != -1: var main = get_tree().get_root().get_node_or_null("Main") if main: main.rpc("sync_grid_item", pos.x, 1, pos.y, item_id)