diff --git a/art/exported/characters/base/netfishing_base_character.glb b/art/exported/characters/base/netfishing_base_character.glb index 679fd69..71373ea 100644 Binary files a/art/exported/characters/base/netfishing_base_character.glb and b/art/exported/characters/base/netfishing_base_character.glb differ diff --git a/art/source/characters/base/netfishing_character_source.blend b/art/source/characters/base/netfishing_character_source.blend index 2d419f7..de588af 100644 Binary files a/art/source/characters/base/netfishing_character_source.blend and b/art/source/characters/base/netfishing_character_source.blend differ diff --git a/fishing/fishing_spot.gd b/fishing/fishing_spot.gd index f437c26..cf8da57 100644 --- a/fishing/fishing_spot.gd +++ b/fishing/fishing_spot.gd @@ -363,15 +363,15 @@ func handle_escape_action() -> bool: func set_local_menu_input_suppressed( - owner: StringName, + input_owner: StringName, suppressed: bool, ) -> void: - if owner.is_empty(): + if input_owner.is_empty(): return if suppressed: - _local_menu_input_owners[owner] = true + _local_menu_input_owners[input_owner] = true else: - _local_menu_input_owners.erase(owner) + _local_menu_input_owners.erase(input_owner) if suppressed and state == FishingState.WAITING_FOR_BITE: _withdrawal_input_held = false _stop_reeling_audio() @@ -627,6 +627,7 @@ func _begin_aiming(player: PlayerType) -> void: _cast_charge = 0.0 _cast_target = _calculate_cast_target(minimum_cast_distance) state = FishingState.AIMING_CAST + _active_player.set_casting_visual() status_changed.emit("") _cast_path_is_clear = is_cast_path_clear( _get_cast_launch_position(), @@ -813,6 +814,8 @@ func _confirm_cast() -> void: # released so the active bobber/fishing event remains anchored. _active_player.set_movement_enabled(false) state = FishingState.CASTING + if _active_player != null: + _active_player.set_release_visual() status_changed.emit("") _presentation.begin_cast( _cast_target, @@ -1102,7 +1105,7 @@ func _on_catch_encounter_updated( barrier_health: PackedInt32Array, barrier_max_health: PackedInt32Array, active_barrier_index: int, - visible: bool, + encounter_visible: bool, ) -> void: catch_display_changed.emit( progress, @@ -1111,9 +1114,9 @@ func _on_catch_encounter_updated( barrier_health, barrier_max_health, active_barrier_index, - visible + encounter_visible ) - if state != FishingState.FIGHTING or not visible: + if state != FishingState.FIGHTING or not encounter_visible: return diff --git a/fishing/remote_fishing_presentation.gd b/fishing/remote_fishing_presentation.gd index 22b1859..980a298 100644 --- a/fishing/remote_fishing_presentation.gd +++ b/fishing/remote_fishing_presentation.gd @@ -62,6 +62,7 @@ func show_cast(origin: Vector3, target: Vector3) -> void: _bobber.visible = true _line.visible = true _owner.set_active_item_is_rod(true) + _owner.set_release_visual() _redraw_line() _cast_tween = create_tween() _cast_tween.set_trans(Tween.TRANS_SINE) @@ -99,6 +100,7 @@ func show_bite() -> void: func play_return(showcase_catch: FishCatch = null) -> void: if _owner != null and is_instance_valid(_owner): _owner.set_fighting_visual(false) + _owner.set_fishing_visual(false) if not _active or _bobber == null: cleanup() return_completed.emit() @@ -135,6 +137,7 @@ func cleanup() -> void: _bobber_idle_elapsed = 0.0 if _owner != null and is_instance_valid(_owner): _owner.set_fighting_visual(false) + _owner.set_fishing_visual(false) _owner.end_catch_showcase(Callable(), true) if _bobber != null: _bobber.visible = false @@ -162,6 +165,8 @@ func _set_cast_sample( func _on_cast_finished() -> void: _cast_tween = null _bobber_idle_elapsed = 0.0 + if _owner != null and is_instance_valid(_owner): + _owner.set_fishing_visual(true) _apply_bobber_idle_motion() _redraw_line() diff --git a/player/player.gd b/player/player.gd index 301d82c..5e2176f 100644 --- a/player/player.gd +++ b/player/player.gd @@ -41,10 +41,19 @@ const CHARACTER_IDLE_SIT_ANIMATION: StringName = &"idle_sit" const CHARACTER_IDLE_SIT_SHOW_ANIMATION: StringName = &"idle_sit_show" const CHARACTER_WALKING_ANIMATION: StringName = &"walking" const CHARACTER_WALKING_SHOW_ANIMATION: StringName = &"walking_show" +const CHARACTER_CASTING_ANIMATION: StringName = &"casting" +const CHARACTER_RELEASE_ANIMATION: StringName = &"release" const CHARACTER_FISHING_ANIMATION: StringName = &"fishing" const CHARACTER_FISHING_SIT_ANIMATION: StringName = &"fishing_sit" const CHARACTER_FIGHTING_ANIMATION: StringName = &"fighting" const CHARACTER_FIGHTING_SIT_ANIMATION: StringName = &"fighting_sit" + +enum FishingVisualPhase { + NONE, + CASTING, + RELEASE, + FISHING, +} const FIGHTING_EYES_ID: String = "alligator_eyes" const BASE_REEL_SPEED: float = 0.16 # The target Android handheld exposes its physical right trigger through @@ -121,9 +130,24 @@ func set_fighting_visual(active: bool) -> void: func set_fishing_visual(active: bool) -> void: - if _fishing_visual_active == active: + _set_fishing_visual_phase( + FishingVisualPhase.FISHING if active else FishingVisualPhase.NONE + ) + + +func set_casting_visual() -> void: + _set_fishing_visual_phase(FishingVisualPhase.CASTING) + + +func set_release_visual() -> void: + _set_fishing_visual_phase(FishingVisualPhase.RELEASE) + + +func _set_fishing_visual_phase(phase: FishingVisualPhase) -> void: + if _fishing_visual_phase == phase: return - _fishing_visual_active = active + _fishing_visual_phase = phase + _fishing_visual_active = phase != FishingVisualPhase.NONE _character_animation_name = &"" _update_character_animation() @@ -283,8 +307,12 @@ var _held_fish_visible: bool = false var _showcase_animation_active: bool = false var _fighting_visual_active: bool = false var _fishing_visual_active: bool = false +var _fishing_visual_phase: FishingVisualPhase = FishingVisualPhase.NONE var _fishing_rod: Node3D var _fishing_rod_tip: Marker3D +var _fishing_rod_skeleton: Skeleton3D +var _fishing_rod_hand_bone: int = -1 +var _fishing_rod_relative_basis: Basis = Basis.IDENTITY var _controller_mapping_manager: ControllerMappingManagerType @@ -334,6 +362,38 @@ func _initialize_fishing_rod() -> void: _fishing_rod_tip = attachment.get_node( "FishingRod/FishingRodTip" ) as Marker3D + _fishing_rod_skeleton = skeleton + _fishing_rod_hand_bone = skeleton.find_bone(&"hand.R") + if _fishing_rod_hand_bone < 0: + push_error("Player character right-hand bone is unavailable.") + return + _fishing_rod_relative_basis = ( + skeleton.global_basis.orthonormalized().inverse() + * _fishing_rod.global_basis.orthonormalized() + ) + _fishing_rod.top_level = true + _sync_fishing_rod_transform() + + +func _sync_fishing_rod_transform() -> void: + if ( + _fishing_rod == null + or _fishing_rod_skeleton == null + or _fishing_rod_hand_bone < 0 + ): + return + var skeleton_transform := _fishing_rod_skeleton.global_transform + var hand_pose := _fishing_rod_skeleton.get_bone_global_pose( + _fishing_rod_hand_bone + ) + var fixed_basis := ( + skeleton_transform.basis.orthonormalized() + * _fishing_rod_relative_basis + ) + _fishing_rod.global_transform = Transform3D( + fixed_basis.scaled(skeleton_transform.basis.get_scale().abs()), + (skeleton_transform * hand_pose).origin, + ) func _initialize_held_item_attachment() -> void: @@ -363,6 +423,7 @@ func _initialize_held_item_attachment() -> void: func _physics_process(delta: float) -> void: + _sync_fishing_rod_transform() if _network_interpolation_enabled: _update_network_interpolation(delta) return @@ -455,6 +516,7 @@ func _physics_process(delta: float) -> void: func _process(delta: float) -> void: + _sync_fishing_rod_transform() _update_character_animation() # The hand bone supplies the attachment position, but its animated wrist # rotation should not turn the flat fish/catch artwork edge-on. Keep each @@ -588,10 +650,24 @@ func _update_character_animation() -> void: &"idle_sit_loop", ] else: - requested_animation = [ - CHARACTER_FISHING_ANIMATION, - CHARACTER_IDLE_ANIMATION, - ] + match _fishing_visual_phase: + FishingVisualPhase.CASTING: + requested_animation = [ + CHARACTER_CASTING_ANIMATION, + CHARACTER_FISHING_ANIMATION, + CHARACTER_IDLE_ANIMATION, + ] + FishingVisualPhase.RELEASE: + requested_animation = [ + CHARACTER_RELEASE_ANIMATION, + CHARACTER_FISHING_ANIMATION, + CHARACTER_IDLE_ANIMATION, + ] + _: + requested_animation = [ + CHARACTER_FISHING_ANIMATION, + CHARACTER_IDLE_ANIMATION, + ] elif _sitting: if _held_fish_visible: requested_animation = [