Add character calls, settings controls, and gameplay fixes

This commit is contained in:
Alexander Sellite 2026-08-09 11:22:35 -04:00
parent 7b2898c11a
commit 40696b6bd9
33 changed files with 1120 additions and 78 deletions

View file

@ -24,6 +24,7 @@ func _ready() -> void:
polyphonic_stream.polyphony = POLYPHONY
_player = AudioStreamPlayer.new()
_player.name = "AnimaleseAudio"
_player.bus = &"SFX"
_player.stream = polyphonic_stream
add_child(_player)
_player.play()

View file

@ -215,9 +215,9 @@ func open_chat() -> void:
_session.submit_neutral_local_movement()
_entry.show()
_entry.virtual_keyboard_enabled = false
_entry.grab_focus()
_hint.hide()
_refresh_input_ownership()
call_deferred("_focus_entry_after_open")
_update_panel_opacity(true)
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
@ -304,6 +304,13 @@ func request_virtual_keyboard() -> bool:
return true
func _focus_entry_after_open() -> void:
if not _opened or not _entry.visible:
return
_entry.grab_focus()
_refresh_input_ownership()
func is_open() -> bool:
return _opened
@ -314,7 +321,13 @@ func is_collapsed() -> bool:
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("open_chat"):
toggle_chat()
if event is InputEventKey:
if _opened:
_focus_entry_after_open()
else:
open_chat()
else:
toggle_chat()
get_viewport().set_input_as_handled()
return
if event is InputEventKey and event.pressed and not event.echo:
@ -796,7 +809,7 @@ func _handle_time_command(parts: PackedStringArray) -> void:
"World time set to %s (%s)."
% [phase_name, _world_time.get_clock_text()]
)
close_chat()
call_deferred("close_chat")
func _on_local_message_confirmed(message: Dictionary) -> void:
@ -811,7 +824,7 @@ func _on_local_message_confirmed(message: Dictionary) -> void:
_entry.clear()
_set_status("")
_flush_draft()
close_chat()
call_deferred("close_chat")
func _on_message(message: Dictionary) -> void:

View file

