133 lines
3.8 KiB
GDScript
133 lines
3.8 KiB
GDScript
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)
|