Compare commits

...

6 commits

35 changed files with 415 additions and 88 deletions

3
.gitignore vendored
View file

@ -2,3 +2,6 @@
.godot/
/android/
/builds/
# Blender backup files
*.blend1

BIN
audio/music/world/craft.mp3 Normal file

Binary file not shown.

View file

@ -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

View file

@ -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()),
)

View file

@ -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,

View file

@ -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

View file

@ -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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View file

@ -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

View file

@ -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:

View file

@ -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")

View file

@ -1,6 +1,7 @@
class_name NetworkTransport
extends Node
@warning_ignore("unused_signal")
signal transport_error(message: String)
var _peer: MultiplayerPeer

View file

@ -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 = [

View file

@ -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.

View file

@ -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)

View file

@ -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)
)

View file

@ -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()

View file

@ -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()

View file

@ -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))

View file

@ -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"]

View file

@ -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

View file

@ -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()

View file

@ -1,8 +1,6 @@
class_name MailPage
extends Control
signal focus_requested
const GREETING_LABELS := {
"dear": "Dear",
"to": "To",

View file

@ -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()

View file

@ -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

View file

@ -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()

View file

@ -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;
}

View file

@ -0,0 +1 @@
uid://c3q7l8m9n2v4x

View file

@ -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

View file

@ -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
)