feat: implement Tekton NPC with movement, combat, and interaction mechanics, and add initial portal system components.

This commit is contained in:
2026-02-27 04:29:02 +08:00
parent 38a7c06311
commit 1c5c3d47f6
3 changed files with 77 additions and 10 deletions
+59 -3
View File
@@ -19,6 +19,7 @@ var tile_refresh_timer: Timer
var finish_spawned: bool = false
var missions_required: int = 3
var arena_setup_done: bool = false
var player_portal_cooldowns: Dictionary = {}
func initialize(p_main: Node, p_gridmap: Node):
main = p_main
@@ -346,12 +347,23 @@ func _on_tile_refresh_timer_timeout():
func _refresh_tiles():
# GridMap Floor 0 has the walls (ID 4) and floors (ID 0)
# GridMap Floor 1 should have the items (Heart, Star, etc)
# Cache door positions to avoid spawning under them
var door_positions = []
for door in doors:
if is_instance_valid(door):
var local_pos = gridmap.local_to_map(door.global_position)
door_positions.append(Vector2i(local_pos.x, local_pos.z))
for x in range(GRID_SIZE):
for z in range(GRID_SIZE):
# 1. Check if Floor 0 is a wall or empty (non-walkable)
var floor_0_item = gridmap.get_cell_item(Vector3i(x, 0, z))
if floor_0_item == 4 or floor_0_item == -1:
continue
# 1.5. Prevent spawning directly under portal doors
if door_positions.has(Vector2i(x, z)):
continue
# 2. Check if Floor 1 is already occupied
if gridmap.get_cell_item(Vector3i(x, 1, z)) != -1:
@@ -379,6 +391,12 @@ func _pick_weighted_tile(weights: Dictionary) -> int:
func handle_portal_interaction(player, door):
if not multiplayer.is_server(): return
var current_time = Time.get_ticks_msec()
if player_portal_cooldowns.has(player.name):
if current_time - player_portal_cooldowns[player.name] < 3000:
return
player_portal_cooldowns[player.name] = current_time
var source_id = door.door_id
if not connections.has(source_id): return
@@ -388,13 +406,51 @@ func handle_portal_interaction(player, door):
# Use stored offset to avoid infinite loop (spawn inside the target room)
var offset = target_door.get_meta("spawn_offset") if target_door.has_meta("spawn_offset") else Vector2i(0, 0)
# Convert world pos back to grid
var target_world = target_door.global_position
var target_grid_3d = gridmap.local_to_map(target_world)
var target_grid = Vector2i(target_grid_3d.x, target_grid_3d.z) + offset
print("[Portal] Teleporting %s to Room %d, Pos %s (via Door %d)" % [player.name, target_door.room_id, target_grid, target_id])
# Check for overlaps at the target_grid
var final_target = target_grid
var all_players = get_tree().get_nodes_in_group("Players")
var is_occupied = true
var search_radius = 0
var max_search_radius = 2
while is_occupied and search_radius <= max_search_radius:
is_occupied = false
for p in all_players:
if p != player and p.current_position == final_target:
is_occupied = true
break
if is_occupied:
# Try to find an adjacent cell
search_radius += 1
var found_empty = false
# Check immediate neighbors first
var offsets = [Vector2i(1, 0), Vector2i(-1, 0), Vector2i(0, 1), Vector2i(0, -1),
Vector2i(1, 1), Vector2i(-1, 1), Vector2i(1, -1), Vector2i(-1, -1)]
for offset_vec in offsets:
var test_pos = final_target + offset_vec
# Check if it's strictly a floor tile (ID 0) on Floor 0, not a wall
if gridmap.get_cell_item(Vector3i(test_pos.x, 0, test_pos.y)) == 0:
# Verify no player is on this test_pos
var test_occupied = false
for p in all_players:
if p != player and p.current_position == test_pos:
test_occupied = true
break
if not test_occupied:
final_target = test_pos
found_empty = true
break
if found_empty:
is_occupied = false
print("[Portal] Teleporting %s to Room %d, Pos %s (via Door %d)" % [player.name, target_door.room_id, final_target, target_id])
# Snap player
if player.has_method("set_spawn_position"):
player.rpc("set_spawn_position", target_grid)
player.rpc("set_spawn_position", final_target)