Refresh menus chat and player expression

This commit is contained in:
Alexander Sellite 2026-08-08 00:34:06 -04:00
parent 2a5e0a8f26
commit 0d50c324e9
127 changed files with 3222 additions and 221 deletions

123
ui/animalese_voice.gd Normal file
View file

@ -0,0 +1,123 @@
class_name AnimaleseVoice
extends Node
const SAMPLE_DIRECTORY := "res://sound/dialogue/animalese/placeholder"
const SUPPORTED_CHARACTERS := "abcdefghijklmnopqrstuvwxyz"
const DEFAULT_CHARACTERS_PER_SECOND: float = 28.0
const POLYPHONY: int = 8
const VoiceProfilesType = preload(
"res://player/animalese_voice_profiles.gd"
)
const TypewriterRevealType = preload("res://ui/typewriter_reveal.gd")
var base_pitch: float = 1.0
var volume_db: float = -13.0
var _samples: Dictionary[String, AudioStream] = {}
var _player: AudioStreamPlayer
var _playback: AudioStreamPlaybackPolyphonic
func _ready() -> void:
_load_samples()
var polyphonic_stream := AudioStreamPolyphonic.new()
polyphonic_stream.polyphony = POLYPHONY
_player = AudioStreamPlayer.new()
_player.name = "AnimaleseAudio"
_player.stream = polyphonic_stream
add_child(_player)
_player.play()
_playback = (
_player.get_stream_playback() as AudioStreamPlaybackPolyphonic
)
func speak_text(
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 resolved_characters_per_second := (
characters_per_second
if characters_per_second > 0.0
else TypewriterRevealType.get_characters_per_second()
)
var character_seconds := 1.0 / maxf(
resolved_characters_per_second,
1.0,
)
var voice_pitch := (
base_pitch * VoiceProfilesType.pitch_for(voice_profile_id)
)
for character_index: int in range(text.length()):
speech_tween.tween_interval(character_seconds)
speech_tween.tween_callback(
_play_character.bind(
text.substr(character_index, 1),
character_index,
text,
voice_key,
voice_pitch,
)
)
return speech_tween
func _load_samples() -> void:
for character_index: int in range(SUPPORTED_CHARACTERS.length()):
var character := SUPPORTED_CHARACTERS.substr(character_index, 1)
_load_sample(character)
_load_sample("fallback")
func _load_sample(sample_name: String) -> void:
var sample_path := "%s/%s.wav" % [SAMPLE_DIRECTORY, sample_name]
var sample := load(sample_path) as AudioStream
if sample != null:
_samples[sample_name] = sample
func _play_character(
character: String,
character_index: int,
full_text: String,
voice_key: String,
voice_pitch: float,
) -> void:
if _playback == null or character.strip_edges().is_empty():
return
var normalized := character.to_lower()
if normalized in [".", ",", "!", "?", ":", ";", "-", "_"]:
return
var sample_name := (
normalized if SUPPORTED_CHARACTERS.contains(normalized) else "fallback"
)
var sample: AudioStream = _samples.get(sample_name)
if sample == null:
return
var speaker_variation := (
float(posmod(hash(voice_key), 1001)) / 1000.0 - 0.5
) * 0.16
var character_variation := (
float(posmod(hash("%s:%d" % [voice_key, character_index]), 1001))
/ 1000.0
- 0.5
) * 0.10
var question_lift := 0.0
if full_text.ends_with("?") and character_index >= full_text.length() * 3 / 4:
var final_progress := (
float(character_index) / maxf(float(full_text.length() - 1), 1.0)
)
question_lift = lerpf(0.0, 0.14, final_progress)
var pitch := clampf(
voice_pitch
+ speaker_variation
+ character_variation
+ question_lift,
0.72,
1.45,
)
_playback.play_stream(sample, 0.0, volume_db, pitch)

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bnwbcopsjmi3s"
path="res://.godot/imported/ui_logbook.png-e45558834671114c04857bb4a6091575.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/assets/logbook/ui_logbook.png"
dest_files=["res://.godot/imported/ui_logbook.png-e45558834671114c04857bb4a6091575.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

@ -6,6 +6,7 @@ const RECENT_SECONDS: float = 8.0
const SPEECH_SECONDS: float = 6.0
const DRAFT_SAVE_DELAY: float = 0.4
const IDLE_ALPHA: float = 0.58
const CHAT_SURFACE_COLOR := Color(0.025, 0.13, 0.19, 0.94)
const ORIGINAL_PANEL_WIDTH: float = 390.0
const PANEL_WIDTH: float = ORIGINAL_PANEL_WIDTH * 0.85
const COMPACT_HEIGHT: float = 250.0
@ -33,12 +34,31 @@ const CLOCK_SIZE := Vector2(116.0, 34.0)
const CLOCK_EDGE_MARGIN: float = 10.0
const WEATHER_ICON_SIZE := Vector2(34.0, 34.0)
const WEATHER_ICON_GAP: float = 6.0
const CHAT_SHOW_ICON: Texture2D = preload(
"res://ui/icons/pictograms/arrow_light_right_more.png"
)
const CHAT_HIDE_ICON: Texture2D = preload(
"res://ui/icons/pictograms/arrow_light_left_more.png"
)
const CHAT_EXPAND_ICON: Texture2D = preload(
"res://ui/icons/pictograms/arrow_light_up_more.png"
)
const CHAT_COMPACT_ICON: Texture2D = preload(
"res://ui/icons/pictograms/arrow_light_down_more.png"
)
const WorldTimeServiceType = preload("res://world/world_time_service.gd")
const WorldWeatherServiceType = preload(
"res://world/world_weather_service.gd"
)
const WeatherIconType = preload("res://ui/weather_icon.gd")
const TypewriterRevealType = preload("res://ui/typewriter_reveal.gd")
const AnimaleseVoiceType = preload("res://ui/animalese_voice.gd")
const VoiceProfilesType = preload(
"res://player/animalese_voice_profiles.gd"
)
enum PresentationState {
COLLAPSED,
COMPACT,
@ -62,6 +82,7 @@ var _height_button: Button
var _unread_indicator: Label
var _hint: Label
var _speech_layer: Control
var _animalese_voice: AnimaleseVoiceType
var _clock_panel: PanelContainer
var _clock_label: Label
var _weather_icon: WeatherIconType
@ -379,6 +400,15 @@ func _build_ui() -> void:
_collapse_button.mouse_filter = Control.MOUSE_FILTER_STOP
_collapse_button.focus_mode = Control.FOCUS_ALL
_collapse_button.z_index = 3
_collapse_button.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
_collapse_button.expand_icon = true
_collapse_button.alignment = HORIZONTAL_ALIGNMENT_CENTER
_collapse_button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
_collapse_button.vertical_icon_alignment = VERTICAL_ALIGNMENT_CENTER
_collapse_button.add_theme_constant_override(
"icon_max_width",
roundi(minf(HANDLE_SIZE.x, HANDLE_SIZE.y) * 0.5),
)
_collapse_button.tooltip_text = "Collapse chat"
_collapse_button.pressed.connect(func() -> void:
if _presentation_state == PresentationState.COLLAPSED:
@ -405,6 +435,15 @@ func _build_ui() -> void:
_height_button.mouse_filter = Control.MOUSE_FILTER_STOP
_height_button.focus_mode = Control.FOCUS_ALL
_height_button.z_index = 3
_height_button.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
_height_button.expand_icon = true
_height_button.alignment = HORIZONTAL_ALIGNMENT_CENTER
_height_button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
_height_button.vertical_icon_alignment = VERTICAL_ALIGNMENT_CENTER
_height_button.add_theme_constant_override(
"icon_max_width",
roundi(minf(HANDLE_SIZE.x, HANDLE_SIZE.y) * 0.5),
)
_height_button.pressed.connect(func() -> void:
if _presentation_state == PresentationState.COLLAPSED:
_visible_state_before_collapse = (
@ -461,6 +500,9 @@ func _build_ui() -> void:
_speech_layer.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_speech_layer.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_speech_layer)
_animalese_voice = AnimaleseVoiceType.new()
_animalese_voice.name = "PlayerAnimaleseVoice"
add_child(_animalese_voice)
_draft_save_timer = Timer.new()
_draft_save_timer.one_shot = true
_draft_save_timer.wait_time = DRAFT_SAVE_DELAY
@ -478,7 +520,7 @@ func _build_ui() -> void:
func _chat_panel_style() -> StyleBoxFlat:
var style := _borderless_style(Color(0.025, 0.13, 0.19, 0.94))
var style := _borderless_style(CHAT_SURFACE_COLOR)
if _mobile_mode:
style.corner_radius_top_left = 0
style.corner_radius_top_right = 0
@ -509,7 +551,7 @@ func _refresh_input_ownership() -> void:
func _clock_panel_style() -> StyleBoxFlat:
var style := _borderless_style(Color(0.025, 0.13, 0.19, 0.94))
var style := _borderless_style(CHAT_SURFACE_COLOR)
style.set_corner_radius_all(12)
style.content_margin_left = 10
style.content_margin_right = 10
@ -522,6 +564,10 @@ func _borderless_style(color: Color) -> StyleBoxFlat:
var style := StyleBoxFlat.new()
style.bg_color = color
style.set_border_width_all(0)
style.anti_aliasing = false
style.shadow_size = 0
style.shadow_color = Color.TRANSPARENT
style.shadow_offset = Vector2.ZERO
return style
@ -530,6 +576,12 @@ func _flat_chat_button_style(color: Color) -> StyleBoxFlat:
style.bg_color = color
style.set_border_width_all(0)
style.set_corner_radius_all(0)
if _dock_right:
style.corner_radius_top_left = 10
style.corner_radius_bottom_left = 10
else:
style.corner_radius_top_right = 10
style.corner_radius_bottom_right = 10
style.anti_aliasing = false
style.shadow_size = 0
style.shadow_color = Color.TRANSPARENT
@ -572,6 +624,18 @@ func _apply_flat_chat_button(button: Button) -> void:
button.add_theme_stylebox_override(
"disabled", _flat_chat_button_style(UtilityPageStyle.OCEAN_DISABLED)
)
for state: StringName in [
&"normal",
&"hover",
&"pressed",
&"hover_pressed",
&"focus",
&"disabled",
]:
button.add_theme_stylebox_override(
state,
_flat_chat_button_style(CHAT_SURFACE_COLOR),
)
func _speech_panel_style() -> StyleBoxFlat:
@ -605,6 +669,8 @@ func _send() -> void:
if _send_pending:
return
var body := _entry.text
if _handle_chat_command(body):
return
_send_pending = true
_pending_send_body = NetworkChatProtocol.sanitize_body(body)
_entry.editable = false
@ -616,6 +682,59 @@ func _send() -> void:
_set_status("Sending…")
func _handle_chat_command(body: String) -> bool:
var command_text := body.strip_edges()
if not command_text.begins_with("/"):
return false
var parts: PackedStringArray = command_text.split(" ", false)
var command := String(parts[0]).trim_prefix("/").to_lower()
match command:
"time":
_handle_time_command(parts)
"":
_set_status("Enter a command after /.")
_:
_set_status("Unknown command: /%s" % command)
_entry.clear()
_flush_draft()
return true
func _handle_time_command(parts: PackedStringArray) -> void:
if parts.size() != 2:
_set_status("Usage: /time [dawn, day, dusk, night]")
return
if _session == null or not _session.is_host():
_set_status("Only the host can change world time.")
return
if _world_time == null:
_set_status("World time is unavailable.")
return
var phase_name := String(parts[1]).to_lower()
var target_hour: float
match phase_name:
"dawn":
target_hour = WorldTimeServiceType.DAWN_START_HOUR
"day":
target_hour = WorldTimeServiceType.DAWN_END_HOUR
"dusk":
target_hour = WorldTimeServiceType.DUSK_START_HOUR
"night":
target_hour = WorldTimeServiceType.DUSK_END_HOUR
_:
_set_status("Usage: /time [dawn, day, dusk, night]")
return
if not _world_time.set_authoritative_time(target_hour):
_set_status("World time could not be changed.")
return
_set_status("")
_service.broadcast_system_message(
"World time set to %s (%s)."
% [phase_name, _world_time.get_clock_text()]
)
close_chat()
func _on_local_message_confirmed(message: Dictionary) -> void:
if (
not _send_pending
@ -664,10 +783,27 @@ func _on_message(message: Dictionary) -> void:
)
bubble.add_child(label)
_speech_layer.add_child(bubble)
var reveal_seconds := TypewriterRevealType.start(label)
var voice_profile_id: String = VoiceProfilesType.DEFAULT_ID
var speaker_avatar := _spawn.get_avatar(peer_id)
if speaker_avatar != null:
voice_profile_id = VoiceProfilesType.sanitized_id(
speaker_avatar.get_animalese_voice_id()
)
_animalese_voice.speak_text(
label,
label.text,
str(peer_id),
voice_profile_id,
)
_speech[peer_id] = {
"bubble": bubble,
"pointer": pointer,
"expires": Time.get_ticks_msec() / 1000.0 + SPEECH_SECONDS,
"expires": (
Time.get_ticks_msec() / 1000.0
+ reveal_seconds
+ SPEECH_SECONDS
),
"fingerprint": str(message.get("sender_fingerprint", "")),
}
@ -770,6 +906,8 @@ func set_dock_right(should_dock_right: bool) -> void:
if not is_node_ready() or _panel == null:
return
_panel.add_theme_stylebox_override("panel", _chat_panel_style())
_apply_flat_chat_button(_collapse_button)
_apply_flat_chat_button(_height_button)
_refresh_handle_labels(_presentation_state)
_layout_presentation(false)
@ -800,21 +938,24 @@ func is_mobile_mode() -> bool:
func _refresh_handle_labels(state: PresentationState) -> void:
var collapsed := state == PresentationState.COLLAPSED
var height_state := _visible_state_before_collapse if collapsed else state
if _mobile_mode:
_collapse_button.text = "v" if collapsed else "^"
elif _dock_right:
_collapse_button.text = "<" if collapsed else ">"
else:
_collapse_button.text = ">" if collapsed else "<"
_collapse_button.text = ""
_collapse_button.icon = CHAT_SHOW_ICON if collapsed else CHAT_HIDE_ICON
_collapse_button.tooltip_text = (
"Show chat" if collapsed else "Hide chat"
)
_height_button.text = "v" if height_state == PresentationState.EXPANDED else "^"
_collapse_button.accessibility_name = _collapse_button.tooltip_text
_height_button.text = ""
_height_button.icon = (
CHAT_COMPACT_ICON
if height_state == PresentationState.EXPANDED
else CHAT_EXPAND_ICON
)
_height_button.tooltip_text = (
"Compact chat"
if height_state == PresentationState.EXPANDED
else "Expand chat history"
)
_height_button.accessibility_name = _height_button.tooltip_text
func _update_panel_opacity(immediate_recheck: bool = false) -> void:
@ -823,10 +964,8 @@ func _update_panel_opacity(immediate_recheck: bool = false) -> void:
if _presentation_state == PresentationState.COLLAPSED:
if _height_tween == null or not _height_tween.is_running():
_panel.modulate.a = IDLE_ALPHA
_collapse_button.modulate.a = (
1.0 if _collapsed_has_unread else 0.82
)
_height_button.modulate.a = 0.82
_collapse_button.modulate.a = IDLE_ALPHA
_height_button.modulate.a = IDLE_ALPHA
return
var recent := (
Time.get_ticks_msec() / 1000.0 - _last_message_time < RECENT_SECONDS

View file

@ -1,6 +1,8 @@
class_name BubbleButton
extends Button
const ICON_FILL_RATIO: float = 0.76
@export var profile: BubbleMenuProfile
@export_group("Authored Layout")
@ -37,6 +39,7 @@ func _ready() -> void:
resized.connect(_update_pivot)
_update_pivot()
apply_profile()
_apply_icon_presentation(neutral_size)
func apply_layout(
@ -49,6 +52,7 @@ func apply_layout(
neutral_position = position
presented_size = bubble_size
_update_pivot()
_apply_icon_presentation(bubble_size)
var label_control: Control = get_label_control()
var font_size := clampi(
roundi(minf(bubble_size.x, bubble_size.y) * font_size_ratio),
@ -63,6 +67,9 @@ func get_layout_size(layout_scale: float, compact: bool) -> Vector2:
if compact:
bubble_size.x = maxf(bubble_size.x, compact_minimum_size.x)
bubble_size.y = maxf(bubble_size.y, compact_minimum_size.y)
if _uses_icon_only_presentation():
var diameter: float = maxf(bubble_size.x, bubble_size.y)
bubble_size = Vector2(diameter, diameter)
return bubble_size
@ -78,6 +85,24 @@ func get_label_control() -> Control:
return self
func _uses_icon_only_presentation() -> bool:
return icon != null and text.is_empty() and label_control_path.is_empty()
func _apply_icon_presentation(bubble_size: Vector2) -> void:
if not _uses_icon_only_presentation():
return
alignment = HORIZONTAL_ALIGNMENT_CENTER
icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
vertical_icon_alignment = VERTICAL_ALIGNMENT_CENTER
expand_icon = true
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
add_theme_constant_override(
"icon_max_width",
maxi(1, roundi(minf(bubble_size.x, bubble_size.y) * ICON_FILL_RATIO)),
)
func advance_emphasis(delta: float) -> void:
var target: float = 1.0 if _hovered or _focused else 0.0
emphasis = move_toward(
@ -122,6 +147,8 @@ func calculate_visual_scale(
* motion_scale
* lerpf(1.0, profile.emphasized_deformation_scale, emphasis)
)
if _uses_icon_only_presentation():
deformation_amount = 0.0
var hover_scale: float = lerpf(
1.0,
profile.hover_focus_scale,

View file

@ -5,7 +5,7 @@
[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/placeholder/fish_coin.svg" id="5_coin"]
[ext_resource type="Texture2D" path="res://items/icons/placeholder/cooler_expansion.svg" id="6_cooler"]
[ext_resource type="Texture2D" path="res://items/icons/equipment/64_cooler_plus.png" id="6_cooler"]
[node name="FishingShop" type="Control"]
unique_name_in_owner = true
@ -196,7 +196,7 @@ layout_mode = 2
theme_override_constants/separation = 9
[node name="Icon" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row"]
custom_minimum_size = Vector2(44, 44)
custom_minimum_size = Vector2(88, 88)
layout_mode = 2
texture = ExtResource("3_reel")
expand_mode = 1
@ -250,7 +250,7 @@ layout_mode = 2
theme_override_constants/separation = 9
[node name="Icon" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row"]
custom_minimum_size = Vector2(44, 44)
custom_minimum_size = Vector2(88, 88)
layout_mode = 2
texture = ExtResource("4_barrier")
expand_mode = 1
@ -304,7 +304,7 @@ layout_mode = 2
theme_override_constants/separation = 9
[node name="Icon" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row"]
custom_minimum_size = Vector2(44, 44)
custom_minimum_size = Vector2(88, 88)
layout_mode = 2
texture = ExtResource("6_cooler")
expand_mode = 1

View file

@ -12,6 +12,7 @@ const PlayerMenuType = preload("res://ui/player_menu.gd")
const PlayerType = preload("res://player/player.gd")
const PlayerWalletType = preload("res://economy/player_wallet.gd")
const ItemCatalogType = preload("res://items/item_catalog.gd")
const ItemDataType = preload("res://items/item_data.gd")
const PlayerBagType = preload("res://inventory/player_bag.gd")
const PlayerHotbarType = preload("res://inventory/player_hotbar.gd")
const HotbarUIType = preload("res://ui/hotbar.gd")
@ -57,6 +58,12 @@ const PlayerExperienceType = preload(
const UIReferencePresentationType = preload(
"res://ui/ui_reference_presentation.gd"
)
const MAIN_BUBBLE_PROFILE: BubbleMenuProfile = preload(
"res://ui/components/bubble_menu/bubble_menu_profile.tres"
)
const ACTIVE_BAIT_EMPTY_ICON: Texture2D = preload(
"res://ui/icons/pictograms/x_dark.png"
)
signal pixelation_settings_visibility_changed(is_visible: bool)
signal crisp_reset_focus_requested
@ -85,6 +92,9 @@ const FISHING_PANEL_SHOWCASE_BOTTOM_OFFSET: float = -104.0
@onready var _status_label: Label = %StatusLabel
@onready var _gameplay_transient_hud: Control = %GameplayTransientHUD
@onready var _active_bait_button: Button = %ActiveBaitButton
@onready var _active_bait_quantity_badge: Panel = %ActiveBaitQuantityBadge
@onready var _active_bait_quantity: Label = %ActiveBaitQuantity
@onready var _experience_presentation: Control = %ExperiencePresentation
@onready var _catch_track: Control = %CatchTrack
@onready var _green_catch_progress: ProgressBar = %GreenCatchProgress
@ -101,6 +111,13 @@ const FISHING_PANEL_SHOWCASE_BOTTOM_OFFSET: float = -104.0
@onready var _experience_progress: ProgressBar = %ExperienceProgress
@onready var _experience_bubble: PanelContainer = %ExperienceBubble
@onready var _experience_bubble_label: Label = %ExperienceBubbleLabel
const TypewriterRevealType = preload("res://ui/typewriter_reveal.gd")
const AnimaleseVoiceType = preload("res://ui/animalese_voice.gd")
const SHOP_ANIMALESE_VOICE_ID: String = "natural"
const SHOP_ANIMALESE_BASE_PITCH: float = 1.08
const SHOP_SPEECH_CHARACTERS_PER_SECOND: float = 28.0
@onready var _canonical_stage: Control = %CanonicalStage
@onready var _ui_root: Control = %UIRoot
@onready var _player_menu: PlayerMenuType = %PlayerMenu
@ -109,7 +126,13 @@ const FISHING_PANEL_SHOWCASE_BOTTOM_OFFSET: float = -104.0
@onready var _pause_menu: PauseMenuType = %PauseMenu
@onready var _hotbar_ui: HotbarUIType = %Hotbar
@onready var _fishing_shop: FishingShopType = %FishingShop
@onready var _shop_prompt: PanelContainer = %ShopPrompt
@onready var _shop_prompt: Control = %ShopPrompt
@onready var _shop_prompt_bubble: PanelContainer = %ShopPromptBubble
@onready var _shop_prompt_message: Label = %ShopPromptMessage
@onready var _shop_prompt_key_badge: Panel = %ShopPromptKeyBadge
@onready var _shop_prompt_key: Label = %ShopPromptKey
@onready var _shop_prompt_pointer: Polygon2D = %ShopPromptPointer
var _shop_animalese_voice: AnimaleseVoiceType
@onready var _effect_status: Label = %EffectStatus
@onready var _chat_ui: ChatUIType = %ChatUI
@onready var _emote_radial_menu: EmoteRadialMenuType = %EmoteRadialMenu
@ -129,6 +152,8 @@ const FISHING_PANEL_SHOWCASE_BOTTOM_OFFSET: float = -104.0
var _showcase_active: bool = false
var _player: PlayerType
var _bag: PlayerBagType
var _item_catalog: ItemCatalogType
var _player_menu_open: bool = false
var _gameplay_ui_enabled: bool = false
var _fishing_spot: FishingSpotType
@ -163,6 +188,8 @@ var _settings_manager: PlayerSettingsManagerType
func _ready() -> void:
_apply_active_bait_indicator_style()
_refresh_active_bait_indicator()
# Reward feedback must remain above full-screen canonical menus. Keeping the
# overlay as the final stage child makes that ownership explicit instead of
# relying on scene declaration order when another menu adds high-z children.
@ -236,10 +263,27 @@ func setup(
world_sun: DirectionalLight3D,
) -> void:
_player = player
_bag = bag
_item_catalog = item_catalog
_settings_manager = settings_manager
_fishing_spot = fishing_spot
_item_effects = item_effects
_experience = experience
if (
_player != null
and not _player.active_bait_changed.is_connected(
_on_hud_active_bait_changed
)
):
_player.active_bait_changed.connect(_on_hud_active_bait_changed)
if (
_bag != null
and not _bag.contents_changed.is_connected(
_on_hud_bait_inventory_changed
)
):
_bag.contents_changed.connect(_on_hud_bait_inventory_changed)
_refresh_active_bait_indicator()
if (
_experience != null
and not _experience.experience_awarded.is_connected(
@ -1174,6 +1218,88 @@ func get_pause_menu() -> PauseMenuType:
return _pause_menu
func _apply_active_bait_indicator_style() -> void:
var normal_style: StyleBoxFlat = MAIN_BUBBLE_PROFILE.make_normal_style()
var hover_style: StyleBoxFlat = MAIN_BUBBLE_PROFILE.make_hover_style()
var pressed_style: StyleBoxFlat = MAIN_BUBBLE_PROFILE.make_pressed_style()
var disabled_style: StyleBoxFlat = MAIN_BUBBLE_PROFILE.make_disabled_style()
for style: StyleBoxFlat in [
normal_style,
hover_style,
pressed_style,
disabled_style,
]:
style.set_corner_radius_all(36)
style.content_margin_left = 13.5
style.content_margin_top = 13.5
style.content_margin_right = 13.5
style.content_margin_bottom = 13.5
_active_bait_button.add_theme_stylebox_override("normal", normal_style)
_active_bait_button.add_theme_stylebox_override("hover", hover_style)
_active_bait_button.add_theme_stylebox_override("focus", hover_style)
_active_bait_button.add_theme_stylebox_override("pressed", pressed_style)
_active_bait_button.add_theme_stylebox_override("disabled", disabled_style)
for state: StringName in [
&"icon_normal_color",
&"icon_hover_color",
&"icon_focus_color",
&"icon_pressed_color",
]:
_active_bait_button.add_theme_color_override(state, Color.WHITE)
var badge_style := StyleBoxFlat.new()
badge_style.bg_color = Color("0b5558")
badge_style.set_corner_radius_all(12)
badge_style.anti_aliasing = false
_active_bait_quantity_badge.add_theme_stylebox_override(
"panel",
badge_style,
)
func _refresh_active_bait_indicator() -> void:
if not is_node_ready():
return
var item: ItemDataType
if (
_player != null
and _item_catalog != null
and not _player.active_bait_id.is_empty()
):
item = _item_catalog.get_item_by_id(_player.active_bait_id)
var has_active_bait: bool = item != null and item.is_bait()
_active_bait_button.icon = (
item.icon
if has_active_bait and item.icon != null
else ACTIVE_BAIT_EMPTY_ICON
)
_active_bait_quantity_badge.visible = has_active_bait
if not has_active_bait:
_active_bait_button.tooltip_text = "no bait selected"
return
var quantity: int = (
_bag.get_quantity(item.item_id)
if _bag != null
else 0
)
_active_bait_quantity.text = str(quantity)
_active_bait_quantity.add_theme_font_size_override(
"font_size",
13 if quantity >= 100 else 15,
)
_active_bait_button.tooltip_text = "%s ×%d" % [
item.display_name,
quantity,
]
func _on_hud_active_bait_changed(_item_id: StringName) -> void:
_refresh_active_bait_indicator()
func _on_hud_bait_inventory_changed() -> void:
_refresh_active_bait_indicator()
func set_gameplay_ui_enabled(enabled: bool) -> void:
_gameplay_ui_enabled = enabled
_gameplay_transient_hud.visible = enabled and not _player_menu_open
@ -1231,7 +1357,12 @@ func get_fishing_shop() -> FishingShopType:
return _fishing_shop
func set_shop_prompt_visible(is_visible: bool) -> void:
func set_shop_prompt_visible(
is_visible: bool,
world_anchor: Vector3 = Vector3(0.0, INF, 0.0),
) -> void:
_apply_shop_prompt_style()
var was_visible := _shop_prompt.visible
_shop_prompt.visible = (
is_visible
and _gameplay_ui_enabled
@ -1239,6 +1370,112 @@ func set_shop_prompt_visible(is_visible: bool) -> void:
and not _player_menu_open
and not _shop_open
)
if _shop_prompt.visible:
if not was_visible:
_ensure_shop_animalese_voice()
TypewriterRevealType.start(
_shop_prompt_message,
SHOP_SPEECH_CHARACTERS_PER_SECOND,
)
_shop_animalese_voice.speak_text(
_shop_prompt_message,
_shop_prompt_message.text,
"shopkeeper",
SHOP_ANIMALESE_VOICE_ID,
SHOP_SPEECH_CHARACTERS_PER_SECOND,
)
if world_anchor.is_finite():
_position_shop_prompt(world_anchor)
func _ensure_shop_animalese_voice() -> void:
if _shop_animalese_voice != null:
return
_shop_animalese_voice = AnimaleseVoiceType.new()
_shop_animalese_voice.name = "ShopAnimaleseVoice"
_shop_animalese_voice.base_pitch = SHOP_ANIMALESE_BASE_PITCH
add_child(_shop_animalese_voice)
func _apply_shop_prompt_style() -> void:
if _shop_prompt.has_meta(&"shop_prompt_styled"):
return
_shop_prompt.set_meta(&"shop_prompt_styled", true)
var bubble_style := StyleBoxFlat.new()
bubble_style.bg_color = Color(UtilityPageStyle.OCEAN_PANEL_MID, 0.96)
bubble_style.set_border_width_all(0)
bubble_style.set_corner_radius_all(12)
bubble_style.anti_aliasing = false
_shop_prompt_bubble.add_theme_stylebox_override("panel", bubble_style)
var badge_style := StyleBoxFlat.new()
badge_style.bg_color = Color("0b5558")
badge_style.set_border_width_all(0)
badge_style.set_corner_radius_all(12)
badge_style.anti_aliasing = false
_shop_prompt_key_badge.add_theme_stylebox_override("panel", badge_style)
for label: Label in [_shop_prompt_message, _shop_prompt_key]:
label.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
label.add_theme_color_override(
"font_color",
UtilityPageStyle.OCEAN_TEXT_PRIMARY,
)
_shop_prompt_pointer.color = Color(
UtilityPageStyle.OCEAN_PANEL_MID,
0.96,
)
func _position_shop_prompt(world_anchor: Vector3) -> void:
if _player == null:
_shop_prompt.hide()
return
var camera: Camera3D = _player.get_gameplay_camera()
if camera == null or camera.is_position_behind(world_anchor):
_shop_prompt.hide()
return
var camera_viewport_size := camera.get_viewport().get_visible_rect().size
var ui_viewport_size: Vector2 = _canonical_stage.size
if camera_viewport_size.x <= 0.0 or camera_viewport_size.y <= 0.0:
_shop_prompt.hide()
return
var screen_position := (
camera.unproject_position(world_anchor)
* ui_viewport_size
/ camera_viewport_size
)
if (
screen_position.x < -80.0
or screen_position.x > ui_viewport_size.x + 80.0
or screen_position.y < -80.0
or screen_position.y > ui_viewport_size.y + 80.0
):
_shop_prompt.hide()
return
var pointer_height := 10.0
var desired := screen_position - Vector2(
_shop_prompt.size.x * 0.5,
_shop_prompt.size.y + pointer_height,
)
_shop_prompt.position = Vector2(
clampf(
desired.x,
8.0,
ui_viewport_size.x - _shop_prompt.size.x - 8.0,
),
clampf(
desired.y,
8.0,
ui_viewport_size.y - _shop_prompt.size.y - pointer_height - 8.0,
),
)
_shop_prompt_pointer.position = Vector2(
clampf(
screen_position.x - _shop_prompt.position.x,
20.0,
_shop_prompt.size.x - 20.0,
),
_shop_prompt.size.y - 1.0,
)
func get_screen_fade() -> ScreenFade:

View file

@ -82,6 +82,71 @@ grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="ActiveBaitIndicator" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
unique_name_in_owner = true
z_index = 54
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -88.0
offset_top = -88.0
offset_right = -16.0
offset_bottom = -16.0
grow_horizontal = 0
grow_vertical = 0
mouse_filter = 2
theme = ExtResource("3_theme")
[node name="ActiveBaitButton" type="Button" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ActiveBaitIndicator"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
focus_mode = 0
texture_filter = 1
expand_icon = true
alignment = 1
icon_alignment = 1
vertical_icon_alignment = 1
[node name="ActiveBaitQuantityBadge" type="Panel" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ActiveBaitIndicator"]
unique_name_in_owner = true
z_index = 2
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -27.0
offset_top = 3.0
offset_right = -3.0
offset_bottom = 27.0
grow_horizontal = 0
mouse_filter = 2
[node name="ActiveBaitQuantity" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ActiveBaitIndicator/ActiveBaitQuantityBadge"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 1.0
offset_right = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_colors/font_color = Color(0.905882, 0.960784, 0.956863, 1)
theme_override_font_sizes/font_size = 15
text = "0"
horizontal_alignment = 1
vertical_alignment = 1
[node name="ExperiencePresentation" type="Control" parent="UIRoot/CanonicalStage"]
unique_name_in_owner = true
z_index = 200
@ -333,35 +398,67 @@ unique_name_in_owner = true
[node name="QuickRadialMenu" parent="UIRoot/CanonicalStage" instance=ExtResource("12_quick")]
unique_name_in_owner = true
[node name="ShopPrompt" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
[node name="ShopPrompt" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
unique_name_in_owner = true
visible = false
z_index = 55
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -150.0
offset_top = -284.0
offset_right = 150.0
offset_bottom = -246.0
grow_horizontal = 2
grow_vertical = 0
offset_right = 300.0
offset_bottom = 72.0
mouse_filter = 2
theme = ExtResource("3_theme")
[node name="Margin" type="MarginContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt"]
[node name="ShopPromptBubble" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="Margin" type="MarginContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt/ShopPromptBubble"]
layout_mode = 2
theme_override_constants/margin_left = 12
theme_override_constants/margin_top = 7
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 12
theme_override_constants/margin_bottom = 7
theme_override_constants/margin_bottom = 8
[node name="Label" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt/Margin"]
[node name="ShopPromptMessage" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt/ShopPromptBubble/Margin"]
unique_name_in_owner = true
layout_mode = 2
text = "press e to open fishing shop"
theme_override_font_sizes/font_size = 18
text = "Hey, if you've got any fish I've got cash! OwO"
horizontal_alignment = 1
vertical_alignment = 1
autowrap_mode = 2
[node name="ShopPromptKeyBadge" type="Panel" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt"]
unique_name_in_owner = true
layout_mode = 0
offset_left = 282.0
offset_top = -8.0
offset_right = 306.0
offset_bottom = 16.0
mouse_filter = 2
[node name="ShopPromptKey" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt/ShopPromptKeyBadge"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_font_sizes/font_size = 15
text = "E"
horizontal_alignment = 1
vertical_alignment = 1
[node name="ShopPromptPointer" type="Polygon2D" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt"]
unique_name_in_owner = true
polygon = PackedVector2Array(-8, 0, 8, 0, 0, 10)
[node name="FishingShop" parent="UIRoot/CanonicalStage" instance=ExtResource("8_shop")]
unique_name_in_owner = true

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d4aah3j5brkij"
path="res://.godot/imported/arrow_dark_down_full.png-0e7bb59c41a81c0bd02261d6a7b319ef.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/arrow_dark_down_full.png"
dest_files=["res://.godot/imported/arrow_dark_down_full.png-0e7bb59c41a81c0bd02261d6a7b319ef.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d34x31jx5p5ll"
path="res://.godot/imported/arrow_dark_up_full.png-907f3a8653cf6bf8b6ba95ad31b18e0d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/arrow_dark_up_full.png"
dest_files=["res://.godot/imported/arrow_dark_up_full.png-907f3a8653cf6bf8b6ba95ad31b18e0d.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cmc7sguttrl2o"
path="res://.godot/imported/arrow_light_down_more.png-a02fb5d09f2022507ddb6c06e3f22732.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/arrow_light_down_more.png"
dest_files=["res://.godot/imported/arrow_light_down_more.png-a02fb5d09f2022507ddb6c06e3f22732.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b3lx64prsccyk"
path="res://.godot/imported/arrow_light_left_more.png-a92f7c96dbcafd9c1b2cb612e94fef44.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/arrow_light_left_more.png"
dest_files=["res://.godot/imported/arrow_light_left_more.png-a92f7c96dbcafd9c1b2cb612e94fef44.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cedstrt57et53"
path="res://.godot/imported/arrow_light_right_more.png-56d3df49848f9fee268e5c4b6c279828.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/arrow_light_right_more.png"
dest_files=["res://.godot/imported/arrow_light_right_more.png-56d3df49848f9fee268e5c4b6c279828.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dqu71oklbvofk"
path="res://.godot/imported/arrow_light_up_more.png-7e8472cc9f410835ab24173229e09c9b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/arrow_light_up_more.png"
dest_files=["res://.godot/imported/arrow_light_up_more.png-7e8472cc9f410835ab24173229e09c9b.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://3x5tryb721th"
path="res://.godot/imported/check_mark_dark.png-d9abda15f33a6a8f8c29c5f148cda086.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/check_mark_dark.png"
dest_files=["res://.godot/imported/check_mark_dark.png-d9abda15f33a6a8f8c29c5f148cda086.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dtibvrik7ug0j"
path="res://.godot/imported/check_mark_light.png-83417416d6580b7b5eb41987b7af5ccf.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/check_mark_light.png"
dest_files=["res://.godot/imported/check_mark_light.png-83417416d6580b7b5eb41987b7af5ccf.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://uxkgiwd7py03"
path="res://.godot/imported/online_dark.png-081123db8a385b31ed626b7c95b20c3c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/online_dark.png"
dest_files=["res://.godot/imported/online_dark.png-081123db8a385b31ed626b7c95b20c3c.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b71gds1q8ny61"
path="res://.godot/imported/save_dark.png-6dbadbfe5b8057ede23f77629e3c1226.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/save_dark.png"
dest_files=["res://.godot/imported/save_dark.png-6dbadbfe5b8057ede23f77629e3c1226.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ul28xl5qed55"
path="res://.godot/imported/settings_dark.png-7196550e6c3f5a58c1402ab0ccc9c9d0.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/settings_dark.png"
dest_files=["res://.godot/imported/settings_dark.png-7196550e6c3f5a58c1402ab0ccc9c9d0.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://deux1h722j33u"
path="res://.godot/imported/undo_dark.png-652c08d6b38e19b70b66927ecfc6222c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/undo_dark.png"
dest_files=["res://.godot/imported/undo_dark.png-652c08d6b38e19b70b66927ecfc6222c.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://1qr5t7gx1eag"
path="res://.godot/imported/undo_light.png-83dd161fb13faa93326dee6cb01e205c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/undo_light.png"
dest_files=["res://.godot/imported/undo_light.png-83dd161fb13faa93326dee6cb01e205c.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cg8vi6sxnpl2t"
path="res://.godot/imported/x_dark.png-5430cf57b7772f059ee1fac9842c6898.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/x_dark.png"
dest_files=["res://.godot/imported/x_dark.png-5430cf57b7772f059ee1fac9842c6898.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cvb228ypykqaq"
path="res://.godot/imported/x_light.png-b099203c17614ca6f36b269beabfcbab.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/x_light.png"
dest_files=["res://.godot/imported/x_light.png-b099203c17614ca6f36b269beabfcbab.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ce6ge5yypnnpf"
path="res://.godot/imported/weather_clear_day.png-1cd1140830664281637d5589a748ef58.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/weather/weather_clear_day.png"
dest_files=["res://.godot/imported/weather_clear_day.png-1cd1140830664281637d5589a748ef58.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://78l0keyrvuiw"
path="res://.godot/imported/weather_clear_night_full.png-3640fcf886e9bd01942e21cc0212bc45.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/weather/weather_clear_night_full.png"
dest_files=["res://.godot/imported/weather_clear_night_full.png-3640fcf886e9bd01942e21cc0212bc45.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b17frlbta1p7f"
path="res://.godot/imported/weather_cloudy.png-c41e4d98007294310395f752b39ddaa6.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/weather/weather_cloudy.png"
dest_files=["res://.godot/imported/weather_cloudy.png-c41e4d98007294310395f752b39ddaa6.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://wpaj2l01upt6"
path="res://.godot/imported/weather_fog.png-0fe4352261121bbcc8da10ebc1995663.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/weather/weather_fog.png"
dest_files=["res://.godot/imported/weather_fog.png-0fe4352261121bbcc8da10ebc1995663.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cfqvlqp3nrfok"
path="res://.godot/imported/weather_rain.png-affbb7091de81103538e2740c5b2c762.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/weather/weather_rain.png"
dest_files=["res://.godot/imported/weather_rain.png-affbb7091de81103538e2740c5b2c762.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

@ -23,9 +23,26 @@ const HANDWRITTEN_NUMERIC_SCALE: float = 0.8
const PORTRAIT_VIEW_MAX_SIZE := Vector2(860.0, 480.0)
const INK := Color("251b10")
const MUTED_INK := Color("6d5b45")
const PAPER := Color("f2e6c9")
const PAPER_DARK := Color("e8d7b4")
const LOGBOOK_TAB_LEFT_INSET: float = 34.0
const LOGBOOK_ARTWORK: Texture2D = preload(
"res://ui/assets/logbook/ui_logbook.png"
)
const SCROLL_UP_TEXTURE: Texture2D = preload(
"res://ui/icons/pictograms/arrow_dark_up_full.png"
)
const SCROLL_DOWN_TEXTURE: Texture2D = preload(
"res://ui/icons/pictograms/arrow_dark_down_full.png"
)
const LOGBOOK_ARTWORK_SOURCE_SIZE := Vector2(512.0, 247.0)
const LEFT_PAGE_CONTENT_RECT := Rect2(56.0, 26.0, 190.0, 198.0)
const RIGHT_PAGE_CONTENT_RECT := Rect2(267.0, 26.0, 210.0, 198.0)
const LOGBOOK_TAB_LEFT_INSET: float = 122.0
const PAGE_CONTENT_SCALE: float = 0.97
const CATALOG_PORTRAIT_SIZE := Vector2(78.0, 36.0)
const CATALOG_ENTRY_SIZE := Vector2(92.0, 92.0)
const CATALOG_ROW_STEP: float = 98.0
const CATALOG_SNAP_DELAY: float = 0.12
const DETAIL_PORTRAIT_SIZE := Vector2(160.0, 88.0)
const DETAIL_BOTTOM_INSET: float = 35.0
var _collection_log: CollectionLogType
var _inventory: FishInventoryType
@ -41,10 +58,14 @@ var _detail_tween: Tween
var _category_generation: int = 0
var _category_tween: Tween
var _silhouette_material: ShaderMaterial
var _snapping_catalog_scroll: bool = false
var _catalog_scroll_snap_timer: Timer
var _category_tabs: Array[Button] = []
var _catalog_scroll: ScrollContainer
var _catalog_grid: GridContainer
var _catalog_scroll_up_indicator: TextureRect
var _catalog_scroll_down_indicator: TextureRect
var _empty_state: Label
var _detail_body: VBoxContainer
var _detail_portrait_button: Button
@ -145,7 +166,7 @@ func _build_interface() -> void:
var outer := MarginContainer.new()
outer.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
outer.add_theme_constant_override("margin_left", 52)
outer.add_theme_constant_override("margin_top", 116)
outer.add_theme_constant_override("margin_top", 85)
outer.add_theme_constant_override("margin_right", 52)
outer.add_theme_constant_override("margin_bottom", 18)
add_child(outer)
@ -155,9 +176,8 @@ func _build_interface() -> void:
outer.add_child(stack)
var tabs := HBoxContainer.new()
# The 26 px page corner plus 8 px clearance keeps the flange behind
# the straight portion of the spread.
tabs.position = Vector2(LOGBOOK_TAB_LEFT_INSET, 20.0)
# Align the first flange with the authored left paper edge.
tabs.position = Vector2(LOGBOOK_TAB_LEFT_INSET, 37.0)
tabs.size = Vector2(384.0, 38.0)
tabs.z_index = 10
tabs.mouse_filter = Control.MOUSE_FILTER_PASS
@ -179,71 +199,206 @@ func _build_interface() -> void:
_category_tabs.append(tab)
_configure_category_focus()
var book := HBoxContainer.new()
var book := Control.new()
book.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
book.offset_top = 50.0
book.z_index = 20
book.add_theme_constant_override("separation", 12)
book.mouse_filter = Control.MOUSE_FILTER_IGNORE
stack.add_child(book)
var left_page := PanelContainer.new()
left_page.size_flags_horizontal = Control.SIZE_EXPAND_FILL
left_page.size_flags_stretch_ratio = 1.12
left_page.add_theme_stylebox_override(
"panel", _paper_style(PAPER, true)
)
var artwork := TextureRect.new()
artwork.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
artwork.texture = LOGBOOK_ARTWORK
artwork.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
artwork.stretch_mode = TextureRect.STRETCH_SCALE
artwork.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
artwork.mouse_filter = Control.MOUSE_FILTER_IGNORE
book.add_child(artwork)
var left_page := Control.new()
left_page.mouse_filter = Control.MOUSE_FILTER_IGNORE
_apply_artwork_rect(left_page, LEFT_PAGE_CONTENT_RECT)
book.add_child(left_page)
var left_margin := MarginContainer.new()
_set_margins(left_margin, 18, 16, 18, 16)
left_page.add_child(left_margin)
var left_layout := VBoxContainer.new()
left_layout.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
left_layout.add_theme_constant_override("separation", 8)
left_margin.add_child(left_layout)
var heading := _label("catch catalog", 25)
heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
left_layout.add_child(heading)
left_page.add_child(left_layout)
_apply_page_content_scale(left_layout)
var scroll_indicator_top_lane := Control.new()
scroll_indicator_top_lane.custom_minimum_size.y = 20.0
scroll_indicator_top_lane.mouse_filter = Control.MOUSE_FILTER_IGNORE
left_layout.add_child(scroll_indicator_top_lane)
_empty_state = _label("", 18)
_empty_state.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_empty_state.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_empty_state.size_flags_vertical = Control.SIZE_EXPAND_FILL
left_layout.add_child(_empty_state)
var catalog_scroll_margin := MarginContainer.new()
catalog_scroll_margin.size_flags_vertical = Control.SIZE_EXPAND_FILL
catalog_scroll_margin.add_theme_constant_override("margin_left", 20)
left_layout.add_child(catalog_scroll_margin)
_catalog_scroll = ScrollContainer.new()
_catalog_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
_catalog_scroll.horizontal_scroll_mode = (
ScrollContainer.SCROLL_MODE_DISABLED
)
left_layout.add_child(_catalog_scroll)
_catalog_scroll.scroll_vertical_custom_step = CATALOG_ROW_STEP
catalog_scroll_margin.add_child(_catalog_scroll)
_catalog_grid = GridContainer.new()
_catalog_grid.columns = 5
_catalog_grid.columns = 4
_catalog_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_catalog_grid.add_theme_constant_override("h_separation", 8)
_catalog_grid.add_theme_constant_override("v_separation", 8)
_catalog_grid.add_theme_constant_override("h_separation", 6)
_catalog_grid.add_theme_constant_override("v_separation", 6)
_catalog_scroll.add_child(_catalog_grid)
var gutter := ColorRect.new()
gutter.custom_minimum_size.x = 10
gutter.color = Color("795f3f")
gutter.mouse_filter = Control.MOUSE_FILTER_IGNORE
book.add_child(gutter)
var right_page := PanelContainer.new()
right_page.size_flags_horizontal = Control.SIZE_EXPAND_FILL
right_page.add_theme_stylebox_override(
"panel", _paper_style(PAPER_DARK, false)
var scroll_indicator_bottom_lane := Control.new()
scroll_indicator_bottom_lane.custom_minimum_size.y = 32.0
scroll_indicator_bottom_lane.mouse_filter = Control.MOUSE_FILTER_IGNORE
left_layout.add_child(scroll_indicator_bottom_lane)
_configure_catalog_scroll_bar()
_catalog_scroll_up_indicator = _make_scroll_indicator(
"CatalogScrollUpIndicator",
SCROLL_UP_TEXTURE,
true,
)
left_page.add_child(_catalog_scroll_up_indicator)
_catalog_scroll_down_indicator = _make_scroll_indicator(
"CatalogScrollDownIndicator",
SCROLL_DOWN_TEXTURE,
false,
)
left_page.add_child(_catalog_scroll_down_indicator)
var right_page := Control.new()
right_page.mouse_filter = Control.MOUSE_FILTER_IGNORE
_apply_artwork_rect(right_page, RIGHT_PAGE_CONTENT_RECT)
book.add_child(right_page)
var right_margin := MarginContainer.new()
_set_margins(right_margin, 22, 16, 22, 16)
right_page.add_child(right_margin)
_detail_body = VBoxContainer.new()
_detail_body.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_detail_body.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_detail_body.size_flags_vertical = Control.SIZE_EXPAND_FILL
_detail_body.add_theme_constant_override("separation", 16)
right_margin.add_child(_detail_body)
right_page.add_child(_detail_body)
_apply_page_content_scale(_detail_body)
_show_no_selection()
_build_portrait_overlay()
func _apply_artwork_rect(control: Control, source_rect: Rect2) -> void:
control.anchor_left = source_rect.position.x / LOGBOOK_ARTWORK_SOURCE_SIZE.x
control.anchor_top = source_rect.position.y / LOGBOOK_ARTWORK_SOURCE_SIZE.y
control.anchor_right = source_rect.end.x / LOGBOOK_ARTWORK_SOURCE_SIZE.x
control.anchor_bottom = source_rect.end.y / LOGBOOK_ARTWORK_SOURCE_SIZE.y
control.offset_left = 0.0
control.offset_top = 0.0
control.offset_right = 0.0
control.offset_bottom = 0.0
func _apply_page_content_scale(control: Control) -> void:
control.scale = Vector2.ONE * PAGE_CONTENT_SCALE
control.resized.connect(_center_scaled_content.bind(control))
_center_scaled_content.call_deferred(control)
func _center_scaled_content(control: Control) -> void:
if is_instance_valid(control):
control.pivot_offset = control.size * 0.5
func _configure_catalog_scroll_bar() -> void:
_catalog_scroll_snap_timer = Timer.new()
_catalog_scroll_snap_timer.one_shot = true
_catalog_scroll_snap_timer.wait_time = CATALOG_SNAP_DELAY
_catalog_scroll_snap_timer.timeout.connect(_snap_catalog_scroll_to_row)
_catalog_scroll.add_child(_catalog_scroll_snap_timer)
var scroll_bar: VScrollBar = _catalog_scroll.get_v_scroll_bar()
scroll_bar.mouse_filter = Control.MOUSE_FILTER_IGNORE
scroll_bar.focus_mode = Control.FOCUS_NONE
scroll_bar.self_modulate = Color.TRANSPARENT
scroll_bar.custom_minimum_size.x = 0.0
scroll_bar.add_theme_constant_override("scroll_size", 0)
var empty_style := StyleBoxEmpty.new()
for style_name: StringName in [
&"scroll",
&"scroll_focus",
&"grabber",
&"grabber_highlight",
&"grabber_pressed",
]:
scroll_bar.add_theme_stylebox_override(style_name, empty_style)
scroll_bar.changed.connect(_refresh_catalog_scroll_indicators)
scroll_bar.value_changed.connect(_on_catalog_scroll_value_changed)
func _make_scroll_indicator(
indicator_name: String,
indicator_texture: Texture2D,
at_top: bool,
) -> TextureRect:
var indicator := TextureRect.new()
indicator.name = indicator_name
indicator.anchor_left = 0.5
indicator.anchor_right = 0.5
indicator.anchor_top = 0.0 if at_top else 1.0
indicator.anchor_bottom = indicator.anchor_top
indicator.offset_left = -14.0
indicator.offset_right = 14.0
indicator.offset_top = -8.0 if at_top else -35.0
indicator.offset_bottom = 20.0 if at_top else -7.0
indicator.z_index = 30
indicator.texture = indicator_texture
indicator.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
indicator.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
indicator.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
indicator.mouse_filter = Control.MOUSE_FILTER_IGNORE
indicator.visible = false
return indicator
func _on_catalog_scroll_value_changed(value: float) -> void:
if _snapping_catalog_scroll:
return
_catalog_scroll_snap_timer.start()
_refresh_catalog_scroll_indicators()
func _snap_catalog_scroll_to_row() -> void:
var scroll_bar: VScrollBar = _catalog_scroll.get_v_scroll_bar()
var value: float = scroll_bar.value
var maximum_scroll: float = scroll_bar.max_value - scroll_bar.page
var snapped_value: float = clampf(
roundf(value / CATALOG_ROW_STEP) * CATALOG_ROW_STEP,
scroll_bar.min_value,
maximum_scroll,
)
if not is_equal_approx(value, snapped_value):
_snapping_catalog_scroll = true
_catalog_scroll.scroll_vertical = roundi(snapped_value)
_snapping_catalog_scroll = false
_refresh_catalog_scroll_indicators()
func _refresh_catalog_scroll_indicators() -> void:
if (
_catalog_scroll == null
or _catalog_scroll_up_indicator == null
or _catalog_scroll_down_indicator == null
):
return
var scroll_bar: VScrollBar = _catalog_scroll.get_v_scroll_bar()
var maximum_scroll: float = scroll_bar.max_value - scroll_bar.page
var has_overflow: bool = (
_catalog_scroll.visible
and maximum_scroll > scroll_bar.min_value + 0.5
)
_catalog_scroll_up_indicator.visible = (
has_overflow and scroll_bar.value > scroll_bar.min_value + 0.5
)
_catalog_scroll_down_indicator.visible = (
has_overflow and scroll_bar.value < maximum_scroll - 0.5
)
func _refresh_catalog() -> void:
if not is_node_ready():
return
@ -293,11 +448,12 @@ func _refresh_catalog() -> void:
_refresh_selection_styles()
_refresh_details(false)
set_interactive(_interactive)
_refresh_catalog_scroll_indicators.call_deferred()
func _make_entry(fish: FishDataType, discovered: bool) -> Button:
var entry := Button.new()
entry.custom_minimum_size = Vector2(102, 102)
entry.custom_minimum_size = CATALOG_ENTRY_SIZE
entry.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
entry.size_flags_vertical = Control.SIZE_SHRINK_CENTER
entry.toggle_mode = true
@ -344,7 +500,7 @@ func _add_entry_content(
var portrait_view := LogbookPortraitType.new()
portrait_view.configure(
portrait,
LogbookPortraitType.ENTRY_FRAME_SIZE,
CATALOG_PORTRAIT_SIZE,
_silhouette_material if unknown else null,
)
content.add_child(portrait_view)
@ -495,10 +651,14 @@ func _refresh_details(animate: bool) -> void:
func _build_known_details(fish: FishDataType) -> void:
_clear_details()
var catalog_number: int = LogbookCatalog.catalog_number(fish.id)
var summary_margin := MarginContainer.new()
summary_margin.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_set_margins(summary_margin, 40, 0, 40, 0)
_detail_body.add_child(summary_margin)
var summary_columns := HBoxContainer.new()
summary_columns.size_flags_horizontal = Control.SIZE_EXPAND_FILL
summary_columns.add_theme_constant_override("separation", 24)
_detail_body.add_child(summary_columns)
summary_margin.add_child(summary_columns)
var portrait_column := VBoxContainer.new()
portrait_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
@ -510,12 +670,11 @@ func _build_known_details(fish: FishDataType) -> void:
portrait_column.add_child(title)
if fish.display_texture != null:
_detail_portrait_button = Button.new()
_detail_portrait_button.custom_minimum_size = (
LogbookPortraitType.DETAIL_FRAME_SIZE
)
_detail_portrait_button.custom_minimum_size = DETAIL_PORTRAIT_SIZE
_detail_portrait_button.size_flags_horizontal = (
Control.SIZE_SHRINK_CENTER
)
_detail_portrait_button.clip_contents = true
_detail_portrait_button.tooltip_text = "View larger artwork"
_detail_portrait_button.accessibility_name = (
"View larger artwork for %s" % fish.display_name
@ -545,7 +704,7 @@ func _build_known_details(fish: FishDataType) -> void:
var artwork := LogbookPortraitType.new()
artwork.configure(
fish.display_texture,
LogbookPortraitType.DETAIL_FRAME_SIZE,
DETAIL_PORTRAIT_SIZE,
)
artwork_center.add_child(artwork)
@ -562,11 +721,15 @@ func _build_known_details(fish: FishDataType) -> void:
facts_column.add_child(facts)
_detail_body.add_child(_build_quality_progress(fish.id))
var stats_anchor := VBoxContainer.new()
stats_anchor.size_flags_horizontal = Control.SIZE_EXPAND_FILL
stats_anchor.size_flags_vertical = Control.SIZE_EXPAND_FILL
stats_anchor.alignment = BoxContainer.ALIGNMENT_END
_detail_body.add_child(stats_anchor)
var stats_columns := HBoxContainer.new()
stats_columns.size_flags_horizontal = Control.SIZE_EXPAND_FILL
stats_columns.size_flags_vertical = Control.SIZE_EXPAND_FILL
stats_columns.add_theme_constant_override("separation", 28)
_detail_body.add_child(stats_columns)
stats_anchor.add_child(stats_columns)
var left_stats := _make_stats_column()
var right_stats := _make_stats_column()
stats_columns.add_child(left_stats)
@ -611,11 +774,16 @@ func _build_known_details(fish: FishDataType) -> void:
"number owned",
str(_inventory.get_count(fish.id) if _inventory != null else 0),
)
var bottom_inset := Control.new()
bottom_inset.custom_minimum_size.y = DETAIL_BOTTOM_INSET
bottom_inset.mouse_filter = Control.MOUSE_FILTER_IGNORE
stats_anchor.add_child(bottom_inset)
func _build_quality_progress(fish_id: StringName) -> VBoxContainer:
var quality_section := VBoxContainer.new()
quality_section.add_theme_constant_override("separation", 3)
quality_section.size_flags_horizontal = Control.SIZE_EXPAND_FILL
quality_section.add_theme_constant_override("separation", 6)
var discovered_count: int = 0
for quality: int in FishQualityType.TIER_COUNT:
if _collection_log.has_discovered_quality(fish_id, quality):
@ -625,29 +793,36 @@ func _build_quality_progress(fish_id: StringName) -> VBoxContainer:
discovered_count,
FishQualityType.TIER_COUNT,
],
14,
17,
)
heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
heading.add_theme_color_override("font_color", MUTED_INK)
quality_section.add_child(heading)
var tiers := HBoxContainer.new()
tiers.alignment = BoxContainer.ALIGNMENT_CENTER
tiers.add_theme_constant_override("separation", 10)
var tiers := VBoxContainer.new()
tiers.add_theme_constant_override("separation", 6)
quality_section.add_child(tiers)
for quality: int in FishQualityType.TIER_COUNT:
var row: HBoxContainer
if quality % 2 == 0:
row = HBoxContainer.new()
row.alignment = BoxContainer.ALIGNMENT_CENTER
row.add_theme_constant_override("separation", 28)
tiers.add_child(row)
else:
row = tiers.get_child(tiers.get_child_count() - 1) as HBoxContainer
var discovered: bool = _collection_log.has_discovered_quality(
fish_id,
quality,
)
var tier := HBoxContainer.new()
tier.alignment = BoxContainer.ALIGNMENT_CENTER
tier.add_theme_constant_override("separation", 3)
tier.add_theme_constant_override("separation", 5)
var quality_color: Color = UIPalette.get_quality_color(quality)
var dot := _label("" if discovered else "", 13)
var dot := _label("" if discovered else "", 17)
dot.add_theme_color_override("font_color", quality_color)
dot.modulate.a = 1.0 if discovered else 0.48
tier.add_child(dot)
var tier_label := _label(FishQualityType.display_name(quality), 13)
var tier_label := _label(FishQualityType.display_name(quality), 16)
tier_label.add_theme_color_override(
"font_color",
INK if discovered else MUTED_INK,
@ -659,7 +834,7 @@ func _build_quality_progress(fish_id: StringName) -> VBoxContainer:
else "%s quality not yet collected"
) % FishQualityType.display_name(quality)
tier.add_child(tier_label)
tiers.add_child(tier)
row.add_child(tier)
return quality_section
@ -848,18 +1023,6 @@ func _entry_label_text(entry_name: String) -> String:
)
func _paper_style(color: Color, left: bool) -> StyleBoxFlat:
var style := StyleBoxFlat.new()
style.bg_color = color
style.border_color = Color("9b7a4f")
style.set_border_width_all(3)
style.corner_radius_top_left = 26 if left else 6
style.corner_radius_bottom_left = 32 if left else 6
style.corner_radius_top_right = 6 if left else 28
style.corner_radius_bottom_right = 6 if left else 34
return style
func _build_portrait_overlay() -> void:
_portrait_overlay = Control.new()
_portrait_overlay.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=11 format=3]
[gd_scene load_steps=16 format=3]
[ext_resource type="Script" path="res://ui/pause_menu.gd" id="1_script"]
[ext_resource type="PackedScene" path="res://ui/settings_panel.tscn" id="2_settings"]
@ -10,6 +10,11 @@
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_transition_flurry.gd" id="8_flurry"]
[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_confirmation_page.tscn" id="9_confirmation"]
[ext_resource type="PackedScene" path="res://ui/network/join_game_page.tscn" id="10_join_page"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/online_dark.png" id="11_online_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/save_dark.png" id="12_save_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/settings_dark.png" id="13_settings_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/undo_dark.png" id="14_undo_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/x_dark.png" id="15_x_dark"]
[node name="PauseMenu" type="Control"]
unique_name_in_owner = true
@ -87,7 +92,9 @@ compact_reference_size = Vector2(608, 448)
[node name="ResumeButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "return\nto game"
texture_filter = 1
icon = ExtResource("14_undo_dark")
tooltip_text = "return to game"
accessibility_name = "return to game"
neutral_size = Vector2(174, 164)
desktop_anchor = Vector2(335, 220)
@ -100,7 +107,9 @@ motion_phase = 0.3
[node name="SaveButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "save\nnow"
texture_filter = 1
icon = ExtResource("12_save_dark")
tooltip_text = "save now"
accessibility_name = "save now"
neutral_size = Vector2(126, 120)
desktop_anchor = Vector2(185, 185)
@ -113,7 +122,10 @@ motion_phase = 1.15
[node name="SettingsButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "settings"
texture_filter = 1
icon = ExtResource("13_settings_dark")
tooltip_text = "settings"
accessibility_name = "settings"
neutral_size = Vector2(144, 136)
desktop_anchor = Vector2(485, 170)
compact_anchor = Vector2(515, 175)
@ -125,7 +137,9 @@ motion_phase = 2.05
[node name="JoinGameButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "join\ngame"
texture_filter = 1
icon = ExtResource("11_online_dark")
tooltip_text = "join game"
accessibility_name = "join game"
neutral_size = Vector2(126, 120)
desktop_anchor = Vector2(105, 320)
@ -164,7 +178,10 @@ motion_phase = 3.9
[node name="QuitButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "quit"
texture_filter = 1
icon = ExtResource("15_x_dark")
tooltip_text = "quit"
accessibility_name = "quit"
neutral_size = Vector2(102, 98)
desktop_anchor = Vector2(390, 395)
compact_anchor = Vector2(390, 410)

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=26 format=3]
[gd_scene load_steps=27 format=3]
[ext_resource type="Script" path="res://ui/player_menu.gd" id="1_menu"]
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
@ -24,6 +24,7 @@
[ext_resource type="Script" path="res://ui/components/inventory_notepad.gd" id="22_inventory_notepad"]
[ext_resource type="Script" path="res://ui/components/organizer_tab.gd" id="23_organizer_tab"]
[ext_resource type="PackedScene" path="res://ui/the_net_page.tscn" id="24_the_net_page"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/online_dark.png" id="25_online_dark"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_collection"]
@ -700,11 +701,13 @@ layout_mode = 2
text = "Select bait or a lure for details."
autowrap_mode = 2
[node name="TackleEquipButton" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleDetailPanel/Margin/Layout"]
[node name="TackleEquipButton" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleDetailPanel/Margin/Layout" instance=ExtResource("12_ink_action")]
unique_name_in_owner = true
layout_mode = 2
custom_minimum_size = Vector2(0, 44)
custom_minimum_size = Vector2(150, 68)
size_flags_horizontal = 4
text = "equip worms"
allow_persistent_mark = true
[node name="LogbookPage" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot"]
unique_name_in_owner = true
@ -1506,7 +1509,9 @@ deformation_period = 6.5
unique_name_in_owner = true
layout_mode = 0
toggle_mode = true
text = "online"
texture_filter = 1
icon = ExtResource("25_online_dark")
tooltip_text = "online"
accessibility_name = "online"
script = ExtResource("6_bubble")
profile = ExtResource("4_profile")

View file

@ -6,6 +6,12 @@ const OPTION_GRID_COLUMNS: int = 6
const ControllerMappingManagerType = preload(
"res://settings/controller_mapping_manager.gd"
)
const AnimaleseVoiceType = preload("res://ui/animalese_voice.gd")
const TypewriterRevealType = preload("res://ui/typewriter_reveal.gd")
const VoiceProfilesType = preload(
"res://player/animalese_voice_profiles.gd"
)
const VOICE_CATEGORY_ID: String = "voice"
var _service: NetworkProfileService
var _experience: PlayerExperience
@ -13,6 +19,10 @@ var _draft_name: String = ""
var _draft_appearance: Dictionary = {}
var _persisted_name: String = ""
var _persisted_appearance: Dictionary = {}
var _draft_voice_id: String = VoiceProfilesType.DEFAULT_ID
var _persisted_voice_id: String = VoiceProfilesType.DEFAULT_ID
var _draft_speech_speed_id: String = VoiceProfilesType.DEFAULT_SPEED_ID
var _persisted_speech_speed_id: String = VoiceProfilesType.DEFAULT_SPEED_ID
var _category_id: String = "species"
var _dirty: bool = false
var _allow_duplicate: bool = false
@ -36,6 +46,8 @@ var _experience_progress: ProgressBar
var _experience_value: Label
var _feature_preview_cache: Dictionary = {}
var _scale_value_label: Label
var _voice_preview: AnimaleseVoiceType
var _voice_preview_tween: Tween
func _ready() -> void:
@ -104,6 +116,8 @@ func activate() -> void:
func deactivate() -> void:
visible = false
_debounce.stop()
if _voice_preview_tween != null and _voice_preview_tween.is_valid():
_voice_preview_tween.kill()
_preview.reset_view()
@ -368,15 +382,22 @@ func _build_ui() -> void:
UtilityPageStyle.apply_ocean_button(_keep_editing_button)
confirm_buttons.add_child(_keep_editing_button)
_voice_preview = AnimaleseVoiceType.new()
_voice_preview.name = "ProfileVoicePreview"
add_child(_voice_preview)
_build_categories()
func _build_categories() -> void:
for child: Node in _category_list.get_children():
child.queue_free()
for category_id: String in CharacterCustomizationCatalog.CATEGORY_IDS:
var category_ids: Array[String] = []
for appearance_category: String in CharacterCustomizationCatalog.CATEGORY_IDS:
category_ids.append(appearance_category)
category_ids.append(VOICE_CATEGORY_ID)
for category_id: String in category_ids:
var button := Button.new()
button.text = CharacterCustomizationCatalog.category_label(category_id)
button.text = _category_label(category_id)
button.toggle_mode = true
button.custom_minimum_size.x = 120.0
button.button_pressed = category_id == _category_id
@ -392,7 +413,7 @@ func _select_category(category_id: String) -> void:
var button := child as Button
if button != null:
button.button_pressed = button.text == (
CharacterCustomizationCatalog.category_label(category_id)
_category_label(category_id)
)
_refresh_options()
@ -401,12 +422,15 @@ func _refresh_options() -> void:
for child: Node in _option_list.get_children():
child.queue_free()
var title := Label.new()
title.text = CharacterCustomizationCatalog.category_label(_category_id)
title.text = _category_label(_category_id)
title.add_theme_font_size_override("font_size", 18)
title.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
)
_option_list.add_child(title)
if _category_id == VOICE_CATEGORY_ID:
_build_voice_options()
return
if _category_id == CharacterCustomizationCatalog.SCALE_CATEGORY_ID:
_build_scale_option()
return
@ -440,6 +464,106 @@ func _refresh_options() -> void:
_option_list.add_child(button)
func _category_label(category_id: String) -> String:
return (
"voice"
if category_id == VOICE_CATEGORY_ID
else CharacterCustomizationCatalog.category_label(category_id)
)
func _build_voice_options() -> void:
var description := Label.new()
description.text = "Choose how your character sounds in chat."
description.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
)
_option_list.add_child(description)
var grid := GridContainer.new()
grid.columns = 2
grid.add_theme_constant_override("h_separation", 8)
grid.add_theme_constant_override("v_separation", 8)
_option_list.add_child(grid)
for option: Dictionary in VoiceProfilesType.OPTIONS:
var option_id := str(option.get("id", ""))
var button := Button.new()
button.text = str(option.get("label", option_id))
button.toggle_mode = true
button.custom_minimum_size = Vector2(170.0, 42.0)
button.button_pressed = _draft_voice_id == option_id
button.pressed.connect(_select_voice_option.bind(option_id))
UtilityPageStyle.apply_compact_ocean_button(button)
grid.add_child(button)
var divider := HSeparator.new()
divider.custom_minimum_size.y = 8.0
_option_list.add_child(divider)
var speed_title := Label.new()
speed_title.text = "speech speed"
speed_title.add_theme_font_size_override("font_size", 16)
speed_title.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
)
_option_list.add_child(speed_title)
var speed_description := Label.new()
speed_description.text = "Controls player chat speech on this device only."
speed_description.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
)
_option_list.add_child(speed_description)
var speed_grid := GridContainer.new()
speed_grid.columns = 2
speed_grid.add_theme_constant_override("h_separation", 8)
speed_grid.add_theme_constant_override("v_separation", 8)
_option_list.add_child(speed_grid)
for option: Dictionary in VoiceProfilesType.SPEED_OPTIONS:
var speed_id := str(option.get("id", ""))
var speed_button := Button.new()
speed_button.text = str(option.get("label", speed_id))
speed_button.tooltip_text = "%d characters per second" % roundi(
float(option.get("characters_per_second", 28.0))
)
speed_button.toggle_mode = true
speed_button.custom_minimum_size = Vector2(170.0, 38.0)
speed_button.button_pressed = _draft_speech_speed_id == speed_id
speed_button.pressed.connect(
_select_speech_speed_option.bind(speed_id)
)
UtilityPageStyle.apply_compact_ocean_button(speed_button)
speed_grid.add_child(speed_button)
func _select_voice_option(voice_id: String) -> void:
if not VoiceProfilesType.is_valid(voice_id):
return
_draft_voice_id = voice_id
_dirty = _draft_differs()
_refresh_options()
_refresh_actions()
_play_voice_preview()
func _select_speech_speed_option(speed_id: String) -> void:
if not VoiceProfilesType.is_valid_speed(speed_id):
return
_draft_speech_speed_id = speed_id
_dirty = _draft_differs()
_refresh_options()
_refresh_actions()
_play_voice_preview()
func _play_voice_preview() -> void:
if _voice_preview_tween != null and _voice_preview_tween.is_valid():
_voice_preview_tween.kill()
_voice_preview_tween = _voice_preview.speak_text(
self,
"hello there!",
"profile-preview",
_draft_voice_id,
VoiceProfilesType.speed_for(_draft_speech_speed_id),
)
func _build_scale_option() -> void:
var description := Label.new()
description.text = "Adjust your character's visual size."
@ -741,7 +865,13 @@ func _apply() -> void:
if _service == null:
return
_apply_button.disabled = true
_service.apply_profile(_draft_name, _draft_appearance, _allow_duplicate)
_service.apply_profile(
_draft_name,
_draft_appearance,
_allow_duplicate,
_draft_voice_id,
_draft_speech_speed_id,
)
func _on_apply_finished(accepted: bool, message: String) -> void:
@ -756,8 +886,17 @@ func _load_persisted() -> void:
return
_persisted_name = _service.get_persisted_name()
_persisted_appearance = _service.get_persisted_appearance()
_persisted_voice_id = _service.get_persisted_voice_id()
_persisted_speech_speed_id = (
_service.get_persisted_speech_speed_id()
)
_draft_name = _persisted_name
_draft_appearance = _persisted_appearance.duplicate(true)
_draft_voice_id = _persisted_voice_id
_draft_speech_speed_id = _persisted_speech_speed_id
TypewriterRevealType.set_characters_per_second(
VoiceProfilesType.speed_for(_persisted_speech_speed_id)
)
_name_edit.text = _draft_name
_preview.apply_appearance_profile(_draft_appearance)
_dirty = false
@ -793,6 +932,8 @@ func _confirm_pending_action() -> void:
_discard_confirmation.visible = false
if _confirmation_action == "defaults":
_draft_appearance = CharacterCustomizationCatalog.default_snapshot()
_draft_voice_id = VoiceProfilesType.DEFAULT_ID
_draft_speech_speed_id = VoiceProfilesType.DEFAULT_SPEED_ID
_preview.apply_appearance_profile(_draft_appearance)
_dirty = _draft_differs()
_refresh_options()
@ -806,6 +947,8 @@ func _draft_differs() -> bool:
return (
_draft_name.strip_edges() != _persisted_name
or _draft_appearance != _persisted_appearance
or _draft_voice_id != _persisted_voice_id
or _draft_speech_speed_id != _persisted_speech_speed_id
)

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=7 format=3]
[gd_scene load_steps=9 format=3]
[ext_resource type="Script" path="res://ui/settings_panel.gd" id="1_panel"]
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
@ -6,6 +6,8 @@
[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_button.tscn" id="4_bubble"]
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_cluster.gd" id="5_cluster"]
[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="6_profile"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/undo_dark.png" id="7_undo_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/check_mark_dark.png" id="8_check_mark_dark"]
[node name="SettingsPanel" type="Control"]
visible = false
@ -109,7 +111,9 @@ offset_left = 474.0
offset_top = 308.0
offset_right = 586.0
offset_bottom = 412.0
text = "apply"
icon = ExtResource("8_check_mark_dark")
tooltip_text = "apply"
accessibility_name = "apply"
neutral_size = Vector2(112, 104)
desktop_anchor = Vector2(511, 332)
compact_anchor = Vector2(470, 330)
@ -137,7 +141,9 @@ offset_left = 70.0
offset_top = 343.0
offset_right = 170.0
offset_bottom = 437.0
text = "back"
icon = ExtResource("7_undo_dark")
tooltip_text = "back"
accessibility_name = "back"
neutral_size = Vector2(100, 94)
desktop_anchor = Vector2(220, 300)
compact_anchor = Vector2(155, 275)

View file

@ -1,7 +1,10 @@
[gd_scene load_steps=3 format=3]
[gd_scene load_steps=6 format=3]
[ext_resource type="Script" path="res://ui/surface_drawing_toolbar.gd" id="1_script"]
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/undo_light.png" id="3_undo_light"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/check_mark_light.png" id="4_check_mark_light"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/x_light.png" id="5_x_light"]
[node name="SurfaceDrawingToolbar" type="Control"]
visible = false
@ -55,8 +58,11 @@ text = "⌫"
[node name="UndoButton" type="Button" parent="TopPanel/Top"]
unique_name_in_owner = true
layout_mode = 2
texture_filter = 1
tooltip_text = "undo last stroke"
text = "↶"
accessibility_name = "undo last stroke"
icon = ExtResource("3_undo_light")
expand_icon = true
[node name="HideGuideButton" type="Button" parent="TopPanel/Top"]
unique_name_in_owner = true
@ -75,15 +81,21 @@ text = "▤"
[node name="FinalizeGuideButton" type="Button" parent="TopPanel/Top"]
unique_name_in_owner = true
layout_mode = 2
texture_filter = 1
tooltip_text = "finish grid on next click"
toggle_mode = true
text = "✓"
accessibility_name = "finish grid on next click"
icon = ExtResource("4_check_mark_light")
expand_icon = true
[node name="CloseButton" type="Button" parent="TopPanel/Top"]
unique_name_in_owner = true
layout_mode = 2
texture_filter = 1
tooltip_text = "close art tools"
text = "×"
accessibility_name = "close art tools"
icon = ExtResource("5_x_light")
expand_icon = true
[node name="ColorPanel" type="PanelContainer" parent="."]
unique_name_in_owner = true

View file

@ -407,9 +407,9 @@ func _refresh_forecast(force: bool) -> void:
slot.mouse_filter = Control.MOUSE_FILTER_IGNORE
_forecast_list.add_child(slot)
var icon := WeatherIcon.new()
icon.position = Vector2(1.0, 7.0)
icon.size = Vector2(56.0, 56.0)
icon.custom_minimum_size = Vector2(56.0, 56.0)
icon.position = Vector2(10.5, 17.0)
icon.size = Vector2(42.0, 42.0)
icon.custom_minimum_size = Vector2(42.0, 42.0)
icon.set_weather(weather)
icon.set_nighttime(
WorldTimeService.phase_for_hour(start_hour)
@ -417,13 +417,13 @@ func _refresh_forecast(force: bool) -> void:
)
slot.add_child(icon)
var time_label := Label.new()
time_label.position = Vector2(34.0, 0.0)
time_label.size = Vector2(29.0, 18.0)
time_label.position = Vector2(27.0, 0.0)
time_label.size = Vector2(36.0, 22.0)
time_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
time_label.text = _forecast_exponent_time(start_hour)
time_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
time_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
time_label.add_theme_font_size_override("font_size", 10)
time_label.add_theme_font_size_override("font_size", 13)
time_label.add_theme_constant_override("outline_size", 3)
time_label.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=14 format=3]
[gd_scene load_steps=17 format=3]
[ext_resource type="Script" path="res://ui/title_screen.gd" id="1_script"]
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
@ -11,6 +11,9 @@
[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="9_bubble_profile"]
[ext_resource type="PackedScene" path="res://ui/title_confirmation_bubble_page.tscn" id="10_confirmation_page"]
[ext_resource type="PackedScene" path="res://ui/network/join_game_page.tscn" id="11_join_page"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/online_dark.png" id="12_online_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/settings_dark.png" id="13_settings_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/x_dark.png" id="14_x_dark"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water"]
shader = ExtResource("4_water_shader")
@ -303,7 +306,10 @@ offset_left = 264.0
offset_top = 17.0
offset_right = 384.0
offset_bottom = 133.0
text = "settings"
texture_filter = 1
icon = ExtResource("13_settings_dark")
tooltip_text = "settings"
accessibility_name = "settings"
script = ExtResource("7_bubble_button")
profile = ExtResource("9_bubble_profile")
desktop_anchor = Vector2(324, 75)
@ -322,7 +328,10 @@ offset_left = 126.0
offset_top = 224.0
offset_right = 248.0
offset_bottom = 342.0
text = "join\ngame"
texture_filter = 1
icon = ExtResource("12_online_dark")
tooltip_text = "join game"
accessibility_name = "join game"
script = ExtResource("7_bubble_button")
profile = ExtResource("9_bubble_profile")
neutral_size = Vector2(122, 118)
@ -380,7 +389,10 @@ offset_left = 275.0
offset_top = 217.0
offset_right = 369.0
offset_bottom = 309.0
text = "quit"
texture_filter = 1
icon = ExtResource("14_x_dark")
tooltip_text = "quit"
accessibility_name = "quit"
script = ExtResource("7_bubble_button")
profile = ExtResource("9_bubble_profile")
neutral_size = Vector2(94, 92)

47
ui/typewriter_reveal.gd Normal file
View file

@ -0,0 +1,47 @@
class_name TypewriterReveal
extends RefCounted
const DEFAULT_CHARACTERS_PER_SECOND: float = 28.0
const MIN_CHARACTERS_PER_SECOND: float = 12.0
const MAX_CHARACTERS_PER_SECOND: float = 60.0
static var _characters_per_second: float = DEFAULT_CHARACTERS_PER_SECOND
static func set_characters_per_second(value: float) -> void:
_characters_per_second = clampf(
value,
MIN_CHARACTERS_PER_SECOND,
MAX_CHARACTERS_PER_SECOND,
)
static func get_characters_per_second() -> float:
return _characters_per_second
static func start(
label: Label,
characters_per_second: float = -1.0,
) -> float:
if label == null:
return 0.0
var character_count: int = label.get_total_character_count()
if character_count <= 0:
label.visible_characters = -1
return 0.0
label.visible_characters = 0
var resolved_characters_per_second := (
characters_per_second
if characters_per_second > 0.0
else _characters_per_second
)
var duration := float(character_count) / resolved_characters_per_second
var reveal_tween := label.create_tween()
reveal_tween.tween_property(
label,
"visible_characters",
character_count,
duration,
).from(0).set_trans(Tween.TRANS_LINEAR)
return duration

View file

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

View file

@ -1,11 +1,21 @@
class_name WeatherIcon
extends Control
const BUBBLE_COLOR := Color(0.025, 0.13, 0.19, 0.94)
const ICON_COLOR := Color(0.78, 0.91, 0.95)
const SUN_COLOR := Color(0.98, 0.82, 0.34)
const MOON_COLOR := Color(0.78, 0.88, 1.0)
const RAIN_COLOR := Color(0.28, 0.73, 0.82)
const CLEAR_DAY_TEXTURE: Texture2D = preload(
"res://ui/icons/weather/weather_clear_day.png"
)
const CLEAR_NIGHT_TEXTURE: Texture2D = preload(
"res://ui/icons/weather/weather_clear_night_full.png"
)
const CLOUDY_TEXTURE: Texture2D = preload(
"res://ui/icons/weather/weather_cloudy.png"
)
const FOG_TEXTURE: Texture2D = preload(
"res://ui/icons/weather/weather_fog.png"
)
const RAIN_TEXTURE: Texture2D = preload(
"res://ui/icons/weather/weather_rain.png"
)
var _weather: WorldWeatherService.Weather = WorldWeatherService.Weather.SUNNY
var _is_nighttime: bool = false
@ -14,6 +24,7 @@ var _is_nighttime: bool = false
func _ready() -> void:
mouse_filter = Control.MOUSE_FILTER_PASS
focus_mode = Control.FOCUS_NONE
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
custom_minimum_size = Vector2(34.0, 34.0)
_update_tooltip()
queue_redraw()
@ -44,55 +55,22 @@ func get_weather() -> WorldWeatherService.Weather:
func _draw() -> void:
var center: Vector2 = size * 0.5
var radius: float = minf(size.x, size.y) * 0.5
draw_circle(center, radius, BUBBLE_COLOR)
var icon_size := Vector2.ONE * minf(size.x, size.y)
var icon_rect := Rect2((size - icon_size) * 0.5, icon_size)
draw_texture_rect(_get_weather_texture(), icon_rect, false)
func _get_weather_texture() -> Texture2D:
match _weather:
WorldWeatherService.Weather.SUNNY:
if _is_nighttime:
_draw_moon(center)
else:
_draw_sun(center)
return CLEAR_NIGHT_TEXTURE if _is_nighttime else CLEAR_DAY_TEXTURE
WorldWeatherService.Weather.CLOUDY:
_draw_cloud(center + Vector2(0.0, 1.0))
return CLOUDY_TEXTURE
WorldWeatherService.Weather.RAINY:
_draw_cloud(center + Vector2(0.0, -2.0))
for x_offset: float in PackedFloat32Array([-6.0, 0.0, 6.0]):
draw_line(
center + Vector2(x_offset + 1.0, 5.0),
center + Vector2(x_offset - 1.0, 9.0),
RAIN_COLOR,
2.0,
true,
)
return RAIN_TEXTURE
WorldWeatherService.Weather.FOGGY:
for y_offset: float in PackedFloat32Array([-6.0, 0.0, 6.0]):
draw_line(
center + Vector2(-9.0, y_offset),
center + Vector2(9.0, y_offset),
ICON_COLOR,
2.0,
true,
)
func _draw_sun(center: Vector2) -> void:
draw_circle(center, 5.0, SUN_COLOR)
for index: int in 8:
var angle: float = TAU * float(index) / 8.0
var direction := Vector2(cos(angle), sin(angle))
draw_line(
center + direction * 8.0,
center + direction * 11.0,
SUN_COLOR,
2.0,
true,
)
func _draw_moon(center: Vector2) -> void:
draw_circle(center, 8.0, MOON_COLOR)
draw_circle(center + Vector2(4.0, -2.0), 7.0, BUBBLE_COLOR)
return FOG_TEXTURE
return CLEAR_DAY_TEXTURE
func _update_tooltip() -> void:
@ -101,10 +79,3 @@ func _update_tooltip() -> void:
if _weather == WorldWeatherService.Weather.SUNNY
else WorldWeatherService.weather_name(_weather)
)
func _draw_cloud(center: Vector2) -> void:
draw_circle(center + Vector2(-5.0, 0.0), 5.0, ICON_COLOR)
draw_circle(center + Vector2(0.0, -3.0), 6.0, ICON_COLOR)
draw_circle(center + Vector2(6.0, 0.0), 5.0, ICON_COLOR)
draw_rect(Rect2(center + Vector2(-9.0, 0.0), Vector2(18.0, 5.0)), ICON_COLOR)