From 0bbd449c550f0737a1aae4f3251ee5b2e99efc56 Mon Sep 17 00:00:00 2001 From: Voyager Date: Sat, 8 Aug 2026 10:25:17 -0400 Subject: [PATCH] feat: adapt fishing visuals to character scale --- fishing/fishing_presentation.gd | 18 +++++++++------ fishing/fishing_spot.gd | 12 +++++++++- fishing/remote_fishing_presentation.gd | 14 ++++++++--- player/player.gd | 32 ++++++++++++++++++++++++-- 4 files changed, 63 insertions(+), 13 deletions(-) diff --git a/fishing/fishing_presentation.gd b/fishing/fishing_presentation.gd index e19fb0f..8a5ecb0 100644 --- a/fishing/fishing_presentation.gd +++ b/fishing/fishing_presentation.gd @@ -59,6 +59,7 @@ var _cast_arrival_position: Vector3 var _cast_tracks_water_surface: bool = false var _bobber_surface_position: Vector3 var _bobber_idle_elapsed: float = 0.0 +var _bobber_base_scale: Vector3 = Vector3.ONE var _active_tween: Tween var _rod_tween: Tween var _bite_tween: Tween @@ -94,11 +95,13 @@ func begin_aim( minimum_target: Vector3, target_normal: Vector3, target_is_fishable: bool, + character_scale: float = 1.0, ) -> void: cleanup() if rod_tip == null or rod == null: return + _bobber_base_scale = Vector3.ONE * maxf(character_scale, 0.01) _mode = VisualMode.AIMING _rod = rod _rod_tip = rod_tip @@ -158,7 +161,7 @@ func begin_cast( _mode = VisualMode.CASTING _target_marker.visible = false _bobber.visible = true - _bobber.scale = Vector3.ONE + _bobber.scale = _bobber_base_scale _cast_arrival_position = ( blocked_landing_position if cast_is_blocked else target ) @@ -188,26 +191,26 @@ func show_bite() -> void: return _kill_bite_tween() - _bobber.scale = Vector3.ONE + _bobber.scale = _bobber_base_scale _bite_tween = create_tween() _bite_tween.set_trans(Tween.TRANS_QUAD) _bite_tween.set_ease(Tween.EASE_IN_OUT) _bite_tween.tween_property( _bobber, "scale", - Vector3.ONE * 0.68, + _bobber_base_scale * 0.68, 0.08 ) _bite_tween.tween_property( _bobber, "scale", - Vector3.ONE * 1.2, + _bobber_base_scale * 1.2, 0.1 ) _bite_tween.tween_property( _bobber, "scale", - Vector3.ONE, + _bobber_base_scale, 0.1 ) _bite_tween.finished.connect(_on_bite_tween_finished) @@ -247,7 +250,7 @@ func play_outcome(outcome: StringName) -> void: # Withdrawal is a visible return, not an instant cancellation. Keep the # bobber present even if the preceding reel update hid or reset it. _bobber.visible = true - _bobber.scale = Vector3.ONE + _bobber.scale = _bobber_base_scale _active_tween = create_tween() _active_tween.set_trans(Tween.TRANS_QUAD) _active_tween.set_ease(Tween.EASE_IN) @@ -296,6 +299,7 @@ func cleanup() -> void: _invalid_target_marker.visible = false _bobber.visible = false _bobber.scale = Vector3.ONE + _bobber_base_scale = Vector3.ONE _bobber.rotation = Vector3.ZERO _cast_arrival_position = Vector3.ZERO _cast_tracks_water_surface = false @@ -475,7 +479,7 @@ func _kill_bite_tween() -> void: _bite_tween.kill() _bite_tween = null if is_instance_valid(_bobber): - _bobber.scale = Vector3.ONE + _bobber.scale = _bobber_base_scale func _on_bite_tween_finished() -> void: diff --git a/fishing/fishing_spot.gd b/fishing/fishing_spot.gd index 6c7b434..b275bcb 100644 --- a/fishing/fishing_spot.gd +++ b/fishing/fishing_spot.gd @@ -609,7 +609,8 @@ func _begin_aiming(player: PlayerType) -> void: _active_player.get_fishing_rod(), preview_position, _aim_surface_sample.normal, - target_is_fishable + target_is_fishable, + _active_player.get_character_visual_scale(), ) @@ -952,6 +953,7 @@ func _activate_bite() -> void: return state = FishingState.FIGHTING + _active_player.set_fighting_visual(true) _state_time_remaining = 0.0 _withdrawal_input_held = false _pending_catch = _fish_selector.create_catch(_selected_fish) @@ -1040,6 +1042,7 @@ func _on_catch_completed() -> void: _cancel_attempt() return _stop_fight_audio() + _active_player.set_fighting_visual(false) state = FishingState.SHOWING_CATCH _showcase_ready = false _showcase_outcome_completed = false @@ -1162,6 +1165,8 @@ func _cleanup_attempt( ) -> void: _stop_fight_audio() _stop_reeling_audio() + if _active_player != null: + _active_player.set_fighting_visual(false) if not visual_outcome.is_empty(): if state == FishingState.RETURNING: return @@ -1190,6 +1195,7 @@ func _cleanup_attempt( func _finalize_attempt_cleanup(cooldown_message: String) -> void: _showcase_restore_generation += 1 if _active_player != null: + _active_player.set_fighting_visual(false) _active_player.end_catch_showcase() _active_player.set_movement_enabled(true) _active_player = null @@ -1477,6 +1483,8 @@ func _on_network_bite_started(_attempt_id: String) -> void: return _stop_reeling_audio() state = FishingState.FIGHTING + if _active_player != null: + _active_player.set_fighting_visual(true) _withdrawal_input_held = false _network_primary_input_held = Input.is_action_pressed("fish_primary") _network_input_resend_elapsed = 0.0 @@ -1567,6 +1575,8 @@ func _on_network_catch_received(fish_catch: FishCatchType) -> void: return _stop_fight_audio() _pending_catch = fish_catch + if _active_player != null: + _active_player.set_fighting_visual(false) state = FishingState.SHOWING_CATCH _showcase_ready = false _showcase_outcome_completed = false diff --git a/fishing/remote_fishing_presentation.gd b/fishing/remote_fishing_presentation.gd index 8dab308..22b1859 100644 --- a/fishing/remote_fishing_presentation.gd +++ b/fishing/remote_fishing_presentation.gd @@ -19,6 +19,7 @@ var _return_tween: Tween var _showcase_tween: Tween var _return_showcase_catch: FishCatch var _bobber_idle_elapsed: float = 0.0 +var _bobber_base_scale: Vector3 = Vector3.ONE func setup(owning_player: Player) -> void: @@ -52,11 +53,12 @@ func show_cast(origin: Vector3, target: Vector3) -> void: return _kill_cast_tween() _active = true + _bobber_base_scale = Vector3.ONE * _owner.get_character_visual_scale() _target = origin _pending_target = target _bobber_idle_elapsed = 0.0 _bobber.global_position = origin - _bobber.scale = Vector3.ONE + _bobber.scale = _bobber_base_scale _bobber.visible = true _line.visible = true _owner.set_active_item_is_rod(true) @@ -87,12 +89,16 @@ func update_bobber(world_position: Vector3) -> void: func show_bite() -> void: if not _active: return + if _owner != null and is_instance_valid(_owner): + _owner.set_fighting_visual(true) var tween: Tween = create_tween() - tween.tween_property(_bobber, "scale", Vector3.ONE * 0.7, 0.08) - tween.tween_property(_bobber, "scale", Vector3.ONE, 0.12) + tween.tween_property(_bobber, "scale", _bobber_base_scale * 0.7, 0.08) + tween.tween_property(_bobber, "scale", _bobber_base_scale, 0.12) func play_return(showcase_catch: FishCatch = null) -> void: + if _owner != null and is_instance_valid(_owner): + _owner.set_fighting_visual(false) if not _active or _bobber == null: cleanup() return_completed.emit() @@ -128,10 +134,12 @@ func cleanup() -> void: _return_showcase_catch = null _bobber_idle_elapsed = 0.0 if _owner != null and is_instance_valid(_owner): + _owner.set_fighting_visual(false) _owner.end_catch_showcase(Callable(), true) if _bobber != null: _bobber.visible = false _bobber.scale = Vector3.ONE + _bobber_base_scale = Vector3.ONE if _line != null: _line.visible = false _line_mesh.clear_surfaces() diff --git a/player/player.gd b/player/player.gd index d4c0515..f006af4 100644 --- a/player/player.gd +++ b/player/player.gd @@ -41,6 +41,7 @@ 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 FIGHTING_EYES_ID: String = "alligator_eyes" const BASE_REEL_SPEED: float = 0.16 # The target Android handheld exposes its physical right trigger through # Godot's left-trigger axis. Keep the role named here so the platform mapping @@ -75,7 +76,33 @@ func unequip_bait() -> void: func apply_appearance_snapshot(snapshot: Dictionary) -> void: if CharacterCustomizationCatalog.validate_snapshot(snapshot): appearance_snapshot = snapshot.duplicate(true) - PlayerVisualPresenter.apply_appearance(_visuals, appearance_snapshot) + _apply_presented_appearance() + + +func get_character_visual_scale() -> float: + return CharacterCustomizationCatalog.character_scale( + appearance_snapshot.get( + CharacterCustomizationCatalog.SCALE_CATEGORY_ID, + CharacterCustomizationCatalog.DEFAULT_CHARACTER_SCALE, + ) + ) + + +func set_fighting_visual(active: bool) -> void: + if _fighting_visual_active == active: + return + _fighting_visual_active = active + _apply_presented_appearance() + + +func _apply_presented_appearance() -> void: + if not is_node_ready() or _visuals == null: + return + var presented_appearance := appearance_snapshot + if _fighting_visual_active: + presented_appearance = appearance_snapshot.duplicate(true) + presented_appearance["eyes"] = FIGHTING_EYES_ID + PlayerVisualPresenter.apply_appearance(_visuals, presented_appearance) func apply_animalese_voice_id(voice_id: String) -> void: @@ -218,6 +245,7 @@ var _sitting_intent_pending: bool = false var _sitting_intent_sequence: int = -1 var _held_fish_visible: bool = false var _showcase_animation_active: bool = false +var _fighting_visual_active: bool = false var _fishing_rod: Node3D var _fishing_rod_tip: Marker3D var _controller_mapping_manager: ControllerMappingManagerType @@ -226,7 +254,7 @@ var _controller_mapping_manager: ControllerMappingManagerType func _ready() -> void: if not bag.contents_changed.is_connected(_on_bag_contents_changed): bag.contents_changed.connect(_on_bag_contents_changed) - PlayerVisualPresenter.apply_appearance(_visuals, appearance_snapshot) + _apply_presented_appearance() _initialize_fishing_rod() _target_zoom = clampf(_spring_arm.spring_length, minimum_zoom, maximum_zoom) _spring_arm.spring_length = _target_zoom