feat: implement Tekton NPC with movement, combat, and interaction mechanics, and add initial portal system components.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -33,6 +33,13 @@ func _on_body_entered(body: Node3D):
|
||||
if not is_active: return
|
||||
|
||||
if body.is_in_group("Players") or body.get("is_bot"):
|
||||
var current_time = Time.get_ticks_msec()
|
||||
if body.has_meta("last_portal_time"):
|
||||
if current_time - body.get_meta("last_portal_time") < 3000:
|
||||
return
|
||||
|
||||
body.set_meta("last_portal_time", current_time)
|
||||
|
||||
print("[PortalDoor] Player %s entered Door %d in Room %d" % [body.name, door_id, room_id])
|
||||
emit_signal("player_entered_portal", body, self )
|
||||
|
||||
|
||||
+11
-7
@@ -196,7 +196,7 @@ var original_scales: Array[Vector3] = []
|
||||
|
||||
func _ready():
|
||||
# Cache meshes and their initial scales
|
||||
_cache_meshes(self)
|
||||
_cache_meshes(self )
|
||||
|
||||
func _cache_meshes(node: Node):
|
||||
if node is MeshInstance3D:
|
||||
@@ -257,7 +257,7 @@ func on_thrown_landing(attacker: Node = null, intensity: float = 1.0):
|
||||
|
||||
# Spawn tiles (as requested "tekton will spawn a tiles around that floor also")
|
||||
if is_multiplayer_authority():
|
||||
spawn_tiles_around(int(8 * intensity))
|
||||
spawn_tiles_around(int(8 * intensity))
|
||||
|
||||
# Floor Freeze (Visual/Instant - Run on all clients locally)
|
||||
temporarily_change_floor(current_position, 1, 6, 3.0)
|
||||
@@ -281,7 +281,6 @@ func on_thrown_landing(attacker: Node = null, intensity: float = 1.0):
|
||||
controller._start_timer()
|
||||
|
||||
|
||||
|
||||
func temporarily_change_floor(center: Vector2i, radius: int, new_id: int, duration: float):
|
||||
if not enhanced_gridmap: return
|
||||
|
||||
@@ -372,6 +371,11 @@ func spawn_tiles_around(count: int = 4):
|
||||
continue
|
||||
|
||||
if enhanced_gridmap.is_position_valid(pos):
|
||||
# EXTRA CHECK: Do not spawn tiles on walls (ID 4) or empty void (ID -1) on Floor 0
|
||||
var floor_0_item = enhanced_gridmap.get_cell_item(Vector3i(pos.x, 0, pos.y))
|
||||
if floor_0_item == 4 or floor_0_item == -1:
|
||||
continue
|
||||
|
||||
# 50% chance to spawn something
|
||||
if rng.randf() > 0.5: continue
|
||||
|
||||
@@ -403,14 +407,14 @@ func play_animation_rpc(anim_name: String):
|
||||
func spawn_projectile_rpc(target_pos: Vector3, duration: float):
|
||||
var projectile = MeshInstance3D.new()
|
||||
var box = BoxMesh.new()
|
||||
box.size = Vector3(0.4, 0.1, 0.4)
|
||||
box.size = Vector3(0.4, 0.1, 0.4)
|
||||
projectile.mesh = box
|
||||
var mat = StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(1, 0.5, 0) # Orange
|
||||
projectile.material_override = mat
|
||||
|
||||
get_parent().add_child(projectile)
|
||||
projectile.global_position = global_position + Vector3(0, 2.0, 0)
|
||||
projectile.global_position = global_position + Vector3(0, 2.0, 0)
|
||||
|
||||
var tween = create_tween()
|
||||
tween.set_parallel(true)
|
||||
@@ -419,8 +423,8 @@ func spawn_projectile_rpc(target_pos: Vector3, duration: float):
|
||||
|
||||
var mid_y = max(global_position.y, target_pos.y) + 3.0
|
||||
var tween_y = create_tween()
|
||||
tween_y.tween_property(projectile, "global_position:y", mid_y, duration/2).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||
tween_y.tween_property(projectile, "global_position:y", target_pos.y, duration/2).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN).set_delay(duration/2)
|
||||
tween_y.tween_property(projectile, "global_position:y", mid_y, duration / 2).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||
tween_y.tween_property(projectile, "global_position:y", target_pos.y, duration / 2).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN).set_delay(duration / 2)
|
||||
|
||||
tween.chain().tween_callback(projectile.queue_free)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user