@ -50,7 +50,7 @@ enum ShopSection {
const SHOP_SECTION_LABELS: Array[String] = [
"Upgrades",
"Bait",
"Bait and Lures",
"Snacks",
"Equipment",
"Art Supplies",
@ -143,13 +143,19 @@ func _focus_buy_page() -> void:
func _build_shop_tabs() -> void:
_shop_tab_bar.custom_minimum_size.y = 44.0
_shop_tab_bar.show()
for section_index: int in range(ShopSection.size()):
var tab: OrganizerTab = OrganizerTabType.new()
tab.text = SHOP_SECTION_LABELS[section_index]
tab.palette_index = section_index
tab.custom_minimum_size = Vector2(132.0, 42.0)
tab.size_flags_horizontal = Control.SIZE_EXPAND_FILL
tab.focus_mode = Control.FOCUS_ALL
tab.mouse_filter = Control.MOUSE_FILTER_STOP
tab.pressed.connect(_select_shop_section.bind(section_index, true))
_shop_tab_bar.add_child(tab)
tab.show()
_shop_tabs.append(tab)
_select_shop_section(ShopSection.UPGRADES, false)
@ -287,6 +293,7 @@ func open_shop() -> bool:
_feedback.text = ""
deactivate_shop_cooler_page()
show()
_shop_tab_bar.show()
_select_shop_section(ShopSection.UPGRADES, false)
_refresh_all()
_buy_mode_button.grab_focus()
@ -312,6 +319,7 @@ func get_shop_cooler_mount() -> Control:
func activate_shop_cooler_page() -> void:
_shop_tab_bar.hide()
_cooler_page_active = true
_shop_panel.hide()
_shop_cooler_page.show()
@ -319,6 +327,8 @@ func activate_shop_cooler_page() -> void:
func deactivate_shop_cooler_page() -> void:
if visible:
_shop_tab_bar.show()
_cooler_page_active = false
_cooler_modal_open = false
_back_to_shop_button.disabled = false
@ -468,10 +478,32 @@ func _refresh_supplies() -> void:
if _shop_section == ShopSection.UPGRADES:
return
_stock_title.text = SHOP_SECTION_LABELS[int(_shop_section)]
for item_id: StringName in FishingShopStockType.get_stock_item_ids():
var stock_item_ids: Array[StringName] = (
FishingShopStockType.get_stock_item_ids()
)
if _shop_section == ShopSection.BAIT:
var grouped_item_ids: Array[StringName] = []
for item_id: StringName in stock_item_ids:
var bait_item: ItemDataType = _item_catalog.get_item_by_id(item_id)
if bait_item != null and bait_item.is_bait():
grouped_item_ids.append(item_id)
for item_id: StringName in stock_item_ids:
var lure_item: ItemDataType = _item_catalog.get_item_by_id(item_id)
if lure_item != null and lure_item.is_lure():
grouped_item_ids.append(item_id)
stock_item_ids = grouped_item_ids
var current_stock_group: StringName = StringName()
for item_id: StringName in stock_item_ids:
var item: ItemDataType = _item_catalog.get_item_by_id(item_id)
if item == null or not _item_belongs_in_current_section(item):
continue
if _shop_section == ShopSection.BAIT:
var stock_group: StringName = (
&"bait" if item.is_bait() else &"lures"
)
if stock_group != current_stock_group:
_add_stock_section(str(stock_group))
current_stock_group = stock_group
var button := Button.new()
button.custom_minimum_size = Vector2(195, 54)
button.icon = item.icon
@ -563,14 +595,11 @@ func _refresh_supplies() -> void:
func _item_belongs_in_current_section(item: ItemDataType) -> bool:
match _shop_section:
ShopSection.BAIT:
return item.is_bait()
return item.is_bait() or item.is_lure()
ShopSection.SNACKS:
return item.category == ItemDataType.Category.CONSUMABLE
ShopSection.EQUIPMENT:
return item.category in [
ItemDataType.Category.TOOL,
ItemDataType.Category.LURE,
]
return item.category == ItemDataType.Category.TOOL
return false

View file

@ -64,6 +64,7 @@ offset_bottom = 104.0
text = "back to shop"
[node name="ShopPanel" type="PanelContainer" parent="."]
z_index = 2
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 8
@ -143,9 +144,13 @@ text = ""
horizontal_alignment = 1
theme_override_colors/font_color = Color(1, 0.82, 0.4, 1)
[node name="ShopTabBar" type="HBoxContainer" parent="ShopPanel/Margin/Layout"]
[node name="ShopTabBar" type="HBoxContainer" parent="."]
unique_name_in_owner = true
layout_mode = 2
z_index = 1
offset_left = 298.0
offset_top = 26.0
offset_right = 982.0
offset_bottom = 70.0
alignment = 1
theme_override_constants/separation = 6

View file

@ -93,6 +93,7 @@ const FISHING_PANEL_SHOWCASE_BOTTOM_OFFSET: float = -104.0
@onready var _status_label: Label = %StatusLabel
@onready var _bite_prompt_button: Button = %BitePromptButton
@onready var _gameplay_transient_hud: Control = %GameplayTransientHUD
@onready var _active_bait_indicator: Control = %ActiveBaitIndicator
@onready var _active_bait_button: Button = %ActiveBaitButton
@onready var _active_bait_quantity_badge: Panel = %ActiveBaitQuantityBadge
@onready var _active_bait_quantity: Label = %ActiveBaitQuantity
@ -114,9 +115,13 @@ const FISHING_PANEL_SHOWCASE_BOTTOM_OFFSET: float = -104.0
@onready var _experience_bubble_label: Label = %ExperienceBubbleLabel
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"
)
const SHOP_ANIMALESE_VOICE_ID: String = "natural"
const SHOP_ANIMALESE_BASE_PITCH: float = 1.08
const SHOP_SPEECH_CHARACTERS_PER_SECOND: float = 28.0
const SHOP_NPC_SPEECH_COOLDOWN_MILLISECONDS: int = 5000
@onready var _canonical_stage: Control = %CanonicalStage
@ -136,6 +141,7 @@ const SHOP_SPEECH_CHARACTERS_PER_SECOND: float = 28.0
var _shop_animalese_voice: AnimaleseVoiceType
var _shop_npc_player_in_range: bool = false
var _shop_npc_spoken_for_current_visit: bool = false
var _shop_npc_next_speech_msec: int = 0
@onready var _effect_status: Label = %EffectStatus
@onready var _chat_ui: ChatUIType = %ChatUI
@onready var _emote_radial_menu: EmoteRadialMenuType = %EmoteRadialMenu
@ -155,6 +161,9 @@ var _shop_npc_spoken_for_current_visit: bool = false
var _showcase_active: bool = false
var _player: PlayerType
var _network_chat_service: NetworkChatService
var _network_profile: NetworkProfilePreferences
var _spawn_service: PlayerSpawnService
var _bag: PlayerBagType
var _item_catalog: ItemCatalogType
var _player_menu_open: bool = false
@ -279,6 +288,9 @@ func setup(
world_sun: DirectionalLight3D,
) -> void:
_player = player
_network_chat_service = network_chat_service
_network_profile = network_profile
_spawn_service = spawn_service
_bag = bag
_item_catalog = item_catalog
_settings_manager = settings_manager
@ -300,6 +312,15 @@ func setup(
):
_bag.contents_changed.connect(_on_hud_bait_inventory_changed)
_refresh_active_bait_indicator()
if (
_network_chat_service != null
and not _network_chat_service.character_call_received.is_connected(
_on_character_call_received
)
):
_network_chat_service.character_call_received.connect(
_on_character_call_received
)
if (
_experience != null
and not _experience.experience_awarded.is_connected(
@ -411,6 +432,12 @@ func _input(event: InputEvent) -> void:
and _surface_drawing_toolbar.owns_pointer_event(event)
):
return
if event.is_action_pressed("character_call") and _can_use_character_call():
_network_chat_service.send_local_character_call(
_network_profile.call_id
)
get_viewport().set_input_as_handled()
return
var drawing_can_open: bool = (
_gameplay_ui_enabled
and not _system_menu_open
@ -479,6 +506,49 @@ func _input(event: InputEvent) -> void:
get_viewport().set_input_as_handled()
func _can_use_character_call() -> bool:
return (
_gameplay_ui_enabled
and not _system_menu_open
and not _player_menu_open
and not _shop_open
and not _chat_input_open
and not _showcase_active
and not _virtual_mouse_active
and _network_chat_service != null
and _network_profile != null
)
func _on_character_call_received(
peer_id: int,
call_id: String,
pitch_scale: float,
) -> void:
if _spawn_service == null:
return
var avatar: PlayerType = _spawn_service.get_avatar(peer_id)
var audio_path: String = VoiceProfilesType.call_audio_path(call_id)
if avatar == null or not ResourceLoader.exists(audio_path, "AudioStream"):
return
var stream: AudioStream = load(audio_path) as AudioStream
if stream == null:
return
var call_player := AudioStreamPlayer3D.new()
call_player.name = "CharacterCall"
call_player.bus = &"SFX"
call_player.stream = stream
call_player.pitch_scale = pitch_scale
call_player.max_distance = 28.0
call_player.unit_size = 4.0
call_player.position = (
Vector3.UP * 1.25 * avatar.get_character_visual_scale()
)
avatar.add_child(call_player)
call_player.finished.connect(call_player.queue_free)
call_player.play()
func _handle_controller_chat_controls(event: InputEvent) -> bool:
var button_event: InputEventJoypadButton = event as InputEventJoypadButton
if (
@ -1274,6 +1344,7 @@ func _apply_active_bait_indicator_style() -> void:
func _refresh_active_bait_indicator() -> void:
_refresh_active_bait_indicator_visibility()
if not is_node_ready():
return
var item: ItemDataType
@ -1313,12 +1384,22 @@ func _on_hud_active_bait_changed(_item_id: StringName) -> void:
_refresh_active_bait_indicator()
func _refresh_active_bait_indicator_visibility() -> void:
_active_bait_indicator.visible = (
_gameplay_ui_enabled
and not _system_menu_open
and not _player_menu_open
and not _shop_open
)
func _on_hud_bait_inventory_changed() -> void:
_refresh_active_bait_indicator()
func set_gameplay_ui_enabled(enabled: bool) -> void:
_gameplay_ui_enabled = enabled
_refresh_active_bait_indicator_visibility()
if enabled:
_try_start_shop_npc_speech()
_gameplay_transient_hud.visible = enabled and not _player_menu_open
@ -1411,6 +1492,12 @@ func _try_start_shop_npc_speech() -> void:
):
return
_shop_npc_spoken_for_current_visit = true
var now_msec: int = Time.get_ticks_msec()
if now_msec < _shop_npc_next_speech_msec:
return
_shop_npc_next_speech_msec = (
now_msec + SHOP_NPC_SPEECH_COOLDOWN_MILLISECONDS
)
_ensure_shop_animalese_voice()
TypewriterRevealType.start(
_shop_prompt_message,
@ -2132,6 +2219,7 @@ func _refresh_hotbar_visibility() -> void:
func _emit_interactive_pointer_ui_changed() -> void:
_refresh_active_bait_indicator_visibility()
interactive_pointer_ui_changed.emit(
_system_menu_open or _player_menu_open or _shop_open or _chat_input_open
)

View file

@ -24,6 +24,8 @@ 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 _draft_call_id: String = VoiceProfilesType.DEFAULT_CALL_ID
var _persisted_call_id: String = VoiceProfilesType.DEFAULT_CALL_ID
var _category_id: String = "species"
var _dirty: bool = false
var _allow_duplicate: bool = false
@ -478,45 +480,54 @@ func _category_label(category_id: String) -> String:
func _build_voice_options() -> void:
var description := Label.new()
description.text = "Choose how your character sounds in chat."
description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
description.size_flags_horizontal = Control.SIZE_EXPAND_FILL
description.add_theme_font_size_override("font_size", 12)
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)
grid.columns = 3
grid.add_theme_constant_override("h_separation", 6)
grid.add_theme_constant_override("v_separation", 4)
grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_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.custom_minimum_size = Vector2(108.0, 32.0)
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
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
divider.custom_minimum_size.y = 2.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_font_size_override("font_size", 13)
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.text = "Controls chat speech on this device only."
speed_description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
speed_description.size_flags_horizontal = Control.SIZE_EXPAND_FILL
speed_description.add_theme_font_size_override("font_size", 12)
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)
speed_grid.columns = 3
speed_grid.add_theme_constant_override("h_separation", 6)
speed_grid.add_theme_constant_override("v_separation", 4)
speed_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_option_list.add_child(speed_grid)
for option: Dictionary in VoiceProfilesType.SPEED_OPTIONS:
var speed_id := str(option.get("id", ""))
@ -526,13 +537,49 @@ func _build_voice_options() -> void:
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.custom_minimum_size = Vector2(108.0, 32.0)
speed_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
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)
var call_divider := HSeparator.new()
call_divider.custom_minimum_size.y = 2.0
_option_list.add_child(call_divider)
var call_title := Label.new()
call_title.text = "call"
call_title.add_theme_font_size_override("font_size", 13)
call_title.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
)
_option_list.add_child(call_title)
var call_description := Label.new()
call_description.text = "Press G to make this sound."
call_description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
call_description.size_flags_horizontal = Control.SIZE_EXPAND_FILL
call_description.add_theme_font_size_override("font_size", 12)
call_description.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
)
_option_list.add_child(call_description)
var call_grid := GridContainer.new()
call_grid.columns = 2
call_grid.add_theme_constant_override("h_separation", 8)
call_grid.add_theme_constant_override("v_separation", 8)
_option_list.add_child(call_grid)
for option: Dictionary in VoiceProfilesType.CALL_OPTIONS:
var call_id := str(option.get("id", ""))
var call_button := Button.new()
call_button.text = str(option.get("label", call_id))
call_button.toggle_mode = true
call_button.custom_minimum_size = Vector2(108.0, 32.0)
call_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
call_button.button_pressed = _draft_call_id == call_id
call_button.pressed.connect(_select_call_option.bind(call_id))
UtilityPageStyle.apply_compact_ocean_button(call_button)
call_grid.add_child(call_button)
func _select_voice_option(voice_id: String) -> void:
@ -555,6 +602,15 @@ func _select_speech_speed_option(speed_id: String) -> void:
_play_voice_preview()
func _select_call_option(call_id: String) -> void:
if not VoiceProfilesType.is_valid_call(call_id):
return
_draft_call_id = call_id
_dirty = _draft_differs()
_refresh_options()
_refresh_actions()
func _play_voice_preview() -> void:
if _voice_preview_tween != null and _voice_preview_tween.is_valid():
_voice_preview_tween.kill()
@ -982,6 +1038,7 @@ func _apply() -> void:
_allow_duplicate,
_draft_voice_id,
_draft_speech_speed_id,
_draft_call_id,
)
@ -1001,10 +1058,12 @@ func _load_persisted() -> void:
_persisted_speech_speed_id = (
_service.get_persisted_speech_speed_id()
)
_persisted_call_id = _service.get_persisted_call_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
_draft_call_id = _persisted_call_id
TypewriterRevealType.set_characters_per_second(
VoiceProfilesType.speed_for(_persisted_speech_speed_id)
)
@ -1045,6 +1104,7 @@ func _confirm_pending_action() -> void:
_draft_appearance = CharacterCustomizationCatalog.default_snapshot()
_draft_voice_id = VoiceProfilesType.DEFAULT_ID
_draft_speech_speed_id = VoiceProfilesType.DEFAULT_SPEED_ID
_draft_call_id = VoiceProfilesType.DEFAULT_CALL_ID
_preview.apply_appearance_profile(_draft_appearance)
_dirty = _draft_differs()
_refresh_options()
@ -1060,6 +1120,7 @@ func _draft_differs() -> bool:
or _draft_appearance != _persisted_appearance
or _draft_voice_id != _persisted_voice_id
or _draft_speech_speed_id != _persisted_speech_speed_id
or _draft_call_id != _persisted_call_id
)

