diff --git a/.gitignore b/.gitignore index a56a938..33ba5fe 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ .godot/ /android/ /builds/ + +# Blender backup files +*.blend1 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_base_character.blend1 b/art/source/characters/base/netfishing_base_character.blend1 deleted file mode 100644 index e9ee0bb..0000000 Binary files a/art/source/characters/base/netfishing_base_character.blend1 and /dev/null 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/art/source/characters/base/netfishing_character_source.blend1 b/art/source/characters/base/netfishing_character_source.blend1 deleted file mode 100644 index 72e072b..0000000 Binary files a/art/source/characters/base/netfishing_character_source.blend1 and /dev/null differ diff --git a/art/source/environment/terrain/starter_island.blend1 b/art/source/environment/terrain/starter_island.blend1 deleted file mode 100644 index 99ca878..0000000 Binary files a/art/source/environment/terrain/starter_island.blend1 and /dev/null differ diff --git a/audio/music/world/craft.mp3 b/audio/music/world/craft.mp3 new file mode 100644 index 0000000..b73cd15 Binary files /dev/null and b/audio/music/world/craft.mp3 differ diff --git a/audio/music/world/craft.mp3.import b/audio/music/world/craft.mp3.import new file mode 100644 index 0000000..b8e054c --- /dev/null +++ b/audio/music/world/craft.mp3.import @@ -0,0 +1,19 @@ +[remap] + +importer="mp3" +type="AudioStreamMP3" +uid="uid://c4y8puv0n6xk8" +path="res://.godot/imported/craft.mp3-ba8d0c0c16ba90585f5d8eb4f37eb87d.mp3str" + +[deps] + +source_file="res://audio/music/world/craft.mp3" +dest_files=["res://.godot/imported/craft.mp3-ba8d0c0c16ba90585f5d8eb4f37eb87d.mp3str"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/drawing/surface_drawing_canvas.gd b/drawing/surface_drawing_canvas.gd index 2d4f506..f6ceb94 100644 --- a/drawing/surface_drawing_canvas.gd +++ b/drawing/surface_drawing_canvas.gd @@ -340,13 +340,13 @@ func _sample_cell_transform( tangent = Vector3.RIGHT var bitangent: Vector3 = normal.cross(tangent).normalized() var pixel_size: float = cell_size * clampf(fill, 0.05, 1.0) - var basis := Basis( + var cell_basis := Basis( tangent * pixel_size, bitangent * pixel_size, normal, ) return Transform3D( - basis, + cell_basis, to_local(point + normal * _surface_offset()), ) diff --git a/fishing/catch_controller.gd b/fishing/catch_controller.gd index e051561..62769aa 100644 --- a/fishing/catch_controller.gd +++ b/fishing/catch_controller.gd @@ -143,7 +143,7 @@ func start_authoritative_encounter( profile: CatchDifficultyProfileType, reel_speed: float, click_power: int, - seed: int, + encounter_seed_value: int, fish_quality: int = FishQualityType.Tier.BORING, fish_rarity: int = 0, fish_weight_percentile: float = 0.0, @@ -151,7 +151,7 @@ func start_authoritative_encounter( var previous_test_mode: bool = use_deterministic_test_seed var previous_seed: int = deterministic_test_seed use_deterministic_test_seed = true - deterministic_test_seed = seed + deterministic_test_seed = encounter_seed_value start_encounter( profile, reel_speed, 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/items/icons/shop/64_power_plus.png b/items/icons/shop/64_power_plus.png new file mode 100644 index 0000000..8aacdbc Binary files /dev/null and b/items/icons/shop/64_power_plus.png differ diff --git a/items/icons/shop/64_power_plus.png.import b/items/icons/shop/64_power_plus.png.import new file mode 100644 index 0000000..c82907d --- /dev/null +++ b/items/icons/shop/64_power_plus.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://xh2g4eytrjep" +path="res://.godot/imported/64_power_plus.png-98b7e06d3090a4411d56e2046b8c9d4b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://items/icons/shop/64_power_plus.png" +dest_files=["res://.godot/imported/64_power_plus.png-98b7e06d3090a4411d56e2046b8c9d4b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/main/main.gd b/main/main.gd index c4a6089..460ac62 100644 --- a/main/main.gd +++ b/main/main.gd @@ -96,6 +96,7 @@ const PlayerJobServiceType = preload("res://jobs/player_job_service.gd") const NetworkJobServiceType = preload("res://network/network_job_service.gd") const TITLE_MUSIC_SILENCE_DB: float = -80.0 +const TIME_CROSSING_EPSILON_HOURS: float = 0.000001 const PLAYER_MENU_PATTERN_SCALE: float = 0.85 const PLAYER_MENU_PATTERN_SCROLL_VELOCITY := Vector2(-7.0, -5.0) const SHOP_PATTERN_SCALE: float = 1.75 @@ -120,6 +121,7 @@ const SHOP_PATTERN_SCALE: float = 1.75 ) @onready var _interface_fonts: InterfaceFontController = %InterfaceFontController @onready var _title_music: AudioStreamPlayer = %TitleMusic +@onready var _dusk_music: AudioStreamPlayer = %DuskMusic @onready var _ui_pixelation: UIPixelationPresenterType = %UIPresentation @onready var _pixelation_reset: PixelationResetOverlayType = ( %PixelationResetOverlay @@ -191,6 +193,7 @@ var _shop_interaction: FishingShopInteractionType var _title_music_tween: Tween var _title_music_transition_generation: int = 0 var _title_music_requested: bool = false +var _dusk_music_played_for_natural_day: bool = false var _quit_in_progress: bool = false var _join_requested_from_title: bool = false var _join_requested_from_pause: bool = false @@ -316,6 +319,12 @@ func _initialize_after_data_root() -> void: _world_weather, _player, ) + if not _world_time.natural_time_advanced.is_connected( + _on_natural_time_advanced + ): + _world_time.natural_time_advanced.connect( + _on_natural_time_advanced + ) _network_world_time.setup(_network_session, _world_time) _network_world_weather.setup(_network_session, _world_weather) _player_jobs.setup( @@ -1184,9 +1193,18 @@ func _reset_pixelation() -> void: func _set_gameplay_active(active: bool) -> void: + var was_gameplay_started: bool = _gameplay_started _gameplay_started = active _shoreline_ambience.set_active(active) _rain_ambience.set_active(active) + if active and not was_gameplay_started: + var current_hour: float = _world_time.get_time_hours() + _dusk_music_played_for_natural_day = ( + current_hour >= WorldTimeServiceType.DUSK_START_HOUR + or current_hour < WorldTimeServiceType.DAWN_START_HOUR + ) + elif not active: + _dusk_music.stop() _title_background.visible = not active _world_pixelation.set_gameplay_active(active) _ui_pixelation.set_gameplay_active(active) @@ -1208,11 +1226,55 @@ func _set_gameplay_active(active: bool) -> void: _set_shop_backdrop_visible(false) -func _set_player_menu_backdrop_visible(is_visible: bool) -> void: +func _on_natural_time_advanced(advanced_hours: float) -> void: + if not _gameplay_started or advanced_hours <= 0.0: + return + var current_hour: float = _world_time.get_time_hours() + var previous_hour: float = fposmod( + current_hour - advanced_hours, + WorldTimeServiceType.HOURS_PER_DAY, + ) + if _natural_interval_crosses_hour( + previous_hour, + advanced_hours, + WorldTimeServiceType.DAWN_START_HOUR, + ): + _dusk_music_played_for_natural_day = false + if ( + not _dusk_music_played_for_natural_day + and _natural_interval_crosses_hour( + previous_hour, + advanced_hours, + WorldTimeServiceType.DUSK_START_HOUR, + ) + ): + _dusk_music_played_for_natural_day = true + _dusk_music.play(0.0) + + +static func _natural_interval_crosses_hour( + previous_hour: float, + advanced_hours: float, + target_hour: float, +) -> bool: + if advanced_hours >= WorldTimeServiceType.HOURS_PER_DAY: + return true + var distance_to_target: float = fposmod( + target_hour - previous_hour, + WorldTimeServiceType.HOURS_PER_DAY, + ) + return ( + distance_to_target > 0.0 + and distance_to_target + <= advanced_hours + TIME_CROSSING_EPSILON_HOURS + ) + + +func _set_player_menu_backdrop_visible(requested_visible: bool) -> void: if _player_menu_backdrop_tween != null: _player_menu_backdrop_tween.kill() _player_menu_backdrop_tween = null - var should_show: bool = is_visible and _gameplay_started + var should_show: bool = requested_visible and _gameplay_started if should_show: var was_visible: bool = _player_menu_backdrop.visible _player_menu_backdrop.visible = true @@ -1243,11 +1305,11 @@ func _set_player_menu_backdrop_visible(is_visible: bool) -> void: ) -func _set_shop_backdrop_visible(is_visible: bool) -> void: +func _set_shop_backdrop_visible(requested_visible: bool) -> void: if _shop_backdrop_tween != null: _shop_backdrop_tween.kill() _shop_backdrop_tween = null - var should_show: bool = is_visible and _gameplay_started + var should_show: bool = requested_visible and _gameplay_started if should_show: var was_visible: bool = _shop_backdrop.visible _shop_backdrop.visible = true @@ -1687,6 +1749,9 @@ func _exit_tree() -> void: if is_instance_valid(_title_music): _title_music.stop() _title_music.stream = null + if is_instance_valid(_dusk_music): + _dusk_music.stop() + _dusk_music.stream = null func _on_shop_range_changed(in_range: bool) -> void: diff --git a/main/main.tscn b/main/main.tscn index 6f52273..608b40a 100644 --- a/main/main.tscn +++ b/main/main.tscn @@ -13,6 +13,7 @@ [ext_resource type="Resource" path="res://items/catalog/item_catalog.tres" id="11_items"] [ext_resource type="Resource" path="res://economy/buyers/main_fishing_shop.tres" id="12_main_shop"] [ext_resource type="AudioStream" uid="uid://bgkfluumuen4m" path="res://audio/music/title/as_in_four_wolves.ogg" id="13_title_music"] +[ext_resource type="AudioStream" path="res://audio/music/world/craft.mp3" id="dusk_music"] [ext_resource type="Script" uid="uid://bdtb05vkiicd" path="res://ui/ui_pixelation_presenter.gd" id="14_pixelation"] [ext_resource type="PackedScene" path="res://ui/pixelation_reset_overlay.tscn" id="15_reset"] [ext_resource type="PackedScene" path="res://main/world_pixelation_postprocess.tscn" id="16_world_pixelation"] @@ -398,6 +399,11 @@ stream = ExtResource("13_title_music") volume_db = -80.0 bus = &"Music" +[node name="DuskMusic" type="AudioStreamPlayer" parent="."] +unique_name_in_owner = true +stream = ExtResource("dusk_music") +bus = &"Music" + [node name="ShorelineAmbience" type="Node" parent="." unique_id=1948037177] unique_name_in_owner = true script = ExtResource("54_shoreline_ambience") diff --git a/network/network_transport.gd b/network/network_transport.gd index 8b0cd05..ce68172 100644 --- a/network/network_transport.gd +++ b/network/network_transport.gd @@ -1,6 +1,7 @@ class_name NetworkTransport extends Node +@warning_ignore("unused_signal") signal transport_error(message: String) var _peer: MultiplayerPeer 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 = [ diff --git a/playtest/README-PLAYTEST.txt b/playtest/README-PLAYTEST.txt index 5be38b4..67e8870 100644 --- a/playtest/README-PLAYTEST.txt +++ b/playtest/README-PLAYTEST.txt @@ -54,7 +54,7 @@ FISHING - Coffee improves movement; other supplies temporarily improve fishing. - The Cooler starts at 12 fish; buy permanent capacity expansions at the shop. - Reel Speed upgrades increase authoritative green reeling progress. -- Barrier Power upgrades increase damage per valid barrier action. +- Rod Power upgrades increase damage per valid barrier action. - Shop upgrades persist in the local progression save. - Escape closes shop confirmations before closing the shop itself. diff --git a/settings/controller_mapping_manager.gd b/settings/controller_mapping_manager.gd index d5acd21..1690963 100644 --- a/settings/controller_mapping_manager.gd +++ b/settings/controller_mapping_manager.gd @@ -913,7 +913,7 @@ func _add_button_action_binding(action: StringName, value: Variant) -> void: return var event := InputEventJoypadButton.new() event.device = _active_device_id - event.button_index = int(binding.get("button", -1)) + event.button_index = int(binding.get("button", -1)) as JoyButton InputMap.action_add_event(action, event) @@ -932,7 +932,7 @@ func _add_axis_action_binding( ) var event := InputEventJoypadMotion.new() event.device = _active_device_id - event.axis = int(binding.get("axis", -1)) + event.axis = int(binding.get("axis", -1)) as JoyAxis event.axis_value = logical_direction * -captured_negative_direction InputMap.action_add_event(action, event) diff --git a/ui/animalese_voice.gd b/ui/animalese_voice.gd index c6372ee..9a75434 100644 --- a/ui/animalese_voice.gd +++ b/ui/animalese_voice.gd @@ -34,13 +34,13 @@ func _ready() -> void: func speak_text( - owner: Node, + tween_owner: Node, text: String, voice_key: String, voice_profile_id: String = VoiceProfilesType.DEFAULT_ID, characters_per_second: float = -1.0, ) -> Tween: - var speech_tween := owner.create_tween() + var speech_tween := tween_owner.create_tween() var resolved_characters_per_second := ( characters_per_second if characters_per_second > 0.0 @@ -108,7 +108,8 @@ func _play_character( - 0.5 ) * 0.10 var question_lift := 0.0 - if full_text.ends_with("?") and character_index >= full_text.length() * 3 / 4: + var question_lift_start: int = floori(float(full_text.length()) * 0.75) + if full_text.ends_with("?") and character_index >= question_lift_start: var final_progress := ( float(character_index) / maxf(float(full_text.length() - 1), 1.0) ) diff --git a/ui/components/organizer_tab.gd b/ui/components/organizer_tab.gd index 8db5122..3625b9a 100644 --- a/ui/components/organizer_tab.gd +++ b/ui/components/organizer_tab.gd @@ -93,14 +93,14 @@ func _initialize_motion() -> void: _set_visual_y(_target_y()) -func _on_toggled(pressed: bool) -> void: - if _selected and not pressed: +func _on_toggled(is_pressed: bool) -> void: + if _selected and not is_pressed: # A selected organizer tab is a page marker, not a collapsible toggle. # Restore without another signal before any lower-state frame is drawn. set_pressed_no_signal(true) refresh_state(false) return - _selected = pressed + _selected = is_pressed refresh_state() diff --git a/ui/controller_mapping_panel.gd b/ui/controller_mapping_panel.gd index 555f499..42fdf92 100644 --- a/ui/controller_mapping_panel.gd +++ b/ui/controller_mapping_panel.gd @@ -389,22 +389,22 @@ func _accept_captured_binding(binding: Dictionary) -> void: ControllerMappingManagerType.ROLE_ORDER[_auto_map_index] ) return - var saved: bool = _mapping_manager.replace_active_bindings( + var profile_saved: bool = _mapping_manager.replace_active_bindings( _auto_map_draft ) _cancel_capture() _progress_label.text = ( "controller mapped successfully" - if saved else "could not save the controller mapping" + if profile_saved else "could not save the controller mapping" ) _refresh_bindings() return var role: StringName = _capturing_role - var saved: bool = _mapping_manager.set_binding(role, binding) + var binding_saved: bool = _mapping_manager.set_binding(role, binding) _cancel_capture() _progress_label.text = ( "updated " + _mapping_manager.get_role_label(role) - if saved else "could not save that controller input" + if binding_saved else "could not save that controller input" ) _refresh_bindings() diff --git a/ui/fishing_shop.gd b/ui/fishing_shop.gd index 12234c6..8e776bb 100644 --- a/ui/fishing_shop.gd +++ b/ui/fishing_shop.gd @@ -525,7 +525,7 @@ func _refresh_supplies() -> void: FishingShopStockType.get_price(item_id), owned, ] - var tooltip_text: String = item.description + var item_tooltip_text: String = item.description if bait_topoff: if item.icon != null: button.custom_minimum_size = Vector2(72, 72) @@ -543,7 +543,7 @@ func _refresh_supplies() -> void: item.max_stack, ] ) - tooltip_text = ( + item_tooltip_text = ( "%s\nunlock $%d • fills to %d/%d\n%s" % [ item.display_name, total_cost, @@ -573,7 +573,7 @@ func _refresh_supplies() -> void: button.tooltip_text = ( "Purchases are unavailable in this session." if _network_shop == null or not _network_shop.can_request_purchase() - else tooltip_text + else item_tooltip_text ) UtilityPageStyleType.apply_ocean_button(button) button.pressed.connect(_purchase_supply.bind(item_id)) diff --git a/ui/fishing_shop.tscn b/ui/fishing_shop.tscn index ef10a3c..58d2961 100644 --- a/ui/fishing_shop.tscn +++ b/ui/fishing_shop.tscn @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://ui/fishing_shop.gd" id="1_script"] [ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"] [ext_resource type="Texture2D" path="res://items/icons/placeholder/reel_speed_upgrade.svg" id="3_reel"] -[ext_resource type="Texture2D" path="res://items/icons/placeholder/barrier_power_upgrade.svg" id="4_barrier"] +[ext_resource type="Texture2D" path="res://items/icons/shop/64_power_plus.png" id="4_barrier"] [ext_resource type="Texture2D" path="res://items/icons/placeholder/fish_coin.svg" id="5_coin"] [ext_resource type="Texture2D" path="res://items/icons/equipment/64_cooler_plus.png" id="6_cooler"] @@ -277,7 +277,7 @@ size_flags_horizontal = 3 [node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"] layout_mode = 2 -text = "barrier power" +text = "rod power" theme_override_font_sizes/font_size = 22 [node name="BarrierLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"] diff --git a/ui/game_ui.gd b/ui/game_ui.gd index 5e1c19a..feab180 100644 --- a/ui/game_ui.gd +++ b/ui/game_ui.gd @@ -125,7 +125,6 @@ const SHOP_NPC_SPEECH_COOLDOWN_MILLISECONDS: int = 5000 @onready var _canonical_stage: Control = %CanonicalStage -@onready var _ui_root: Control = %UIRoot @onready var _player_menu: PlayerMenuType = %PlayerMenu @onready var _screen_fade: ScreenFade = %ScreenFade @onready var _title_screen: TitleScreenType = %TitleScreen @@ -1458,12 +1457,12 @@ func get_fishing_shop() -> FishingShopType: func set_shop_prompt_visible( - is_visible: bool, + requested_visible: bool, world_anchor: Vector3 = Vector3(0.0, INF, 0.0), ) -> void: _apply_shop_prompt_style() _shop_prompt.visible = ( - is_visible + requested_visible and _gameplay_ui_enabled and not _system_menu_open and not _player_menu_open @@ -1688,8 +1687,8 @@ func _refresh_fishing_panel_visibility() -> void: ) -func _on_bite_prompt_changed(is_visible: bool) -> void: - _bite_prompt_button.visible = is_visible +func _on_bite_prompt_changed(prompt_visible: bool) -> void: + _bite_prompt_button.visible = prompt_visible _refresh_fishing_panel_visibility() @@ -1705,10 +1704,10 @@ func _on_catch_display_changed( barrier_health: PackedInt32Array, _barrier_max_health: PackedInt32Array, active_barrier_index: int, - visible: bool, + encounter_requested_visible: bool, ) -> void: var encounter_visible: bool = ( - visible + encounter_requested_visible and _fishing_spot != null and _fishing_spot.is_fighting() ) @@ -1789,10 +1788,10 @@ func _on_showcase_changed( rarity_name: String, weight_lb: float, quality: int, - visible: bool, + showcase_visible: bool, ) -> void: - _showcase_active = visible - if not visible: + _showcase_active = showcase_visible + if not showcase_visible: _set_fishing_panel_showcase_position(false) _showcase_details.text = "" _showcase_details.visible = false @@ -2195,10 +2194,10 @@ func _on_controller_hotbar_management_ended() -> void: func _on_hotbar_presentation_transition_finished( - is_visible: bool, + presentation_visible: bool, ) -> void: _hotbar_ui.set_drag_enabled( - is_visible + presentation_visible and _player_menu_open and _player_menu_hotbar_visible and not _system_menu_open diff --git a/ui/logbook_page.gd b/ui/logbook_page.gd index a7d4767..55c6abb 100644 --- a/ui/logbook_page.gd +++ b/ui/logbook_page.gd @@ -355,7 +355,7 @@ func _make_scroll_indicator( return indicator -func _on_catalog_scroll_value_changed(value: float) -> void: +func _on_catalog_scroll_value_changed(_value: float) -> void: if _snapping_catalog_scroll: return _catalog_scroll_snap_timer.start() diff --git a/ui/mail_page.gd b/ui/mail_page.gd index c89d618..5380a1c 100644 --- a/ui/mail_page.gd +++ b/ui/mail_page.gd @@ -1,8 +1,6 @@ class_name MailPage extends Control -signal focus_requested - const GREETING_LABELS := { "dear": "Dear", "to": "To", diff --git a/ui/player_menu.gd b/ui/player_menu.gd index bb1b418..300f9b1 100644 --- a/ui/player_menu.gd +++ b/ui/player_menu.gd @@ -214,7 +214,6 @@ const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0) @onready var _bag_item_field: Control = %BagItemField @onready var _bag_empty_state: Label = %BagEmptyState @onready var _bag_detail_constellation: Control = %BagDetailConstellation -@onready var _bag_sprite_detail_bubble: Control = %BagDetailBubble @onready var _bag_sprite_detail_texture: TextureRect = %BagSpriteDetailTexture @onready var _bag_sprite_detail_name: Label = %BagSpriteDetailName @onready var _bag_sprite_detail_data: Label = %BagSpriteDetailData @@ -228,7 +227,6 @@ const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0) @onready var _book_spread: BoxContainer = %BookSpread @onready var _left_page: PanelContainer = %LeftPage @onready var _right_page: PanelContainer = %RightPage -@onready var _book_gutter: ColorRect = %BookGutter @onready var _left_heading: Label = %LeftHeading @onready var _right_heading: Label = %RightHeading @onready var _left_entry_field: BoxContainer = %LeftEntryField @@ -259,7 +257,6 @@ const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0) @onready var _offer_status: BubbleStatusBubbleType = %OfferStatus @onready var _inventory_section: Control = %InventorySection @onready var _bag_section: Control = %BagSection -@onready var _logbook_section: Control = %LogbookSection @onready var _bag_empty: Label = %BagEmpty @onready var _bag_grid: GridContainer = %BagGrid @onready var _bag_list: Control = %BagList @@ -272,9 +269,6 @@ const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0) @onready var _held_value: Label = %HeldValue @onready var _cooler_count: Label = %CoolerCount @onready var _inventory_empty: Label = %InventoryEmpty -@onready var _detail_texture: TextureRect = %DetailTexture -@onready var _detail_name: Label = %DetailName -@onready var _detail_data: Label = %DetailData @onready var _selection_summary: Label = %SelectionSummary @onready var _favorite_button: Button = %FavoriteButton @onready var _sell_button: Button = %SellButton @@ -285,7 +279,6 @@ const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0) @onready var _confirm_sale_button: Button = %ConfirmSaleButton @onready var _cancel_sale_button: Button = %CancelSaleButton @onready var _logbook_empty: Label = %LogbookEmpty -@onready var _logbook_grid: GridContainer = %LogbookGrid var _compact_layout: bool = false var _player: PlayerType @@ -1196,11 +1189,9 @@ func _collect_visible_toggle_buttons( or not navigation_cluster.is_ancestor_of(button) ) ): - var group_key: Variant = ( - button.button_group - if button.button_group != null - else button.get_parent() - ) + var group_key: Variant = button.get_parent() + if button.button_group != null: + group_key = button.button_group if not grouped_buttons.has(group_key): grouped_buttons[group_key] = [] var buttons := grouped_buttons[group_key] as Array @@ -3355,7 +3346,7 @@ func _update_bag_detail() -> void: item_state = "equippable" elif item.usable: item_state = "usable" - _bag_detail_texture.texture = item.icon if item != null else null + _bag_detail_texture.texture = null _bag_detail_name.text = item.display_name if item != null else "" _bag_detail_data.text = ( "%s\nquantity: %d\n%s\n%s\n%s" @@ -3369,7 +3360,10 @@ func _update_bag_detail() -> void: if item != null else "select a bag item for details." ) - _bag_sprite_detail_texture.texture = item.icon if item != null else null + _bag_sprite_detail_texture.texture = null + if item != null: + _bag_detail_texture.texture = item.icon + _bag_sprite_detail_texture.texture = item.icon _bag_sprite_detail_name.text = ( item.display_name if item != null else "" ) @@ -3857,11 +3851,6 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void: _favorite_bubble.refresh_ink_state() _detail_constellation.visible = _current_section == Section.COOLER _cooler_sort_controls.visible = true - var detail_interactive: bool = ( - _detail_constellation.visible - and not _transitioning - and not _page_transitioning - ) _refresh_cooler_notepad_action_interactivity() _configure_cooler_fish_focus() @@ -3881,16 +3870,12 @@ func _refresh_cooler_notepad_action_interactivity() -> void: _sell_all_bubble, ]: var action_interactive: bool = detail_interactive and not action.disabled - action.focus_mode = ( - Control.FOCUS_ALL - if action_interactive - else Control.FOCUS_NONE - ) - action.mouse_filter = ( - Control.MOUSE_FILTER_STOP - if action_interactive - else Control.MOUSE_FILTER_IGNORE - ) + if action_interactive: + action.focus_mode = Control.FOCUS_ALL + action.mouse_filter = Control.MOUSE_FILTER_STOP + else: + action.focus_mode = Control.FOCUS_NONE + action.mouse_filter = Control.MOUSE_FILTER_IGNORE action.refresh_ink_state() diff --git a/ui/settings_panel.gd b/ui/settings_panel.gd index 857ce4b..1b10bfd 100644 --- a/ui/settings_panel.gd +++ b/ui/settings_panel.gd @@ -720,7 +720,7 @@ func _identity_operation_allowed() -> bool: return true -func _on_identity_operation_finished(success: bool, message: String) -> void: +func _on_identity_operation_finished(_success: bool, message: String) -> void: _feedback.text = message diff --git a/ui/weather_icon.gd b/ui/weather_icon.gd index e14f3dc..dbe41b2 100644 --- a/ui/weather_icon.gd +++ b/ui/weather_icon.gd @@ -39,10 +39,10 @@ func set_weather(weather: WorldWeatherService.Weather) -> void: queue_redraw() -func set_nighttime(is_nighttime: bool) -> void: - if _is_nighttime == is_nighttime: +func set_nighttime(nighttime: bool) -> void: + if _is_nighttime == nighttime: return - _is_nighttime = is_nighttime + _is_nighttime = nighttime queue_redraw() diff --git a/world/materials/foliage_wind.gdshader b/world/materials/foliage_wind.gdshader new file mode 100644 index 0000000..a1220e7 --- /dev/null +++ b/world/materials/foliage_wind.gdshader @@ -0,0 +1,44 @@ +shader_type spatial; +render_mode cull_back, depth_draw_opaque; + +uniform vec4 albedo_color : source_color = vec4(1.0); +uniform float material_roughness = 1.0; +uniform float material_metallic = 0.0; +uniform float local_min_y = 0.0; +uniform float local_height = 1.0; +uniform float local_wind_strength = 0.08; +uniform float wind_speed = 0.9; +uniform float wind_phase = 0.0; + + +void vertex() { + float height_ratio = clamp( + (VERTEX.y - local_min_y) / max(local_height, 0.001), + 0.0, + 1.0 + ); + float bend_mask = smoothstep(0.08, 1.0, height_ratio); + vec3 world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; + float broad_wave = sin( + TIME * wind_speed + + dot(world_position.xz, vec2(0.23, 0.17)) + + wind_phase + ); + float leaf_flutter = sin( + TIME * wind_speed * 2.17 + + dot(world_position.xz, vec2(-0.41, 0.37)) + + wind_phase * 1.7 + ); + float displacement = ( + broad_wave * 0.72 + leaf_flutter * 0.28 + ) * local_wind_strength * bend_mask; + VERTEX.x += displacement; + VERTEX.z += displacement * 0.42; +} + + +void fragment() { + ALBEDO = albedo_color.rgb; + ROUGHNESS = material_roughness; + METALLIC = material_metallic; +} diff --git a/world/materials/foliage_wind.gdshader.uid b/world/materials/foliage_wind.gdshader.uid new file mode 100644 index 0000000..e5162a5 --- /dev/null +++ b/world/materials/foliage_wind.gdshader.uid @@ -0,0 +1 @@ +uid://c3q7l8m9n2v4x diff --git a/world/regions/starter_island_region.gd b/world/regions/starter_island_region.gd index 01e244f..d4b21a7 100644 --- a/world/regions/starter_island_region.gd +++ b/world/regions/starter_island_region.gd @@ -5,6 +5,14 @@ extends WorldRegion const FishingShopInteractionType = preload( "res://world/fishing_shop_interaction.gd" ) +const FOLIAGE_WIND_SHADER: Shader = preload( + "res://world/materials/foliage_wind.gdshader" +) +const FOLIAGE_MATERIAL_NAMES: Array[StringName] = [ + &"leaf", + &"leaf_light", + &"leaf_dark", +] @export_group("Owned Nodes") @export_node_path("MeshInstance3D") @@ -25,10 +33,14 @@ var fishing_shop_path: NodePath = ( ) @export_group("Terrain Collision") @export var rebuild_terrain_collision_on_ready: bool = false +@export_group("Foliage Wind") +@export_range(0.0, 0.4, 0.005) var foliage_wind_strength: float = 0.12 +@export_range(0.0, 4.0, 0.05) var foliage_wind_speed: float = 0.9 func _ready() -> void: if rebuild_terrain_collision_on_ready or not has_terrain_collision(): _build_terrain_collision() + _apply_foliage_wind() if Engine.is_editor_hint(): update_configuration_warnings() @@ -107,6 +119,73 @@ func _collect_terrain_mesh_instances(root: Node) -> Array[MeshInstance3D]: return mesh_instances +func _apply_foliage_wind() -> void: + var terrain_root: Node = get_node_or_null(terrain_visual_root_path) + if terrain_root == null: + return + for mesh_instance: MeshInstance3D in _collect_terrain_mesh_instances( + terrain_root + ): + _apply_foliage_wind_to_mesh(mesh_instance) + + +func _apply_foliage_wind_to_mesh(mesh_instance: MeshInstance3D) -> void: + if mesh_instance.mesh == null: + return + var bounds: AABB = mesh_instance.get_aabb() + var mesh_global_scale: Vector3 = mesh_instance.global_basis.get_scale().abs() + var horizontal_scale: float = maxf( + (mesh_global_scale.x + mesh_global_scale.z) * 0.5, + 0.001, + ) + var local_strength: float = foliage_wind_strength / horizontal_scale + var phase: float = fposmod( + mesh_instance.global_position.x * 0.73 + + mesh_instance.global_position.z * 0.41, + TAU, + ) + for surface_index: int in mesh_instance.mesh.get_surface_count(): + var source_material: Material = mesh_instance.get_active_material( + surface_index + ) + if not source_material is StandardMaterial3D: + continue + if not FOLIAGE_MATERIAL_NAMES.has( + StringName(source_material.resource_name) + ): + continue + var source_standard := source_material as StandardMaterial3D + var wind_material := ShaderMaterial.new() + wind_material.shader = FOLIAGE_WIND_SHADER + wind_material.set_shader_parameter( + "albedo_color", + source_standard.albedo_color, + ) + wind_material.set_shader_parameter( + "material_roughness", + source_standard.roughness, + ) + wind_material.set_shader_parameter( + "material_metallic", + source_standard.metallic, + ) + wind_material.set_shader_parameter("local_min_y", bounds.position.y) + wind_material.set_shader_parameter( + "local_height", + maxf(bounds.size.y, 0.001), + ) + wind_material.set_shader_parameter( + "local_wind_strength", + local_strength, + ) + wind_material.set_shader_parameter("wind_speed", foliage_wind_speed) + wind_material.set_shader_parameter("wind_phase", phase) + mesh_instance.set_surface_override_material( + surface_index, + wind_material, + ) + + func _get_configuration_warnings() -> PackedStringArray: var warnings := PackedStringArray() var visual_mesh := get_node_or_null(visual_mesh_path) as MeshInstance3D diff --git a/world/water/shoreline_ribbon_generator.gd b/world/water/shoreline_ribbon_generator.gd index c7c7e6e..7af9366 100644 --- a/world/water/shoreline_ribbon_generator.gd +++ b/world/water/shoreline_ribbon_generator.gd @@ -481,4 +481,6 @@ static func _mesh_triangle_count(mesh: ArrayMesh) -> int: if mesh.get_surface_count() == 0: return 0 var arrays := mesh.surface_get_arrays(0) - return (arrays[Mesh.ARRAY_INDEX] as PackedInt32Array).size() / 3 + return floori( + float((arrays[Mesh.ARRAY_INDEX] as PackedInt32Array).size()) / 3.0 + )