diff --git a/scenes/player.gd b/scenes/player.gd index 65cd627..0159249 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -1874,7 +1874,10 @@ func sync_throw_tekton(target_pos: Vector2i): var tekton = carried_tekton carried_tekton = null is_carrying_tekton = false - tekton.set_carried(false) + if tekton.has_method("set_thrown"): + tekton.set_thrown(true) + else: + tekton.set_carried(false) # Visual Arc Tween var start_pos = tekton.global_position diff --git a/scenes/tekton_mesh.tscn b/scenes/tekton_mesh.tscn index ce0df7d..e7083e7 100644 --- a/scenes/tekton_mesh.tscn +++ b/scenes/tekton_mesh.tscn @@ -362,6 +362,7 @@ blend_shape_mode = 0 [sub_resource type="Animation" id="Animation_0jd8m"] resource_name = "tekton_idle" length = 0.875 +loop_mode = 1 tracks/0/type = "position_3d" tracks/0/imported = true tracks/0/enabled = true @@ -569,6 +570,7 @@ _data = { } [node name="tekton" type="Node3D" unique_id=2052742928] +transform = Transform3D(0.15, 0, 0, 0, 0.15, 0, 0, 0, 0.15, 0, 0, 0) [node name="Armature" type="Node3D" parent="." unique_id=1122137922] transform = Transform3D(1.1701986, 0, 0, 0, 1.1701986, 0, 0, 0, 1.1701986, 0, 0, 0) @@ -586,7 +588,7 @@ bones/1/parent = 0 bones/1/rest = Transform3D(0.99999994, -6.3734255e-08, -2.29742e-07, -1.065814e-14, 0.9636076, -0.26732078, 2.384186e-07, 0.2673208, 0.96360755, -1.4760682e-14, 1.0773813, 0) bones/1/enabled = true bones/1/position = Vector3(-1.4760682e-14, 1.0773813, 0) -bones/1/rotation = Quaternion(0.13007852, 0.02469786, 0.04730596, 0.9900666) +bones/1/rotation = Quaternion(0.13489334, -1.18119765e-07, 1.6080534e-08, 0.9908601) bones/1/scale = Vector3(0.99999994, 1, 0.99999994) bones/2/name = "Bone.002" bones/2/parent = 1 diff --git a/scripts/tekton.gd b/scripts/tekton.gd index 86e084a..689136e 100644 --- a/scripts/tekton.gd +++ b/scripts/tekton.gd @@ -7,11 +7,12 @@ 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 +@export var movement_speed: float = 2.0 # Seconds per step (Slower walk) var enhanced_gridmap: Node var is_moving: bool = false var is_carried: bool = false +var is_thrown: bool = false var carrier: Node3D = null var tween: Tween @@ -31,7 +32,7 @@ func sync_position(pos: Vector2i): update_visual_position() func update_visual_position(): - if is_carried: return + if is_carried or is_thrown: return # Align with floor height (matching player's typical grounded height) var floor_y = 0.05 @@ -43,7 +44,7 @@ func update_visual_position(): position = Vector3(current_position.x + 0.2, floor_y, current_position.y + 0.2) func move_to(target_pos: Vector2i): - if is_moving or is_carried: return + if is_moving or is_carried or is_thrown: return # Validate if not enhanced_gridmap.is_position_valid(target_pos): @@ -63,11 +64,14 @@ func move_to(target_pos: Vector2i): var target_rot = atan2(dir.x, dir.z) rotation.y = target_rot + play_animation("tekton_move") + tween = create_tween() - tween.tween_property(self , "position", target_world_pos, movement_speed).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) + tween.tween_property(self , "position", target_world_pos, movement_speed).set_trans(Tween.TRANS_LINEAR).set_ease(Tween.EASE_IN_OUT) tween.tween_callback(func(): current_position = target_pos is_moving = false + play_animation("tekton_idle") emit_signal("movement_finished") ) @@ -76,7 +80,6 @@ func move_to(target_pos: Vector2i): @rpc("any_peer", "call_remote", "reliable") func sync_movement(target_pos: Vector2i): - # Clients execute the move visually move_to(target_pos) # --- COMBAT / INTERACTION --- @@ -98,6 +101,10 @@ func on_hit(attacker: Node = null, intensity: float = 1.0): func set_carried(state: bool, p_carrier: Node3D = null): is_carried = state carrier = p_carrier + + if is_carried: + play_animation("tekton_idle") + is_moving = false if tween: tween.kill() @@ -111,9 +118,33 @@ func set_carried(state: bool, p_carrier: Node3D = null): else: controller.call("_start_timer") - if not state: + if not state and not is_thrown: update_visual_position() +@rpc("any_peer", "call_local") +func set_thrown(state: bool): + is_thrown = state + + if is_thrown: + # If we are thrown, we are not carried anymore, but we must stay disabled + if is_carried: + set_carried(false) # This will re-enable controller, so we must disable it again below + + var controller = get_node_or_null("TektonController") + if controller: + controller.set_physics_process(false) + if controller.get("timer"): + controller.timer.stop() + + # play_animation("RESET") # Optional: ensure no animation plays? Or just idle. + # User said "don't play ANY animation". + # If we play nothing, it freezes on last frame. + # Maybe just don't call anything new, and let previous (idle) stick? + # Or stop? + var anim_player = get_node_or_null("Visuals/tekton/Armature/AnimationPlayer") + if not anim_player: anim_player = find_child("AnimationPlayer", true, false) + if anim_player: anim_player.stop() + func _process(delta): if is_carried and is_instance_valid(carrier): # Carry on head: offset Y by approx carrier height (e.g. 1.25) @@ -125,31 +156,36 @@ var original_scales: Array[Vector3] = [] func _ready(): # Cache meshes and their initial scales - # We wait a frame to ensure all children are ready and transforms applied - await get_tree().process_frame - var meshes = find_children("*", "MeshInstance3D", true) - for mesh in meshes: - mesh_cache.append(mesh) - original_scales.append(mesh.scale) + _cache_meshes(self) + +func _cache_meshes(node: Node): + if node is MeshInstance3D: + mesh_cache.append(node) + original_scales.append(node.scale) + + for child in node.get_children(): + _cache_meshes(child) func _flash_damage(): - # If cache empty (e.g. called before ready), try to populate or just skip custom scaling - if mesh_cache.is_empty(): - return - + if mesh_cache.is_empty(): return + + var tween_flash = create_tween() for i in range(mesh_cache.size()): var mesh = mesh_cache[i] - if is_instance_valid(mesh): - var base_scale = original_scales[i] - var t = create_tween() - t.tween_property(mesh, "scale", base_scale * 1.2, 0.1) - t.tween_property(mesh, "scale", base_scale, 0.1) + var original_scale = original_scales[i] + + # Pop effect + tween_flash.parallel().tween_property(mesh, "scale", original_scale * 1.3, 0.1).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT) + tween_flash.parallel().tween_property(mesh, "scale", original_scale, 0.2).set_delay(0.1) var is_recovering: bool = false # True when shrunk/waiting +var recovery_timer: Timer @rpc("any_peer", "call_local", "reliable") func on_thrown_landing(attacker: Node = null, intensity: float = 1.0): """Called when Tekton lands after being thrown or knocked.""" + is_thrown = false + # Prevent double-triggering (re-entrancy) if is_recovering: return @@ -302,3 +338,21 @@ func spawn_tiles_around(count: int = 4): if main: main.rpc("sync_grid_item", pos.x, 1, pos.y, item_id) spawned += 1 + +func play_animation(anim_name: String): + # Try specific user path first + var anim_player = get_node_or_null("Visuals/tekton/Armature/AnimationPlayer") + + # If not found, try finding recursive + if not anim_player: + anim_player = find_child("AnimationPlayer", true, false) + + if anim_player and anim_player.has_animation(anim_name): + anim_player.play(anim_name) + # print("[Tekton] Playing animation: %s" % anim_name) + else: + if not anim_player: + print("[Tekton] AnimationPlayer NOT FOUND! Scene tree:") + print_tree_pretty() + else: + print("[Tekton] Animation '%s' NOT FOUND in AnimationPlayer!" % anim_name) diff --git a/scripts/tekton_controller.gd b/scripts/tekton_controller.gd index 0ebadd8..8c16e52 100644 --- a/scripts/tekton_controller.gd +++ b/scripts/tekton_controller.gd @@ -3,8 +3,8 @@ extends Node # TektonController - AI for Tekton NPC # Handles random movement logic. -@export var move_interval_min: float = 2.0 -@export var move_interval_max: float = 5.0 +@export var move_interval_min: float = 0.1 +@export var move_interval_max: float = 0.1 var tekton: Node3D var enhanced_gridmap: Node