View file

@ -19,7 +19,7 @@ const ControllerFocusNavigationType = preload(
var _cluster: BubbleCluster
var _bubbles: Array[BubbleButton] = []
var _focus_bubbles: Array[BubbleButton] = []
var _focus_controls: Array[Control] = []
var _transition: Tween
var _transition_generation: int = 0
var _resting_cluster_position: Vector2 = Vector2.ZERO
@ -33,9 +33,9 @@ func _ready() -> void:
if bubble != null:
_bubbles.append(bubble)
for path: NodePath in focus_paths:
var bubble := get_node(path) as BubbleButton
if bubble != null:
_focus_bubbles.append(bubble)
var focus_control := get_node(path) as Control
if focus_control != null:
_focus_controls.append(focus_control)
_cluster.configure(_bubbles)
_configure_focus_order()
resized.connect(_update_layout)
@ -173,13 +173,13 @@ func _update_layout() -> void:
_cluster.position = _resting_cluster_position
_cluster.size = field_size
_cluster.apply_layout(field_size, compact)
ControllerFocusNavigationType.configure_spatial_neighbors(_focus_bubbles)
ControllerFocusNavigationType.configure_spatial_neighbors(_focus_controls)
func _configure_focus_order() -> void:
for bubble: BubbleButton in _bubbles:
bubble.focus_mode = Control.FOCUS_NONE
ControllerFocusNavigationType.configure_spatial_neighbors(_focus_bubbles)
ControllerFocusNavigationType.configure_spatial_neighbors(_focus_controls)
func _set_interactive(interactive: bool) -> void:
@ -188,11 +188,11 @@ func _set_interactive(interactive: bool) -> void:
if interactive
else Control.MOUSE_FILTER_IGNORE
)
for bubble: BubbleButton in _focus_bubbles:
bubble.focus_mode = (
for focus_control: Control in _focus_controls:
focus_control.focus_mode = (
Control.FOCUS_ALL if interactive else Control.FOCUS_NONE
)
bubble.mouse_filter = (
focus_control.mouse_filter = (
Control.MOUSE_FILTER_STOP
if interactive
else Control.MOUSE_FILTER_IGNORE

View file

@ -5,6 +5,7 @@ const MAX_PANEL_SIZE: Vector2 = Vector2(960.0, 700.0)
const PANEL_EDGE_MARGIN: float = 16.0
const PAGE_ROOT: StringName = &"root"
const PAGE_DISPLAY: StringName = &"display"
const PAGE_SOUND: StringName = &"sound"
const PAGE_CONTROLS: StringName = &"controls"
const PAGE_ACCESSIBILITY: StringName = &"accessibility"
const PAGE_DATA: StringName = &"data"
@ -40,6 +41,7 @@ enum PresentationMode {
@onready var _root_page: SettingsBubblePage = %RootPage
@onready var _display_page: SettingsBubblePage = %DisplayPage
@onready var _sound_page: SettingsBubblePage = %SoundPage
@onready var _controls_page: SettingsBubblePage = %ControlsPage
@onready var _accessibility_page: SettingsBubblePage = %AccessibilityPage
@onready var _data_page: SettingsBubblePage = %DataPage
@ -50,12 +52,21 @@ enum PresentationMode {
@onready var _chat_dock: BubbleButton = %ChatDock
@onready var _chat_mode: BubbleButton = %ChatMode
@onready var _paint_dock: BubbleButton = %PaintDock
@onready var _fullscreen_toggle: BubbleButton = %FullscreenToggle
@onready var _mouse_value: BubbleButton = %MouseValue
@onready var _controller_value: BubbleButton = %ControllerValue
@onready var _invert_y_toggle: BubbleButton = %InvertYToggle
@onready var _on_screen_keyboard_toggle: BubbleButton = %OnScreenKeyboardToggle
@onready var _auto_click_toggle: BubbleButton = %AutoClickToggle
@onready var _auto_click_interval: BubbleButton = %AutoClickIntervalValue
@onready var _master_volume_slider: HSlider = %MasterVolumeSlider
@onready var _music_volume_slider: HSlider = %MusicVolumeSlider
@onready var _effects_volume_slider: HSlider = %EffectsVolumeSlider
@onready var _environment_volume_slider: HSlider = %EnvironmentVolumeSlider
@onready var _master_volume_value: Label = %MasterVolumeValue
@onready var _music_volume_value: Label = %MusicVolumeValue
@onready var _effects_volume_value: Label = %EffectsVolumeValue
@onready var _environment_volume_value: Label = %EnvironmentVolumeValue
@onready var _world_options: Array[BubbleButton] = [
%WorldLegible,
@ -86,6 +97,10 @@ var _mouse_sensitivity: float = 0.005
var _controller_sensitivity: float = 2.5
var _invert_camera_y: bool = false
var _on_screen_keyboard_enabled: bool = false
var _master_volume: float = 1.0
var _music_volume: float = 1.0
var _effects_volume: float = 1.0
var _environment_volume: float = 1.0
var _network_profile: NetworkProfilePreferences
var _network_session: NetworkSession
var _data_root: PlayerDataRoot
@ -111,11 +126,13 @@ func _ready() -> void:
_pages = {
PAGE_ROOT: _root_page,
PAGE_DISPLAY: _display_page,
PAGE_SOUND: _sound_page,
PAGE_CONTROLS: _controls_page,
PAGE_ACCESSIBILITY: _accessibility_page,
PAGE_DATA: _data_page,
}
%DisplayCategory.pressed.connect(_push_page.bind(PAGE_DISPLAY))
%SoundCategory.pressed.connect(_push_page.bind(PAGE_SOUND))
%ControlsCategory.pressed.connect(_push_page.bind(PAGE_CONTROLS))
%AccessibilityCategory.pressed.connect(
_push_page.bind(PAGE_ACCESSIBILITY)
@ -124,12 +141,14 @@ func _ready() -> void:
%ApplySettingsButton.pressed.connect(_apply_settings)
%RootBackButton.pressed.connect(close_panel)
%DisplayBackButton.pressed.connect(handle_back)
%SoundBackButton.pressed.connect(handle_back)
%ControlsBackButton.pressed.connect(handle_back)
%ControllerMapping.pressed.connect(_open_controller_mapping)
%AccessibilityBackButton.pressed.connect(handle_back)
%DataBackButton.pressed.connect(handle_back)
%RootBackButton.gui_input.connect(_on_back_bubble_gui_input)
%DisplayBackButton.gui_input.connect(_on_back_bubble_gui_input)
%SoundBackButton.gui_input.connect(_on_back_bubble_gui_input)
%ControlsBackButton.gui_input.connect(_on_back_bubble_gui_input)
%AccessibilityBackButton.gui_input.connect(_on_back_bubble_gui_input)
%DataBackButton.gui_input.connect(_on_back_bubble_gui_input)
@ -150,6 +169,7 @@ func _ready() -> void:
_chat_dock.pressed.connect(_toggle_chat_dock)
_chat_mode.pressed.connect(_toggle_chat_mode)
_paint_dock.pressed.connect(_toggle_paint_dock)
_fullscreen_toggle.pressed.connect(_toggle_fullscreen)
%MouseDecrease.pressed.connect(_adjust_mouse_sensitivity.bind(-1))
%MouseIncrease.pressed.connect(_adjust_mouse_sensitivity.bind(1))
_mouse_value.pressed.connect(_adjust_mouse_sensitivity.bind(1))
@ -177,6 +197,18 @@ func _ready() -> void:
_auto_click_interval.pressed.connect(
_adjust_auto_click_interval.bind(1)
)
_master_volume_slider.value_changed.connect(
_on_audio_volume_changed.bind(&"master")
)
_music_volume_slider.value_changed.connect(
_on_audio_volume_changed.bind(&"music")
)
_effects_volume_slider.value_changed.connect(
_on_audio_volume_changed.bind(&"effects")
)
_environment_volume_slider.value_changed.connect(
_on_audio_volume_changed.bind(&"environment")
)
_mouse_value.gui_input.connect(
_on_continuous_value_input.bind(&"mouse")
)
@ -198,6 +230,7 @@ func _ready() -> void:
_on_controller_mapping_panel_closed
)
_style_data_page()
_style_sound_page()
call_deferred("_refresh_panel_size")
@ -708,6 +741,10 @@ func _apply_settings() -> void:
edited.controller_camera_sensitivity = _controller_sensitivity
edited.invert_camera_y = _invert_camera_y
edited.on_screen_keyboard_enabled = _on_screen_keyboard_enabled
edited.master_volume = _master_volume
edited.music_volume = _music_volume
edited.effects_volume = _effects_volume
edited.environment_volume = _environment_volume
if _settings_manager.apply_settings(edited):
if (
_presentation_mode == PresentationMode.TITLE_EMBEDDED
@ -730,6 +767,17 @@ func _load_controls() -> void:
_controller_sensitivity = settings.controller_camera_sensitivity
_invert_camera_y = settings.invert_camera_y
_on_screen_keyboard_enabled = settings.on_screen_keyboard_enabled
_master_volume = settings.master_volume
_music_volume = settings.music_volume
_effects_volume = settings.effects_volume
_environment_volume = settings.environment_volume
_master_volume_slider.set_value_no_signal(_master_volume * 100.0)
_music_volume_slider.set_value_no_signal(_music_volume * 100.0)
_effects_volume_slider.set_value_no_signal(_effects_volume * 100.0)
_environment_volume_slider.set_value_no_signal(
_environment_volume * 100.0
)
_settings_manager.restore_audio_levels()
_refresh_value_labels()
@ -752,6 +800,9 @@ func _refresh_value_labels() -> void:
_paint_dock.text = (
"paint dock\n" + ("right" if settings.paint_dock_right else "left")
)
_fullscreen_toggle.text = (
"fullscreen\n" + ("on" if settings.fullscreen_enabled else "off")
)
_mouse_value.text = "mouse\nsensitivity\n%.4f" % _mouse_sensitivity
_controller_value.text = (
"controller\nsensitivity\n%.1f" % _controller_sensitivity
@ -771,6 +822,7 @@ func _refresh_value_labels() -> void:
_auto_click_interval.text = (
"auto-click\ninterval\n%.2f s" % _auto_click_interval_value
)
_refresh_audio_value_labels()
for index: int in _world_options.size():
_world_options[index].button_pressed = (
index + 1 == settings.world_pixel_size
@ -803,6 +855,49 @@ func _style_data_page() -> void:
)
func _style_sound_page() -> void:
UtilityPageStyle.apply_page(_sound_page)
var paper := _sound_page.get_node("Paper") as PanelContainer
paper.add_theme_stylebox_override(
"panel", UtilityPageStyle.panel_style()
)
var content := _sound_page.get_node("Paper/Content") as VBoxContainer
for node: Node in content.find_children("*", "Label", true, false):
(node as Label).add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
)
func _on_audio_volume_changed(value: float, channel: StringName) -> void:
var linear_volume: float = clampf(value / 100.0, 0.0, 1.0)
match channel:
&"master":
_master_volume = linear_volume
&"music":
_music_volume = linear_volume
&"effects":
_effects_volume = linear_volume
&"environment":
_environment_volume = linear_volume
_refresh_audio_value_labels()
if _settings_manager != null:
_settings_manager.preview_audio_levels(
_master_volume,
_music_volume,
_effects_volume,
_environment_volume,
)
func _refresh_audio_value_labels() -> void:
_master_volume_value.text = "%d%%" % roundi(_master_volume * 100.0)
_music_volume_value.text = "%d%%" % roundi(_music_volume * 100.0)
_effects_volume_value.text = "%d%%" % roundi(_effects_volume * 100.0)
_environment_volume_value.text = (
"%d%%" % roundi(_environment_volume * 100.0)
)
func _set_world_pixelation(pixel_size: int) -> void:
if _settings_manager == null:
return
@ -849,6 +944,17 @@ func _toggle_chat_mode() -> void:
_refresh_value_labels()
func _toggle_fullscreen() -> void:
if _settings_manager == null:
return
var edited: PlayerSettings = _settings_manager.current_settings.copy()
edited.fullscreen_enabled = not edited.fullscreen_enabled
if not _settings_manager.apply_settings(edited):
_feedback.text = "failed to save fullscreen setting."
return
_refresh_value_labels()
func _toggle_paint_dock() -> void:
if _settings_manager == null:
return

View file

@ -41,8 +41,8 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("3_page")
page_id = &"root"
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/DisplayCategory"), NodePath("BubbleCluster/ControlsCategory"), NodePath("BubbleCluster/AccessibilityCategory"), NodePath("BubbleCluster/DataCategory"), NodePath("BubbleCluster/ApplySettingsButton"), NodePath("BubbleCluster/RootBackButton")])
focus_paths = Array[NodePath]([NodePath("BubbleCluster/DisplayCategory"), NodePath("BubbleCluster/ControlsCategory"), NodePath("BubbleCluster/AccessibilityCategory"), NodePath("BubbleCluster/DataCategory"), NodePath("BubbleCluster/ApplySettingsButton"), NodePath("BubbleCluster/RootBackButton")])
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/DisplayCategory"), NodePath("BubbleCluster/SoundCategory"), NodePath("BubbleCluster/ControlsCategory"), NodePath("BubbleCluster/AccessibilityCategory"), NodePath("BubbleCluster/DataCategory"), NodePath("BubbleCluster/ApplySettingsButton"), NodePath("BubbleCluster/RootBackButton")])
focus_paths = Array[NodePath]([NodePath("BubbleCluster/DisplayCategory"), NodePath("BubbleCluster/SoundCategory"), NodePath("BubbleCluster/ControlsCategory"), NodePath("BubbleCluster/AccessibilityCategory"), NodePath("BubbleCluster/DataCategory"), NodePath("BubbleCluster/ApplySettingsButton"), NodePath("BubbleCluster/RootBackButton")])
initial_focus_path = NodePath("BubbleCluster/DisplayCategory")
back_focus_path = NodePath("BubbleCluster/RootBackButton")
compact_maximum_layout_size = Vector2(544, 400)
@ -88,6 +88,18 @@ minimum_font_size = 17
maximum_font_size = 25
motion_phase = 1.45
[node name="SoundCategory" parent="RootPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "sound"
neutral_size = Vector2(144, 136)
desktop_anchor = Vector2(100, 180)
compact_anchor = Vector2(90, 150)
compact_minimum_size = Vector2(118, 112)
minimum_font_size = 17
maximum_font_size = 25
motion_phase = 2.0
[node name="AccessibilityCategory" parent="RootPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
@ -163,8 +175,8 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("3_page")
page_id = &"display"
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/WorldValue"), NodePath("BubbleCluster/WorldLegible"), NodePath("BubbleCluster/WorldCute"), NodePath("BubbleCluster/WorldRetro"), NodePath("BubbleCluster/WorldHardcore"), NodePath("BubbleCluster/WorldWtf"), NodePath("BubbleCluster/UIValue"), NodePath("BubbleCluster/UILegible"), NodePath("BubbleCluster/UICute"), NodePath("BubbleCluster/UIRetro"), NodePath("BubbleCluster/UIHardcore"), NodePath("BubbleCluster/UIWtf"), NodePath("BubbleCluster/ChatDock"), NodePath("BubbleCluster/ChatMode"), NodePath("BubbleCluster/PaintDock"), NodePath("BubbleCluster/DisplayBackButton")])
focus_paths = Array[NodePath]([NodePath("BubbleCluster/WorldLegible"), NodePath("BubbleCluster/WorldCute"), NodePath("BubbleCluster/WorldRetro"), NodePath("BubbleCluster/WorldHardcore"), NodePath("BubbleCluster/WorldWtf"), NodePath("BubbleCluster/UILegible"), NodePath("BubbleCluster/UICute"), NodePath("BubbleCluster/UIRetro"), NodePath("BubbleCluster/UIHardcore"), NodePath("BubbleCluster/UIWtf"), NodePath("BubbleCluster/ChatDock"), NodePath("BubbleCluster/ChatMode"), NodePath("BubbleCluster/PaintDock"), NodePath("BubbleCluster/DisplayBackButton")])
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/WorldValue"), NodePath("BubbleCluster/WorldLegible"), NodePath("BubbleCluster/WorldCute"), NodePath("BubbleCluster/WorldRetro"), NodePath("BubbleCluster/WorldHardcore"), NodePath("BubbleCluster/WorldWtf"), NodePath("BubbleCluster/UIValue"), NodePath("BubbleCluster/UILegible"), NodePath("BubbleCluster/UICute"), NodePath("BubbleCluster/UIRetro"), NodePath("BubbleCluster/UIHardcore"), NodePath("BubbleCluster/UIWtf"), NodePath("BubbleCluster/ChatDock"), NodePath("BubbleCluster/ChatMode"), NodePath("BubbleCluster/PaintDock"), NodePath("BubbleCluster/FullscreenToggle"), NodePath("BubbleCluster/DisplayBackButton")])
focus_paths = Array[NodePath]([NodePath("BubbleCluster/WorldLegible"), NodePath("BubbleCluster/WorldCute"), NodePath("BubbleCluster/WorldRetro"), NodePath("BubbleCluster/WorldHardcore"), NodePath("BubbleCluster/WorldWtf"), NodePath("BubbleCluster/UILegible"), NodePath("BubbleCluster/UICute"), NodePath("BubbleCluster/UIRetro"), NodePath("BubbleCluster/UIHardcore"), NodePath("BubbleCluster/UIWtf"), NodePath("BubbleCluster/ChatDock"), NodePath("BubbleCluster/ChatMode"), NodePath("BubbleCluster/PaintDock"), NodePath("BubbleCluster/FullscreenToggle"), NodePath("BubbleCluster/DisplayBackButton")])
initial_focus_path = NodePath("BubbleCluster/WorldRetro")
back_focus_path = NodePath("BubbleCluster/DisplayBackButton")
compact_maximum_layout_size = Vector2(544, 400)
@ -343,8 +355,8 @@ unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "chat dock\nleft"
neutral_size = Vector2(96, 96)
desktop_anchor = Vector2(105, 435)
compact_anchor = Vector2(76, 378)
desktop_anchor = Vector2(70, 435)
compact_anchor = Vector2(55, 378)
compact_minimum_size = Vector2(82, 82)
minimum_font_size = 12
maximum_font_size = 16
@ -355,8 +367,8 @@ unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "chat mode\ndesktop"
neutral_size = Vector2(96, 96)
desktop_anchor = Vector2(275, 435)
compact_anchor = Vector2(225, 378)
desktop_anchor = Vector2(215, 435)
compact_anchor = Vector2(180, 378)
compact_minimum_size = Vector2(82, 82)
minimum_font_size = 12
maximum_font_size = 16
@ -367,24 +379,215 @@ unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "paint dock\nright"
neutral_size = Vector2(96, 96)
desktop_anchor = Vector2(445, 435)
compact_anchor = Vector2(380, 378)
desktop_anchor = Vector2(360, 435)
compact_anchor = Vector2(305, 378)
compact_minimum_size = Vector2(82, 82)
minimum_font_size = 12
maximum_font_size = 16
motion_phase = 1.7
[node name="FullscreenToggle" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "fullscreen\noff"
neutral_size = Vector2(104, 96)
desktop_anchor = Vector2(505, 435)
compact_anchor = Vector2(430, 378)
compact_minimum_size = Vector2(88, 82)
minimum_font_size = 11
maximum_font_size = 15
motion_phase = 1.85
[node name="DisplayBackButton" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "back"
neutral_size = Vector2(96, 90)
desktop_anchor = Vector2(615, 435)
compact_anchor = Vector2(530, 378)
desktop_anchor = Vector2(650, 435)
compact_anchor = Vector2(555, 378)
compact_minimum_size = Vector2(82, 78)
minimum_font_size = 13
maximum_font_size = 16
motion_phase = 2.0
motion_phase = 2.15
[node name="SoundPage" type="Control" parent="."]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("3_page")
page_id = &"sound"
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/SoundBackButton")])
focus_paths = Array[NodePath]([NodePath("Paper/Content/VolumeGrid/MasterVolumeSlider"), NodePath("Paper/Content/VolumeGrid/MusicVolumeSlider"), NodePath("Paper/Content/VolumeGrid/EffectsVolumeSlider"), NodePath("Paper/Content/VolumeGrid/EnvironmentVolumeSlider"), NodePath("BubbleCluster/SoundBackButton")])
initial_focus_path = NodePath("Paper/Content/VolumeGrid/MasterVolumeSlider")
back_focus_path = NodePath("BubbleCluster/SoundBackButton")
compact_maximum_layout_size = Vector2(544, 400)
[node name="BubbleCluster" type="Control" parent="SoundPage"]
layout_mode = 0
offset_right = 720.0
offset_bottom = 520.0
script = ExtResource("5_cluster")
profile = ExtResource("6_profile")
desktop_reference_size = Vector2(720, 520)
compact_reference_size = Vector2(608, 448)
[node name="Paper" type="PanelContainer" parent="SoundPage"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -310.0
offset_top = -230.0
offset_right = 310.0
offset_bottom = 205.0
grow_horizontal = 2
grow_vertical = 2
z_index = 1
[node name="Content" type="VBoxContainer" parent="SoundPage/Paper"]
layout_mode = 2
theme_override_constants/separation = 18
[node name="Heading" type="Label" parent="SoundPage/Paper/Content"]
layout_mode = 2
theme_override_font_sizes/font_size = 28
text = "sound"
horizontal_alignment = 1
[node name="Helper" type="Label" parent="SoundPage/Paper/Content"]
layout_mode = 2
text = "Adjust the mix. Changes are saved when you apply settings."
horizontal_alignment = 1
[node name="VolumeGrid" type="GridContainer" parent="SoundPage/Paper/Content"]
layout_mode = 2
theme_override_constants/h_separation = 16
theme_override_constants/v_separation = 18
columns = 3
[node name="MasterLabel" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
custom_minimum_size = Vector2(118, 42)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "master"
vertical_alignment = 1
[node name="MasterVolumeSlider" type="HSlider" parent="SoundPage/Paper/Content/VolumeGrid"]
unique_name_in_owner = true
custom_minimum_size = Vector2(350, 42)
layout_mode = 2
focus_mode = 2
tooltip_text = "master volume"
max_value = 100.0
step = 1.0
value = 100.0
[node name="MasterVolumeValue" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
unique_name_in_owner = true
custom_minimum_size = Vector2(70, 42)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "100%"
horizontal_alignment = 2
vertical_alignment = 1
[node name="MusicLabel" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
custom_minimum_size = Vector2(118, 42)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "music"
vertical_alignment = 1
[node name="MusicVolumeSlider" type="HSlider" parent="SoundPage/Paper/Content/VolumeGrid"]
unique_name_in_owner = true
custom_minimum_size = Vector2(350, 42)
layout_mode = 2
focus_mode = 2
tooltip_text = "music volume"
max_value = 100.0
step = 1.0
value = 100.0
[node name="MusicVolumeValue" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
unique_name_in_owner = true
custom_minimum_size = Vector2(70, 42)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "100%"
horizontal_alignment = 2
vertical_alignment = 1
[node name="EffectsLabel" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
custom_minimum_size = Vector2(118, 42)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "effects"
vertical_alignment = 1
[node name="EffectsVolumeSlider" type="HSlider" parent="SoundPage/Paper/Content/VolumeGrid"]
unique_name_in_owner = true
custom_minimum_size = Vector2(350, 42)
layout_mode = 2
focus_mode = 2
tooltip_text = "effects volume"
max_value = 100.0
step = 1.0
value = 100.0
[node name="EffectsVolumeValue" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
unique_name_in_owner = true
custom_minimum_size = Vector2(70, 42)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "100%"
horizontal_alignment = 2
vertical_alignment = 1
[node name="EnvironmentLabel" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
custom_minimum_size = Vector2(118, 42)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "environment"
vertical_alignment = 1
[node name="EnvironmentVolumeSlider" type="HSlider" parent="SoundPage/Paper/Content/VolumeGrid"]
unique_name_in_owner = true
custom_minimum_size = Vector2(350, 42)
layout_mode = 2
focus_mode = 2
tooltip_text = "environment volume"
max_value = 100.0
step = 1.0
value = 100.0
[node name="EnvironmentVolumeValue" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
unique_name_in_owner = true
custom_minimum_size = Vector2(70, 42)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "100%"
horizontal_alignment = 2
vertical_alignment = 1
[node name="SoundBackButton" parent="SoundPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
z_index = 4
layout_mode = 0
text = "back"
neutral_size = Vector2(96, 90)
desktop_anchor = Vector2(60, 460)
compact_anchor = Vector2(55, 410)
compact_minimum_size = Vector2(82, 78)
minimum_font_size = 13
maximum_font_size = 16
motion_phase = 5.0
[node name="ControlsPage" type="Control" parent="."]
unique_name_in_owner = true