Integrate per-save identity and interaction UI
This commit is contained in:
parent
ac82a28f3b
commit
4fda6e97f6
58 changed files with 3471 additions and 461 deletions
324
ui/chat_ui.gd
324
ui/chat_ui.gd
|
|
@ -25,9 +25,21 @@ const SPEECH_BUBBLE_WIDTH: float = 320.0
|
|||
const SPEECH_POINTER_HALF_WIDTH: float = 12.0
|
||||
const SPEECH_POINTER_HEIGHT: float = 14.0
|
||||
const SPEECH_POINTER_OVERLAP: float = 3.0
|
||||
const NAMEPLATE_WIDTH: float = 300.0
|
||||
const NAMEPLATE_NAME_HEIGHT: float = 24.0
|
||||
const NAMEPLATE_TITLE_TOP: float = 21.0
|
||||
const NAMEPLATE_TITLE_HEIGHT: float = 18.0
|
||||
const NAMEPLATE_HEIGHT: float = (
|
||||
NAMEPLATE_TITLE_TOP + NAMEPLATE_TITLE_HEIGHT
|
||||
)
|
||||
const NAMEPLATE_NAME_FONT_SIZE: int = 20
|
||||
const NAMEPLATE_TITLE_FONT_SIZE: int = 14
|
||||
const NAMEPLATE_HEAD_GAP: float = 3.0
|
||||
const NAMEPLATE_SPEECH_GAP: float = 5.0
|
||||
const ANIMALESE_FULL_VOLUME_DISTANCE: float = 4.0
|
||||
const ANIMALESE_SILENT_DISTANCE: float = 24.0
|
||||
const ANIMALESE_SILENT_VOLUME_DB: float = -80.0
|
||||
const ROLEPLAY_FONT_SLANT: float = -0.18
|
||||
const MAX_EDITOR_GIVE_BALANCE: int = 1_000_000_000_000
|
||||
const MOBILE_COMPACT_WIDTH: float = 620.0
|
||||
const MOBILE_EXPANDED_WIDTH: float = 820.0
|
||||
|
|
@ -107,6 +119,7 @@ var _player: Player
|
|||
var _fishing_spot: FishingSpot
|
||||
var _settings: PlayerSettingsManager
|
||||
var _history: RichTextLabel
|
||||
var _roleplay_font: FontVariation
|
||||
var _entry: LineEdit
|
||||
var _status: Label
|
||||
var _panel: PanelContainer
|
||||
|
|
@ -114,6 +127,7 @@ var _collapse_button: Button
|
|||
var _height_button: Button
|
||||
var _unread_indicator: Label
|
||||
var _hint: Label
|
||||
var _nameplate_layer: Control
|
||||
var _speech_layer: Control
|
||||
var _animalese_voice: AnimaleseVoiceType
|
||||
var _clock_panel: PanelContainer
|
||||
|
|
@ -125,6 +139,7 @@ var _calendar_balance: CurrencyAmount
|
|||
var _weather_icon: WeatherIconType
|
||||
var _status_effect_column: VBoxContainer
|
||||
var _status_effect_icons: Dictionary[StringName, TextureRect] = {}
|
||||
var _nameplates: Dictionary[int, Dictionary] = {}
|
||||
var _speech: Dictionary[int, Dictionary] = {}
|
||||
var _draft_save_timer: Timer
|
||||
var _opacity_tween: Tween
|
||||
|
|
@ -132,6 +147,7 @@ var _height_tween: Tween
|
|||
var _opened: bool = false
|
||||
var _available: bool = false
|
||||
var _hud_hidden: bool = false
|
||||
var _nameplates_hud_hidden: bool = false
|
||||
var _world_speech_visible: bool = true
|
||||
var _presentation_state := PresentationState.COMPACT
|
||||
var _visible_state_before_collapse := PresentationState.COMPACT
|
||||
|
|
@ -247,7 +263,12 @@ func setup(
|
|||
_service.local_message_confirmed.connect(_on_local_message_confirmed)
|
||||
_service.history_replaced.connect(_on_history)
|
||||
_service.send_rejected.connect(_on_rejected)
|
||||
_session.peer_removed.connect(_on_peer_removed)
|
||||
_session.peer_authenticated.connect(_on_peer_authenticated)
|
||||
_session.peer_display_name_changed.connect(_on_peer_display_name_changed)
|
||||
_session.peer_title_changed.connect(_on_peer_title_changed)
|
||||
_session.peer_removed.connect(_on_session_peer_removed)
|
||||
_spawn.avatar_spawned.connect(_on_avatar_spawned)
|
||||
_spawn.avatar_removed.connect(_on_avatar_removed)
|
||||
_entry.text = _settings.current_settings.chat_draft
|
||||
_entry.caret_column = _entry.text.length()
|
||||
_set_presentation_state(
|
||||
|
|
@ -258,6 +279,7 @@ func setup(
|
|||
false,
|
||||
)
|
||||
_refresh_history()
|
||||
_reconcile_nameplates()
|
||||
|
||||
|
||||
func _rebuild_status_effect_icons() -> void:
|
||||
|
|
@ -370,6 +392,7 @@ func open_command_chat() -> void:
|
|||
|
||||
func set_available(value: bool) -> void:
|
||||
_available = value
|
||||
_refresh_nameplate_layer_visibility()
|
||||
if not value:
|
||||
_clear_suspended_chat_state()
|
||||
_send_pending = false
|
||||
|
|
@ -601,6 +624,7 @@ func _process(_delta: float) -> void:
|
|||
_refresh_visibility()
|
||||
_update_status_effect_icons()
|
||||
_update_panel_opacity()
|
||||
_update_nameplates()
|
||||
_update_speech()
|
||||
|
||||
|
||||
|
|
@ -612,6 +636,11 @@ func _exit_tree() -> void:
|
|||
|
||||
|
||||
func _build_ui() -> void:
|
||||
_roleplay_font = FontVariation.new()
|
||||
_roleplay_font.base_font = UtilityPageStyle.TuffyFont
|
||||
var roleplay_transform := Transform2D.IDENTITY
|
||||
roleplay_transform.y.x = ROLEPLAY_FONT_SLANT
|
||||
_roleplay_font.variation_transform = roleplay_transform
|
||||
_clock_panel = PanelContainer.new()
|
||||
_clock_panel.name = "WorldClockPanel"
|
||||
_clock_panel.size = CLOCK_SIZE
|
||||
|
|
@ -720,7 +749,9 @@ func _build_ui() -> void:
|
|||
_history.scroll_following = true
|
||||
_history.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM
|
||||
_history.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_history.bbcode_enabled = false
|
||||
_history.add_theme_font_override("normal_font", UtilityPageStyle.TuffyFont)
|
||||
_history.add_theme_font_override("italics_font", _roleplay_font)
|
||||
_history.add_theme_font_size_override("normal_font_size", HISTORY_FONT_SIZE)
|
||||
_history.add_theme_color_override("default_color", UtilityPageStyle.LIGHT_TEXT)
|
||||
_history.add_theme_stylebox_override("normal", _borderless_style(Color.TRANSPARENT))
|
||||
|
|
@ -846,7 +877,16 @@ func _build_ui() -> void:
|
|||
_hint.add_theme_constant_override("shadow_offset_x", 1)
|
||||
_hint.add_theme_constant_override("shadow_offset_y", 2)
|
||||
add_child(_hint)
|
||||
_nameplate_layer = Control.new()
|
||||
_nameplate_layer.name = "PlayerNameplateLayer"
|
||||
_nameplate_layer.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_nameplate_layer.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_nameplate_layer.visible = (
|
||||
_available and _world_speech_visible and not _nameplates_hud_hidden
|
||||
)
|
||||
add_child(_nameplate_layer)
|
||||
_speech_layer = Control.new()
|
||||
_speech_layer.name = "PlayerSpeechLayer"
|
||||
_speech_layer.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_speech_layer.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_speech_layer)
|
||||
|
|
@ -1193,10 +1233,15 @@ func _on_message(message: Dictionary) -> void:
|
|||
_update_panel_opacity(true)
|
||||
if int(message["kind"]) != NetworkChatProtocol.Kind.PLAYER:
|
||||
return
|
||||
var body: String = str(message["body"])
|
||||
# Roleplay actions belong only in chat history. They deliberately do not
|
||||
# create world speech, animalese, or a talking animation.
|
||||
if NetworkChatProtocol.is_roleplay_body(body):
|
||||
return
|
||||
var peer_id: int = message["sender_peer_id"]
|
||||
_show_speech_bubble(
|
||||
peer_id,
|
||||
str(message["body"]),
|
||||
body,
|
||||
str(message.get("sender_fingerprint", "")),
|
||||
VoiceProfilesType.sanitized_id(str(message.get(
|
||||
"voice_id", VoiceProfilesType.DEFAULT_ID
|
||||
|
|
@ -1221,6 +1266,8 @@ func _show_speech_bubble(
|
|||
requested_sample_set_id: String = "",
|
||||
) -> void:
|
||||
_on_peer_removed(peer_id)
|
||||
if not _speaker_shares_local_space(peer_id):
|
||||
return
|
||||
var bubble := PanelContainer.new()
|
||||
bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
bubble.custom_minimum_size = Vector2(SPEECH_BUBBLE_WIDTH, 0.0)
|
||||
|
|
@ -1342,16 +1389,27 @@ func _on_rejected(message: String) -> void:
|
|||
func _refresh_history() -> void:
|
||||
if _service == null:
|
||||
return
|
||||
var lines: Array[String] = []
|
||||
var messages := _service.get_history()
|
||||
for message: Dictionary in messages:
|
||||
_history.clear()
|
||||
for message_index: int in messages.size():
|
||||
var message: Dictionary = messages[message_index]
|
||||
if message_index > 0:
|
||||
_history.add_text("\n")
|
||||
if int(message["kind"]) == NetworkChatProtocol.Kind.SYSTEM:
|
||||
lines.append("• %s" % str(message["body"]))
|
||||
else:
|
||||
lines.append("%s: %s" % [
|
||||
str(message["sender_display_name"]), str(message["body"]),
|
||||
_history.add_text("• %s" % str(message["body"]))
|
||||
continue
|
||||
var body: String = str(message["body"])
|
||||
if NetworkChatProtocol.is_roleplay_body(body):
|
||||
_history.push_italics()
|
||||
_history.add_text("%s %s" % [
|
||||
str(message["sender_display_name"]),
|
||||
NetworkChatProtocol.roleplay_action(body),
|
||||
])
|
||||
_history.text = "\n".join(lines)
|
||||
_history.pop()
|
||||
continue
|
||||
_history.add_text("%s: %s" % [
|
||||
str(message["sender_display_name"]), body,
|
||||
])
|
||||
_scroll_history_to_bottom.call_deferred()
|
||||
|
||||
|
||||
|
|
@ -1465,6 +1523,8 @@ func is_mobile_mode() -> bool:
|
|||
|
||||
func set_hud_hidden(is_hidden: bool) -> void:
|
||||
_hud_hidden = is_hidden
|
||||
_nameplates_hud_hidden = is_hidden
|
||||
_refresh_nameplate_layer_visibility()
|
||||
_refresh_visibility()
|
||||
|
||||
|
||||
|
|
@ -1472,8 +1532,14 @@ func is_hud_hidden() -> bool:
|
|||
return _hud_hidden
|
||||
|
||||
|
||||
func set_world_nameplates_hud_hidden(is_hidden: bool) -> void:
|
||||
_nameplates_hud_hidden = is_hidden
|
||||
_refresh_nameplate_layer_visibility()
|
||||
|
||||
|
||||
func set_world_speech_visible(should_be_visible: bool) -> void:
|
||||
_world_speech_visible = should_be_visible
|
||||
_refresh_nameplate_layer_visibility()
|
||||
if _speech_layer != null:
|
||||
_speech_layer.visible = should_be_visible
|
||||
|
||||
|
|
@ -1482,6 +1548,14 @@ func is_world_speech_visible() -> bool:
|
|||
return _world_speech_visible
|
||||
|
||||
|
||||
func _refresh_nameplate_layer_visibility() -> void:
|
||||
if _nameplate_layer == null:
|
||||
return
|
||||
_nameplate_layer.visible = (
|
||||
_available and _world_speech_visible and not _nameplates_hud_hidden
|
||||
)
|
||||
|
||||
|
||||
func _refresh_handle_labels(state: PresentationState) -> void:
|
||||
var collapsed := state == PresentationState.COLLAPSED
|
||||
var height_state := _visible_state_before_collapse if collapsed else state
|
||||
|
|
@ -1538,11 +1612,217 @@ func _update_panel_opacity(immediate_recheck: bool = false) -> void:
|
|||
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||
|
||||
|
||||
func _update_nameplates() -> void:
|
||||
_reconcile_nameplates()
|
||||
if _spawn == null or _player == null:
|
||||
_hide_all_nameplates()
|
||||
return
|
||||
var camera := _player.get_active_gameplay_camera()
|
||||
if camera == null:
|
||||
_hide_all_nameplates()
|
||||
return
|
||||
var viewport_size := get_viewport_rect().size
|
||||
for peer_id: int in _nameplates.keys():
|
||||
var state: Dictionary = _nameplates[peer_id]
|
||||
var plate := state.get("control") as Control
|
||||
var avatar := _spawn.get_avatar(peer_id)
|
||||
if plate == null:
|
||||
continue
|
||||
if (
|
||||
avatar == null
|
||||
or not _available
|
||||
or _nameplates_hud_hidden
|
||||
or not _world_speech_visible
|
||||
or not _speaker_shares_local_space(peer_id)
|
||||
or not _avatar_presentation_visible(peer_id, avatar)
|
||||
):
|
||||
plate.hide()
|
||||
continue
|
||||
var world_position := avatar.get_nameplate_anchor_position()
|
||||
if (
|
||||
camera.is_position_behind(world_position)
|
||||
or _is_speech_world_occluded(camera, avatar, world_position)
|
||||
):
|
||||
plate.hide()
|
||||
continue
|
||||
var screen_position := (
|
||||
camera.unproject_position(world_position) / _output_scale
|
||||
)
|
||||
if (
|
||||
screen_position.x < -NAMEPLATE_WIDTH * 0.5
|
||||
or screen_position.x > viewport_size.x + NAMEPLATE_WIDTH * 0.5
|
||||
or screen_position.y < -NAMEPLATE_HEIGHT
|
||||
or screen_position.y > viewport_size.y + NAMEPLATE_HEIGHT
|
||||
):
|
||||
plate.hide()
|
||||
continue
|
||||
plate.position = Vector2(
|
||||
screen_position.x - NAMEPLATE_WIDTH * 0.5,
|
||||
screen_position.y - NAMEPLATE_HEIGHT - NAMEPLATE_HEAD_GAP,
|
||||
)
|
||||
plate.show()
|
||||
|
||||
|
||||
func _reconcile_nameplates() -> void:
|
||||
if _spawn == null or _session == null or _nameplate_layer == null:
|
||||
return
|
||||
var live_peer_ids: Dictionary[int, bool] = {}
|
||||
for peer_id: int in _spawn.get_peer_ids():
|
||||
if (
|
||||
_spawn.get_avatar(peer_id) == null
|
||||
or _session.get_peer_record(peer_id) == null
|
||||
):
|
||||
continue
|
||||
live_peer_ids[peer_id] = true
|
||||
_ensure_nameplate(peer_id)
|
||||
for peer_id: int in _nameplates.keys():
|
||||
if not live_peer_ids.has(peer_id):
|
||||
_remove_nameplate(peer_id)
|
||||
|
||||
|
||||
func _ensure_nameplate(peer_id: int) -> void:
|
||||
if _nameplates.has(peer_id):
|
||||
_refresh_nameplate_text(peer_id)
|
||||
return
|
||||
var plate := Control.new()
|
||||
plate.name = "PlayerNameplate_%d" % peer_id
|
||||
plate.size = Vector2(NAMEPLATE_WIDTH, NAMEPLATE_HEIGHT)
|
||||
plate.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
plate.hide()
|
||||
var name_label := _create_nameplate_label(
|
||||
"PlayerName",
|
||||
NAMEPLATE_NAME_FONT_SIZE,
|
||||
0.0,
|
||||
NAMEPLATE_NAME_HEIGHT,
|
||||
)
|
||||
var title_label := _create_nameplate_label(
|
||||
"PlayerTitle",
|
||||
NAMEPLATE_TITLE_FONT_SIZE,
|
||||
NAMEPLATE_TITLE_TOP,
|
||||
NAMEPLATE_TITLE_HEIGHT,
|
||||
)
|
||||
plate.add_child(name_label)
|
||||
plate.add_child(title_label)
|
||||
_nameplate_layer.add_child(plate)
|
||||
_nameplates[peer_id] = {
|
||||
"control": plate,
|
||||
"name": name_label,
|
||||
"title": title_label,
|
||||
}
|
||||
_refresh_nameplate_text(peer_id)
|
||||
|
||||
|
||||
func _create_nameplate_label(
|
||||
node_name: String,
|
||||
font_size: int,
|
||||
vertical_position: float,
|
||||
height: float,
|
||||
) -> Label:
|
||||
var label := Label.new()
|
||||
label.name = node_name
|
||||
label.position = Vector2(0.0, vertical_position)
|
||||
label.size = Vector2(NAMEPLATE_WIDTH, height)
|
||||
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
label.clip_text = true
|
||||
label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
label.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
|
||||
label.add_theme_font_size_override("font_size", font_size)
|
||||
label.add_theme_color_override("font_color", Color.WHITE)
|
||||
label.add_theme_color_override("font_outline_color", Color.TRANSPARENT)
|
||||
label.add_theme_color_override("font_shadow_color", Color.TRANSPARENT)
|
||||
label.add_theme_constant_override("outline_size", 0)
|
||||
label.add_theme_constant_override("shadow_offset_x", 0)
|
||||
label.add_theme_constant_override("shadow_offset_y", 0)
|
||||
return label
|
||||
|
||||
|
||||
func _refresh_nameplate_text(peer_id: int) -> void:
|
||||
if _session == null or not _nameplates.has(peer_id):
|
||||
return
|
||||
var record := _session.get_peer_record(peer_id)
|
||||
if record == null:
|
||||
return
|
||||
var state: Dictionary = _nameplates[peer_id]
|
||||
var name_label := state.get("name") as Label
|
||||
var title_label := state.get("title") as Label
|
||||
if name_label != null:
|
||||
name_label.text = (
|
||||
record.display_name
|
||||
if not record.display_name.is_empty()
|
||||
else NetworkProfilePreferences.DEFAULT_DISPLAY_NAME
|
||||
)
|
||||
if title_label != null:
|
||||
title_label.text = (
|
||||
record.title
|
||||
if not record.title.is_empty()
|
||||
else NetworkProfilePreferences.DEFAULT_TITLE
|
||||
)
|
||||
|
||||
|
||||
func _remove_nameplate(peer_id: int) -> void:
|
||||
var state: Dictionary = _nameplates.get(peer_id, {})
|
||||
var plate := state.get("control") as Control
|
||||
if plate != null:
|
||||
plate.queue_free()
|
||||
_nameplates.erase(peer_id)
|
||||
|
||||
|
||||
func _hide_all_nameplates() -> void:
|
||||
for state: Dictionary in _nameplates.values():
|
||||
var plate := state.get("control") as Control
|
||||
if plate != null:
|
||||
plate.hide()
|
||||
|
||||
|
||||
func _avatar_presentation_visible(peer_id: int, avatar: Player) -> bool:
|
||||
if _session == null or peer_id == _session.get_local_peer_id():
|
||||
return true
|
||||
return avatar.is_remote_presentation_visible()
|
||||
|
||||
|
||||
func _nameplate_speech_clearance(peer_id: int) -> float:
|
||||
var state: Dictionary = _nameplates.get(peer_id, {})
|
||||
var plate := state.get("control") as Control
|
||||
if plate == null or not plate.is_visible_in_tree():
|
||||
return 0.0
|
||||
return NAMEPLATE_HEIGHT + NAMEPLATE_HEAD_GAP + NAMEPLATE_SPEECH_GAP
|
||||
|
||||
|
||||
func _on_peer_authenticated(peer_id: int, _display_name: String) -> void:
|
||||
_ensure_nameplate(peer_id)
|
||||
|
||||
|
||||
func _on_peer_display_name_changed(peer_id: int, _display_name: String) -> void:
|
||||
_ensure_nameplate(peer_id)
|
||||
|
||||
|
||||
func _on_peer_title_changed(peer_id: int, _title: String) -> void:
|
||||
_ensure_nameplate(peer_id)
|
||||
|
||||
|
||||
func _on_avatar_spawned(peer_id: int, _avatar: Player) -> void:
|
||||
_ensure_nameplate(peer_id)
|
||||
|
||||
|
||||
func _on_avatar_removed(peer_id: int) -> void:
|
||||
_remove_nameplate(peer_id)
|
||||
|
||||
|
||||
func _on_session_peer_removed(peer_id: int) -> void:
|
||||
_on_peer_removed(peer_id)
|
||||
_remove_nameplate(peer_id)
|
||||
|
||||
|
||||
func _update_speech() -> void:
|
||||
var now := Time.get_ticks_msec() / 1000.0
|
||||
for peer_id: int in _speech.keys():
|
||||
var state: Dictionary = _speech[peer_id]
|
||||
var bubble := state.get("bubble") as PanelContainer
|
||||
if not _speaker_shares_local_space(peer_id):
|
||||
_on_peer_removed(peer_id)
|
||||
continue
|
||||
if bubble == null or now >= float(state.get("expires", 0.0)):
|
||||
if bubble != null:
|
||||
bubble.queue_free()
|
||||
|
|
@ -1553,10 +1833,14 @@ func _update_speech() -> void:
|
|||
continue
|
||||
var avatar := _spawn.get_avatar(peer_id)
|
||||
var camera := _player.get_active_gameplay_camera()
|
||||
if avatar == null or camera == null:
|
||||
if (
|
||||
avatar == null
|
||||
or camera == null
|
||||
or not _avatar_presentation_visible(peer_id, avatar)
|
||||
):
|
||||
bubble.hide()
|
||||
continue
|
||||
var world_position := avatar.get_chat_anchor_position()
|
||||
var world_position := avatar.get_nameplate_anchor_position()
|
||||
if camera.is_position_behind(world_position):
|
||||
bubble.hide()
|
||||
continue
|
||||
|
|
@ -1566,6 +1850,7 @@ func _update_speech() -> void:
|
|||
var screen_position := (
|
||||
camera.unproject_position(world_position) / _output_scale
|
||||
)
|
||||
screen_position.y -= _nameplate_speech_clearance(peer_id)
|
||||
var viewport_size := get_viewport_rect().size
|
||||
if (
|
||||
screen_position.x < -80.0
|
||||
|
|
@ -1631,10 +1916,27 @@ func _is_speech_world_occluded(
|
|||
if collider is Player:
|
||||
excluded.append(collider.get_rid())
|
||||
continue
|
||||
if (
|
||||
collider != null
|
||||
and bool(collider.get_meta(
|
||||
HomeWorldService.INTERIOR_SHELL_COLLISION_META, false
|
||||
))
|
||||
):
|
||||
excluded.append(collider.get_rid())
|
||||
continue
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _speaker_shares_local_space(peer_id: int) -> bool:
|
||||
if _session == null:
|
||||
return true
|
||||
var local_peer_id: int = _session.get_local_peer_id()
|
||||
if local_peer_id <= 0:
|
||||
return true
|
||||
return _session.peers_share_space(local_peer_id, peer_id)
|
||||
|
||||
|
||||
func _on_draft_changed(_value: String) -> void:
|
||||
if _settings == null:
|
||||
return
|
||||
|
|
|
|||
163
ui/game_ui.gd
163
ui/game_ui.gd
|
|
@ -88,8 +88,6 @@ signal pixelation_settings_visibility_changed(is_visible: bool)
|
|||
signal crisp_reset_focus_requested
|
||||
signal interactive_pointer_ui_changed(is_open: bool)
|
||||
signal passive_pointer_ui_changed(is_enabled: bool)
|
||||
signal player_menu_backdrop_visibility_changed(is_visible: bool)
|
||||
signal shop_backdrop_visibility_changed(is_visible: bool)
|
||||
signal virtual_pointer_mode_changed(is_active: bool)
|
||||
signal social_prompt_accepted
|
||||
signal social_prompt_declined
|
||||
|
|
@ -119,6 +117,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 _gameplay_screen_effects: Control = %GameplayScreenEffects
|
||||
@onready var _active_bait_indicator: Control = %ActiveBaitIndicator
|
||||
@onready var _active_bait_button: Button = %ActiveBaitButton
|
||||
@onready var _active_bait_quantity_badge: Panel = %ActiveBaitQuantityBadge
|
||||
|
|
@ -148,7 +147,8 @@ const VoiceProfilesType = preload(
|
|||
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
|
||||
const SHOP_PROMPT_DIALOGUE_SIZE := Vector2(300.0, 72.0)
|
||||
const SHOP_PROMPT_COMPACT_SIZE := Vector2(30.0, 30.0)
|
||||
const FISHING_DANGER_START_GAP: float = 0.15
|
||||
const FISHING_DANGER_FULL_GAP: float = 0.025
|
||||
|
||||
|
|
@ -175,9 +175,11 @@ const FISHING_DANGER_FULL_GAP: float = 0.025
|
|||
@onready var _shop_prompt_key: Label = %ShopPromptKey
|
||||
@onready var _shop_prompt_pointer: Polygon2D = %ShopPromptPointer
|
||||
var _shop_animalese_voice: AnimaleseVoiceType
|
||||
var _shop_speech_tween: Tween
|
||||
var _shop_npc_player_in_range: bool = false
|
||||
var _shop_npc_dialogue_active: bool = false
|
||||
var _shop_npc_spoken_for_current_visit: bool = false
|
||||
var _shop_npc_next_speech_msec: int = 0
|
||||
var _shop_npc_source_id: int = 0
|
||||
@onready var _chat_ui: ChatUIType = %ChatUI
|
||||
@onready var _emote_radial_menu: EmoteRadialMenuType = %EmoteRadialMenu
|
||||
@onready var _quick_radial_menu: QuickRadialMenuType = %QuickRadialMenu
|
||||
|
|
@ -559,11 +561,8 @@ func setup(
|
|||
_on_storage_visibility_changed
|
||||
)
|
||||
_fishing_shop.menu_visibility_changed.connect(_on_shop_visibility_changed)
|
||||
_fishing_shop.menu_exit_started.connect(_on_shop_exit_started)
|
||||
_decor_shop.menu_visibility_changed.connect(_on_shop_visibility_changed)
|
||||
_decor_shop.menu_exit_started.connect(_on_shop_exit_started)
|
||||
_rv_upgrade_shop.menu_visibility_changed.connect(_on_shop_visibility_changed)
|
||||
_rv_upgrade_shop.menu_exit_started.connect(_on_shop_exit_started)
|
||||
_fishing_shop.sell_fish_requested.connect(_on_shop_sell_fish_requested)
|
||||
_fishing_shop.shop_cooler_return_requested.connect(
|
||||
_on_shop_cooler_return_requested
|
||||
|
|
@ -700,6 +699,7 @@ func _input(event: InputEvent) -> void:
|
|||
can_open and not _quick_radial_menu.is_open(),
|
||||
):
|
||||
var emote_is_open: bool = _emote_radial_menu.is_open()
|
||||
_refresh_home_decor_toolbar_obscuring()
|
||||
if emote_is_open != emote_was_open and _player != null:
|
||||
_player.set_camera_input_suppressed(
|
||||
EMOTE_RADIAL_CAMERA_OWNER,
|
||||
|
|
@ -713,6 +713,7 @@ func _input(event: InputEvent) -> void:
|
|||
can_open and not _emote_radial_menu.is_open(),
|
||||
):
|
||||
var quick_is_open: bool = _quick_radial_menu.is_open()
|
||||
_refresh_home_decor_toolbar_obscuring()
|
||||
if quick_is_open != quick_was_open and _player != null:
|
||||
_player.set_camera_input_suppressed(
|
||||
QUICK_RADIAL_CAMERA_OWNER,
|
||||
|
|
@ -755,6 +756,7 @@ func _close_radial_menus() -> void:
|
|||
func _close_emote_radial_menu() -> void:
|
||||
if _emote_radial_menu != null and _emote_radial_menu.is_open():
|
||||
_emote_radial_menu.close_menu()
|
||||
_refresh_home_decor_toolbar_obscuring()
|
||||
if _player != null and is_instance_valid(_player):
|
||||
_player.set_camera_input_suppressed(
|
||||
EMOTE_RADIAL_CAMERA_OWNER,
|
||||
|
|
@ -765,6 +767,7 @@ func _close_emote_radial_menu() -> void:
|
|||
func _close_quick_radial_menu() -> void:
|
||||
if _quick_radial_menu != null and _quick_radial_menu.is_open():
|
||||
_quick_radial_menu.close_menu()
|
||||
_refresh_home_decor_toolbar_obscuring()
|
||||
if _player != null and is_instance_valid(_player):
|
||||
_player.set_camera_input_suppressed(
|
||||
QUICK_RADIAL_CAMERA_OWNER,
|
||||
|
|
@ -798,6 +801,14 @@ func _character_call_yields_to_world_interaction(
|
|||
_shop_interaction != null
|
||||
and _shop_interaction.is_local_player_in_range()
|
||||
)
|
||||
or (
|
||||
_decor_shop_interaction != null
|
||||
and _decor_shop_interaction.is_local_player_in_range()
|
||||
)
|
||||
or (
|
||||
_rv_upgrade_shop_interaction != null
|
||||
and _rv_upgrade_shop_interaction.is_local_player_in_range()
|
||||
)
|
||||
or (
|
||||
_storage_interaction != null
|
||||
and _storage_interaction.is_local_player_in_range()
|
||||
|
|
@ -806,6 +817,8 @@ func _character_call_yields_to_world_interaction(
|
|||
and _fishing_spot != null
|
||||
and _fishing_spot.can_open_fishing_shop()
|
||||
and not _fishing_shop.visible
|
||||
and not _decor_shop.visible
|
||||
and not _rv_upgrade_shop.visible
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -1544,12 +1557,19 @@ func _refresh_interaction_prompt_bindings() -> void:
|
|||
|
||||
|
||||
func _resize_interaction_prompts() -> void:
|
||||
var badge_width: float = maxf(
|
||||
24.0,
|
||||
ceilf(_shop_prompt_key.get_combined_minimum_size().x) + 12.0,
|
||||
)
|
||||
_shop_prompt_key_badge.position.x = _shop_prompt.size.x - badge_width + 6.0
|
||||
_shop_prompt_key_badge.size.x = badge_width
|
||||
if not _shop_npc_dialogue_active:
|
||||
_shop_prompt_key_badge.position = Vector2.ZERO
|
||||
_shop_prompt_key_badge.size = SHOP_PROMPT_COMPACT_SIZE
|
||||
else:
|
||||
var badge_width: float = maxf(
|
||||
24.0,
|
||||
ceilf(_shop_prompt_key.get_combined_minimum_size().x) + 12.0,
|
||||
)
|
||||
_shop_prompt_key_badge.position = Vector2(
|
||||
_shop_prompt.size.x - badge_width + 6.0,
|
||||
-8.0,
|
||||
)
|
||||
_shop_prompt_key_badge.size = Vector2(badge_width, 24.0)
|
||||
var storage_width: float = maxf(
|
||||
220.0,
|
||||
ceilf(_storage_prompt_message.get_combined_minimum_size().x) + 24.0,
|
||||
|
|
@ -1925,6 +1945,7 @@ func _refresh_gameplay_hud_visibility() -> void:
|
|||
and not _storage_open
|
||||
)
|
||||
_gameplay_transient_hud.visible = show_world_hud
|
||||
_gameplay_screen_effects.visible = show_world_hud
|
||||
_experience_presentation.visible = (
|
||||
_gameplay_ui_enabled and not _gameplay_hud_hidden
|
||||
)
|
||||
|
|
@ -2110,19 +2131,39 @@ func set_shop_prompt_visible(
|
|||
func set_shop_npc_player_in_range(
|
||||
in_range: bool,
|
||||
dialogue_text: String = "",
|
||||
source_id: int = 0,
|
||||
dialogue_active: bool = false,
|
||||
) -> void:
|
||||
var source_changed: bool = source_id != _shop_npc_source_id
|
||||
var next_dialogue_active: bool = in_range and dialogue_active
|
||||
var dialogue_stage_changed: bool = (
|
||||
next_dialogue_active != _shop_npc_dialogue_active
|
||||
)
|
||||
var dialogue_changed: bool = (
|
||||
not dialogue_text.is_empty()
|
||||
and _shop_prompt_message.text != dialogue_text
|
||||
)
|
||||
if source_changed or dialogue_changed or dialogue_stage_changed:
|
||||
_cancel_shop_npc_speech()
|
||||
_shop_npc_source_id = source_id
|
||||
if dialogue_changed:
|
||||
_shop_prompt_message.text = dialogue_text
|
||||
_shop_prompt_message.visible_characters = -1
|
||||
_shop_npc_spoken_for_current_visit = false
|
||||
if _shop_npc_player_in_range == in_range and not dialogue_changed:
|
||||
if source_changed or dialogue_stage_changed:
|
||||
_shop_npc_spoken_for_current_visit = false
|
||||
if (
|
||||
_shop_npc_player_in_range == in_range
|
||||
and not dialogue_changed
|
||||
and not source_changed
|
||||
and not dialogue_stage_changed
|
||||
):
|
||||
return
|
||||
_shop_npc_player_in_range = in_range
|
||||
if not in_range:
|
||||
_shop_npc_dialogue_active = next_dialogue_active
|
||||
_apply_shop_prompt_mode()
|
||||
if not in_range or not _shop_npc_dialogue_active:
|
||||
_cancel_shop_npc_speech()
|
||||
_shop_npc_spoken_for_current_visit = false
|
||||
return
|
||||
_try_start_shop_npc_speech()
|
||||
|
|
@ -2131,31 +2172,33 @@ func set_shop_npc_player_in_range(
|
|||
func _try_start_shop_npc_speech() -> void:
|
||||
if (
|
||||
not _shop_npc_player_in_range
|
||||
or not _shop_npc_dialogue_active
|
||||
or _shop_npc_spoken_for_current_visit
|
||||
or not _gameplay_ui_enabled
|
||||
):
|
||||
return
|
||||
var now_msec: int = Time.get_ticks_msec()
|
||||
if now_msec < _shop_npc_next_speech_msec:
|
||||
return
|
||||
_shop_npc_spoken_for_current_visit = true
|
||||
_shop_npc_next_speech_msec = (
|
||||
now_msec + SHOP_NPC_SPEECH_COOLDOWN_MILLISECONDS
|
||||
)
|
||||
_ensure_shop_animalese_voice()
|
||||
TypewriterRevealType.start(
|
||||
_shop_prompt_message,
|
||||
SHOP_SPEECH_CHARACTERS_PER_SECOND,
|
||||
)
|
||||
_shop_animalese_voice.speak_text(
|
||||
_shop_speech_tween = _shop_animalese_voice.speak_text(
|
||||
_shop_prompt_message,
|
||||
_shop_prompt_message.text,
|
||||
"shopkeeper",
|
||||
"shopkeeper:%d" % _shop_npc_source_id,
|
||||
SHOP_ANIMALESE_VOICE_ID,
|
||||
SHOP_SPEECH_CHARACTERS_PER_SECOND,
|
||||
)
|
||||
|
||||
|
||||
func _cancel_shop_npc_speech() -> void:
|
||||
TypewriterRevealType.cancel(_shop_prompt_message)
|
||||
if _shop_speech_tween != null and _shop_speech_tween.is_valid():
|
||||
_shop_speech_tween.kill()
|
||||
_shop_speech_tween = null
|
||||
|
||||
|
||||
func _ensure_shop_animalese_voice() -> void:
|
||||
if _shop_animalese_voice != null:
|
||||
return
|
||||
|
|
@ -2191,6 +2234,29 @@ func _apply_shop_prompt_style() -> void:
|
|||
UtilityPageStyle.OCEAN_PANEL_MID,
|
||||
0.96,
|
||||
)
|
||||
_apply_shop_prompt_mode()
|
||||
|
||||
|
||||
func _apply_shop_prompt_mode() -> void:
|
||||
if not is_node_ready():
|
||||
return
|
||||
var dialogue_mode: bool = _shop_npc_dialogue_active
|
||||
var requested_size := (
|
||||
SHOP_PROMPT_DIALOGUE_SIZE
|
||||
if dialogue_mode
|
||||
else SHOP_PROMPT_COMPACT_SIZE
|
||||
)
|
||||
_shop_prompt.custom_minimum_size = requested_size
|
||||
_shop_prompt.size = requested_size
|
||||
_shop_prompt_bubble.visible = dialogue_mode
|
||||
_shop_prompt_message.visible = dialogue_mode
|
||||
_shop_prompt_pointer.visible = dialogue_mode
|
||||
var badge_style := (
|
||||
_shop_prompt_key_badge.get_theme_stylebox("panel") as StyleBoxFlat
|
||||
)
|
||||
if badge_style != null:
|
||||
badge_style.set_corner_radius_all(12 if dialogue_mode else 15)
|
||||
_resize_interaction_prompts()
|
||||
|
||||
|
||||
func _position_shop_prompt(world_anchor: Vector3) -> void:
|
||||
|
|
@ -2219,7 +2285,7 @@ func _position_shop_prompt(world_anchor: Vector3) -> void:
|
|||
):
|
||||
_shop_prompt.hide()
|
||||
return
|
||||
var pointer_height := 10.0
|
||||
var pointer_height := 10.0 if _shop_npc_dialogue_active else 6.0
|
||||
var desired := screen_position - Vector2(
|
||||
_shop_prompt.size.x * 0.5,
|
||||
_shop_prompt.size.y + pointer_height,
|
||||
|
|
@ -2236,14 +2302,15 @@ func _position_shop_prompt(world_anchor: Vector3) -> void:
|
|||
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,
|
||||
)
|
||||
if _shop_npc_dialogue_active:
|
||||
_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:
|
||||
|
|
@ -2831,7 +2898,6 @@ func _on_player_menu_visibility_changed(is_open: bool) -> void:
|
|||
true,
|
||||
UIMotion.PLAYER_MENU_ENTER_DURATION,
|
||||
)
|
||||
player_menu_backdrop_visibility_changed.emit(true)
|
||||
else:
|
||||
_player_menu_hotbar_visible = false
|
||||
_hotbar_ui.set_presentation_visible(false, false)
|
||||
|
|
@ -2862,13 +2928,6 @@ func _on_player_menu_exit_started() -> void:
|
|||
true,
|
||||
UIMotion.PLAYER_MENU_EXIT_DURATION,
|
||||
)
|
||||
player_menu_backdrop_visibility_changed.emit(false)
|
||||
|
||||
|
||||
func _on_shop_exit_started() -> void:
|
||||
if not _shop_open:
|
||||
return
|
||||
shop_backdrop_visibility_changed.emit(false)
|
||||
|
||||
|
||||
func _on_shop_visibility_changed(is_open: bool) -> void:
|
||||
|
|
@ -2876,8 +2935,6 @@ func _on_shop_visibility_changed(is_open: bool) -> void:
|
|||
if is_open:
|
||||
_close_radial_menus()
|
||||
_refresh_surface_drawing_activation()
|
||||
if is_open:
|
||||
shop_backdrop_visibility_changed.emit(true)
|
||||
if not is_open and _player_menu.is_shop_cooler_mounted():
|
||||
_player_menu.unmount_shop_cooler()
|
||||
_refresh_gameplay_hud_visibility()
|
||||
|
|
@ -3049,6 +3106,7 @@ func _refresh_hotbar_visibility() -> void:
|
|||
func _emit_interactive_pointer_ui_changed() -> void:
|
||||
_refresh_active_bait_indicator_visibility()
|
||||
_refresh_rv_storage_button_visibility()
|
||||
_refresh_home_decor_toolbar_obscuring()
|
||||
interactive_pointer_ui_changed.emit(
|
||||
_system_menu_open
|
||||
or _player_menu_open
|
||||
|
|
@ -3058,6 +3116,26 @@ func _emit_interactive_pointer_ui_changed() -> void:
|
|||
)
|
||||
|
||||
|
||||
func _refresh_home_decor_toolbar_obscuring() -> void:
|
||||
if _home_decor_toolbar == null:
|
||||
return
|
||||
_home_decor_toolbar.set_menu_obscured(
|
||||
_system_menu_open
|
||||
or _player_menu_open
|
||||
or _shop_open
|
||||
or _storage_open
|
||||
or _chat_input_open
|
||||
or (
|
||||
_emote_radial_menu != null
|
||||
and _emote_radial_menu.is_open()
|
||||
)
|
||||
or (
|
||||
_quick_radial_menu != null
|
||||
and _quick_radial_menu.is_open()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
func _on_chat_text_entry_ownership_changed(active: bool) -> void:
|
||||
_chat_input_open = active
|
||||
if active:
|
||||
|
|
@ -3080,4 +3158,5 @@ func _refresh_chat_availability() -> void:
|
|||
)
|
||||
_chat_ui.set_available(chat_available)
|
||||
_chat_ui.set_hud_hidden(_gameplay_hud_hidden and not _chat_input_open)
|
||||
_chat_ui.set_world_nameplates_hud_hidden(_gameplay_hud_hidden)
|
||||
passive_pointer_ui_changed.emit(chat_available)
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ mouse_filter = 2
|
|||
[node name="SurfaceDrawingToolbar" parent="UIRoot" instance=ExtResource("11_drawing_toolbar")]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="GameplayTransientHUD" type="Control" parent="UIRoot/CanonicalStage"]
|
||||
[node name="GameplayScreenEffects" type="Control" parent="UIRoot"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
|
|
@ -91,7 +91,7 @@ grow_horizontal = 2
|
|||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="FishingDangerVignette" type="ColorRect" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
|
||||
[node name="FishingDangerVignette" type="ColorRect" parent="UIRoot/GameplayScreenEffects"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = 50
|
||||
|
|
@ -105,6 +105,16 @@ mouse_filter = 2
|
|||
material = SubResource("ShaderMaterial_fishing_vignette")
|
||||
color = Color(1, 1, 1, 1)
|
||||
|
||||
[node name="GameplayTransientHUD" type="Control" parent="UIRoot/CanonicalStage"]
|
||||
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="ActiveBaitIndicator" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
|
||||
unique_name_in_owner = true
|
||||
z_index = 54
|
||||
|
|
|
|||
|
|
@ -801,7 +801,10 @@ func _refresh_entries() -> void:
|
|||
else "online"
|
||||
)
|
||||
_server_list.add_item("%s — %s" % [
|
||||
str(friend.get("display_name", "Player")), state,
|
||||
str(friend.get(
|
||||
"display_name",
|
||||
NetworkProfilePreferences.DEFAULT_DISPLAY_NAME,
|
||||
)), state,
|
||||
])
|
||||
return
|
||||
if _saved_servers == null or _mode == Mode.DIRECT:
|
||||
|
|
@ -1451,7 +1454,9 @@ func _format_friend_details(friend: Dictionary) -> String:
|
|||
return "Select a friend."
|
||||
var room := _selected_friend_room()
|
||||
var lines: Array[String] = [
|
||||
"Friend: %s" % str(friend.get("display_name", "Player")),
|
||||
"Friend: %s" % str(friend.get(
|
||||
"display_name", NetworkProfilePreferences.DEFAULT_DISPLAY_NAME
|
||||
)),
|
||||
"Status: %s" % (
|
||||
"online" if bool(friend.get("online", false)) else "offline"
|
||||
),
|
||||
|
|
|
|||
|
|
@ -689,7 +689,10 @@ func _build_relationship_rows() -> void:
|
|||
label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
var fingerprint := str(record["fingerprint"])
|
||||
label.text = "%s %s" % [
|
||||
str(record.get("last_known_display_name", "Player")),
|
||||
str(record.get(
|
||||
"last_known_display_name",
|
||||
NetworkProfilePreferences.DEFAULT_DISPLAY_NAME,
|
||||
)),
|
||||
"Blocked" if bool(record.get("blocked", false)) else "Muted",
|
||||
]
|
||||
_configure_identity_tooltip(
|
||||
|
|
@ -733,7 +736,9 @@ func _build_friend_rows() -> void:
|
|||
for friend: Dictionary in friends:
|
||||
var row := _make_row()
|
||||
var fingerprint := str(friend.get("fingerprint", ""))
|
||||
var display_name := str(friend.get("display_name", "Player"))
|
||||
var display_name := str(friend.get(
|
||||
"display_name", NetworkProfilePreferences.DEFAULT_DISPLAY_NAME
|
||||
))
|
||||
var room: Dictionary = (
|
||||
friend.get("room", {})
|
||||
if typeof(friend.get("room", {})) == TYPE_DICTIONARY
|
||||
|
|
@ -835,7 +840,10 @@ func _build_ban_rows() -> void:
|
|||
label.clip_text = true
|
||||
label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
label.text = "%s banned %s" % [
|
||||
str(record.get("last_known_display_name", "Player")),
|
||||
str(record.get(
|
||||
"last_known_display_name",
|
||||
NetworkProfilePreferences.DEFAULT_DISPLAY_NAME,
|
||||
)),
|
||||
Time.get_date_string_from_unix_time(int(record.get("banned_unix", 0))),
|
||||
]
|
||||
_configure_identity_tooltip(
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const VoiceProfilesType = preload(
|
|||
"res://player/animalese_voice_profiles.gd"
|
||||
)
|
||||
const VOICE_CATEGORY_ID: String = "voice"
|
||||
const NAME_TOOLTIP_BASE: String = "Shown to other players in multiplayer."
|
||||
|
||||
enum ControllerZone {
|
||||
ACCOUNT,
|
||||
|
|
@ -45,8 +46,10 @@ enum ControllerZone {
|
|||
var _service: NetworkProfileService
|
||||
var _experience: PlayerExperience
|
||||
var _draft_name: String = ""
|
||||
var _draft_title: String = NetworkProfilePreferences.DEFAULT_TITLE
|
||||
var _draft_appearance: Dictionary = {}
|
||||
var _persisted_name: String = ""
|
||||
var _persisted_title: String = NetworkProfilePreferences.DEFAULT_TITLE
|
||||
var _persisted_appearance: Dictionary = {}
|
||||
var _draft_voice_id: String = VoiceProfilesType.DEFAULT_ID
|
||||
var _persisted_voice_id: String = VoiceProfilesType.DEFAULT_ID
|
||||
|
|
@ -61,6 +64,8 @@ var _dirty: bool = false
|
|||
var _allow_duplicate: bool = false
|
||||
|
||||
var _name_edit: LineEdit
|
||||
var _title_edit: LineEdit
|
||||
var _name_label: Label
|
||||
var _name_status: Label
|
||||
var _suggestions: HBoxContainer
|
||||
var _category_list: VBoxContainer
|
||||
|
|
@ -215,8 +220,9 @@ func consume_escape() -> bool:
|
|||
if _discard_confirmation.visible:
|
||||
_close_discard_confirmation()
|
||||
return true
|
||||
if _name_edit.has_focus():
|
||||
if _name_edit.has_focus() or _title_edit.has_focus():
|
||||
_name_edit.release_focus()
|
||||
_title_edit.release_focus()
|
||||
return true
|
||||
if _dirty:
|
||||
_show_confirmation("discard")
|
||||
|
|
@ -426,6 +432,18 @@ func _focus_controller_zone() -> void:
|
|||
if not _profile_interactive:
|
||||
return
|
||||
var controls: Array[Control] = _active_controller_zone_controls()
|
||||
# The account zone starts with a LineEdit in layout order. Do not activate
|
||||
# text entry merely because the Profile page opened or controller focus
|
||||
# returned from customization; keep the name field opt-in.
|
||||
if (
|
||||
_controller_zone == ControllerZone.ACCOUNT
|
||||
and _customize_button in controls
|
||||
and _customize_button.focus_mode != Control.FOCUS_NONE
|
||||
and _customize_button.is_visible_in_tree()
|
||||
and not _customize_button.disabled
|
||||
):
|
||||
_customize_button.grab_focus()
|
||||
return
|
||||
for control: Control in controls:
|
||||
var button := control as BaseButton
|
||||
if (
|
||||
|
|
@ -496,6 +514,7 @@ func _account_controller_controls() -> Array[Control]:
|
|||
var controls: Array[Control] = []
|
||||
for control: Control in [
|
||||
_name_edit,
|
||||
_title_edit,
|
||||
_customize_button,
|
||||
_apply_button,
|
||||
_revert_button,
|
||||
|
|
@ -604,22 +623,26 @@ func _build_ui() -> void:
|
|||
layout.add_child(account_row)
|
||||
var account_stack := VBoxContainer.new()
|
||||
account_stack.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
account_stack.add_theme_constant_override("separation", 2)
|
||||
account_stack.add_theme_constant_override("separation", 7)
|
||||
account_row.add_child(account_stack)
|
||||
var name_line := HBoxContainer.new()
|
||||
name_line.name = "PlayerNameRow"
|
||||
name_line.add_theme_constant_override("separation", 10)
|
||||
account_stack.add_child(name_line)
|
||||
var name_label := Label.new()
|
||||
name_label.text = "player name"
|
||||
name_label.custom_minimum_size.x = 98.0
|
||||
name_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
name_label.add_theme_color_override(
|
||||
_name_label = Label.new()
|
||||
_name_label.name = "PlayerNameLabel"
|
||||
_name_label.text = "name"
|
||||
_name_label.tooltip_text = NAME_TOOLTIP_BASE
|
||||
_name_label.custom_minimum_size.x = 128.0
|
||||
_name_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
_name_label.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||
)
|
||||
name_line.add_child(name_label)
|
||||
name_line.add_child(_name_label)
|
||||
_name_edit = LineEdit.new()
|
||||
_name_edit.name = "PlayerNameEdit"
|
||||
_name_edit.max_length = NetworkProtocol.MAX_DISPLAY_NAME_LENGTH
|
||||
_name_edit.placeholder_text = "Player"
|
||||
_name_edit.placeholder_text = NetworkProfilePreferences.DEFAULT_DISPLAY_NAME
|
||||
_name_edit.custom_minimum_size = Vector2(280, 34)
|
||||
UtilityPageStyle.apply_ocean_line_edit(_name_edit)
|
||||
_name_edit.add_theme_stylebox_override(
|
||||
|
|
@ -629,23 +652,46 @@ func _build_ui() -> void:
|
|||
)
|
||||
_name_edit.text_changed.connect(_on_name_changed)
|
||||
name_line.add_child(_name_edit)
|
||||
var helper := Label.new()
|
||||
helper.text = "Shown to other players in multiplayer."
|
||||
helper.add_theme_font_size_override("font_size", 12)
|
||||
helper.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||
var title_line := HBoxContainer.new()
|
||||
title_line.name = "PlayerTitleRow"
|
||||
title_line.add_theme_constant_override("separation", 10)
|
||||
account_stack.add_child(title_line)
|
||||
var title_label := Label.new()
|
||||
title_label.name = "PlayerTitleLabel"
|
||||
title_label.text = "title"
|
||||
title_label.tooltip_text = "Shown below your name to other players."
|
||||
title_label.custom_minimum_size.x = 128.0
|
||||
title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
title_label.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||
)
|
||||
account_stack.add_child(helper)
|
||||
title_line.add_child(title_label)
|
||||
_title_edit = LineEdit.new()
|
||||
_title_edit.name = "PlayerTitleEdit"
|
||||
_title_edit.max_length = NetworkProtocol.MAX_PLAYER_TITLE_LENGTH
|
||||
_title_edit.placeholder_text = NetworkProfilePreferences.DEFAULT_TITLE
|
||||
_title_edit.custom_minimum_size = Vector2(280, 34)
|
||||
UtilityPageStyle.apply_ocean_line_edit(_title_edit)
|
||||
_title_edit.add_theme_stylebox_override(
|
||||
"normal", UtilityPageStyle.ocean_button_style(
|
||||
UtilityPageStyle.OCEAN_PANEL_MID
|
||||
)
|
||||
)
|
||||
_title_edit.text_changed.connect(_on_title_changed)
|
||||
title_line.add_child(_title_edit)
|
||||
_name_status = Label.new()
|
||||
_name_status.add_theme_font_size_override("font_size", 12)
|
||||
_name_status.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||
)
|
||||
_name_status.name = "PlayerNameStatus"
|
||||
_name_status.visible = false
|
||||
account_stack.add_child(_name_status)
|
||||
_suggestions = HBoxContainer.new()
|
||||
_suggestions.add_theme_constant_override("separation", 6)
|
||||
account_stack.add_child(_suggestions)
|
||||
|
||||
var account_info_stack := VBoxContainer.new()
|
||||
account_info_stack.name = "ProfileIdentityStack"
|
||||
account_info_stack.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
account_info_stack.add_theme_constant_override("separation", 7)
|
||||
account_row.add_child(account_info_stack)
|
||||
var identity_value := Label.new()
|
||||
identity_value.name = "IdentityFingerprint"
|
||||
identity_value.text = "identity • stored on this device"
|
||||
|
|
@ -656,11 +702,14 @@ func _build_ui() -> void:
|
|||
identity_value.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||
)
|
||||
account_stack.add_child(identity_value)
|
||||
identity_value.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
account_info_stack.add_child(identity_value)
|
||||
|
||||
var experience_row := HBoxContainer.new()
|
||||
experience_row.name = "ProfileExperienceRow"
|
||||
experience_row.alignment = BoxContainer.ALIGNMENT_END
|
||||
experience_row.add_theme_constant_override("separation", 9)
|
||||
account_stack.add_child(experience_row)
|
||||
account_info_stack.add_child(experience_row)
|
||||
_experience_level = Label.new()
|
||||
_experience_level.custom_minimum_size.x = 64.0
|
||||
_experience_level.add_theme_font_size_override("font_size", 14)
|
||||
|
|
@ -695,7 +744,7 @@ func _build_ui() -> void:
|
|||
actions.alignment = BoxContainer.ALIGNMENT_END
|
||||
actions.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
||||
actions.add_theme_constant_override("separation", 7)
|
||||
account_row.add_child(actions)
|
||||
account_info_stack.add_child(actions)
|
||||
_customize_button = Button.new()
|
||||
_customize_button.text = "customize"
|
||||
_customize_button.custom_minimum_size.x = 96.0
|
||||
|
|
@ -2127,10 +2176,16 @@ func _on_name_changed(value: String) -> void:
|
|||
_refresh_actions()
|
||||
_clear_suggestions()
|
||||
if NetworkProfilePreferences.is_valid_display_name(value):
|
||||
_name_status.text = "Checking this game…"
|
||||
_set_name_status("Checking this game…")
|
||||
_debounce.start()
|
||||
else:
|
||||
_name_status.text = "Use 1–24 plain-text characters."
|
||||
_set_name_status("Use 1–24 plain-text characters.")
|
||||
|
||||
|
||||
func _on_title_changed(value: String) -> void:
|
||||
_draft_title = value
|
||||
_dirty = _draft_differs()
|
||||
_refresh_actions()
|
||||
|
||||
|
||||
func _request_conflict_check() -> void:
|
||||
|
|
@ -2145,9 +2200,9 @@ func _on_conflict_result(
|
|||
) -> void:
|
||||
_clear_suggestions()
|
||||
if not has_conflict:
|
||||
_name_status.text = "Name is available in this game."
|
||||
_set_name_status("Name is available in this game.")
|
||||
return
|
||||
_name_status.text = "That name is already in use in this game."
|
||||
_set_name_status("That name is already in use in this game.")
|
||||
for suggestion: String in suggestion_names:
|
||||
var button := Button.new()
|
||||
button.text = suggestion
|
||||
|
|
@ -2158,7 +2213,7 @@ func _on_conflict_result(
|
|||
anyway.text = "use anyway"
|
||||
anyway.pressed.connect(func() -> void:
|
||||
_allow_duplicate = true
|
||||
_name_status.text = "Duplicate name allowed for this apply."
|
||||
_set_name_status("Duplicate name allowed for this apply.")
|
||||
)
|
||||
UtilityPageStyle.apply_compact_ocean_button(anyway)
|
||||
_suggestions.add_child(anyway)
|
||||
|
|
@ -2209,12 +2264,13 @@ func _apply() -> void:
|
|||
_draft_speech_speed_id,
|
||||
_draft_call_id,
|
||||
_draft_sample_set_id,
|
||||
_draft_title,
|
||||
)
|
||||
|
||||
|
||||
func _on_apply_finished(accepted: bool, message: String) -> void:
|
||||
_apply_button.disabled = false
|
||||
_name_status.text = message
|
||||
_set_name_status(message)
|
||||
if accepted:
|
||||
_load_persisted()
|
||||
|
||||
|
|
@ -2223,6 +2279,7 @@ func _load_persisted() -> void:
|
|||
if _service == null:
|
||||
return
|
||||
_persisted_name = _service.get_persisted_name()
|
||||
_persisted_title = _service.get_persisted_title()
|
||||
_persisted_appearance = _service.get_persisted_appearance()
|
||||
_persisted_voice_id = _service.get_persisted_voice_id()
|
||||
_persisted_speech_speed_id = (
|
||||
|
|
@ -2231,6 +2288,7 @@ func _load_persisted() -> void:
|
|||
_persisted_call_id = _service.get_persisted_call_id()
|
||||
_persisted_sample_set_id = _service.get_persisted_sample_set_id()
|
||||
_draft_name = _persisted_name
|
||||
_draft_title = _persisted_title
|
||||
_draft_appearance = _persisted_appearance.duplicate(true)
|
||||
_draft_voice_id = _persisted_voice_id
|
||||
_draft_speech_speed_id = _persisted_speech_speed_id
|
||||
|
|
@ -2240,12 +2298,13 @@ func _load_persisted() -> void:
|
|||
VoiceProfilesType.speed_for(_persisted_speech_speed_id)
|
||||
)
|
||||
_name_edit.text = _draft_name
|
||||
_title_edit.text = _draft_title
|
||||
_preview.apply_appearance_profile(_draft_appearance)
|
||||
_appearance_preview_timer.stop()
|
||||
_service.preview_appearance(_persisted_appearance)
|
||||
_dirty = false
|
||||
_allow_duplicate = false
|
||||
_name_status.text = ""
|
||||
_set_name_status("")
|
||||
_clear_suggestions()
|
||||
_refresh_options()
|
||||
_refresh_actions()
|
||||
|
|
@ -2288,6 +2347,8 @@ func _confirm_pending_action() -> void:
|
|||
_draft_speech_speed_id = VoiceProfilesType.DEFAULT_SPEED_ID
|
||||
_draft_call_id = VoiceProfilesType.DEFAULT_CALL_ID
|
||||
_draft_sample_set_id = VoiceProfilesType.DEFAULT_SAMPLE_SET_ID
|
||||
_draft_title = NetworkProfilePreferences.DEFAULT_TITLE
|
||||
_title_edit.text = _draft_title
|
||||
_preview.apply_appearance_profile(_draft_appearance)
|
||||
_queue_draft_appearance_preview()
|
||||
_dirty = _draft_differs()
|
||||
|
|
@ -2318,6 +2379,7 @@ func _publish_draft_appearance() -> void:
|
|||
func _draft_differs() -> bool:
|
||||
return (
|
||||
_draft_name.strip_edges() != _persisted_name
|
||||
or _draft_title.strip_edges() != _persisted_title
|
||||
or _draft_appearance != _persisted_appearance
|
||||
or _draft_voice_id != _persisted_voice_id
|
||||
or _draft_speech_speed_id != _persisted_speech_speed_id
|
||||
|
|
@ -2330,6 +2392,7 @@ func _refresh_actions() -> void:
|
|||
_apply_button.disabled = (
|
||||
not _dirty
|
||||
or not NetworkProfilePreferences.is_valid_display_name(_draft_name)
|
||||
or not NetworkProfilePreferences.is_valid_title(_draft_title)
|
||||
)
|
||||
_revert_button.disabled = not _dirty
|
||||
|
||||
|
|
@ -2339,3 +2402,12 @@ func _clear_suggestions() -> void:
|
|||
_suggestions.remove_child(child)
|
||||
child.queue_free()
|
||||
_apply_controller_zone_focus()
|
||||
|
||||
|
||||
func _set_name_status(message: String) -> void:
|
||||
_name_status.text = message
|
||||
_name_label.tooltip_text = (
|
||||
NAME_TOOLTIP_BASE
|
||||
if message.is_empty()
|
||||
else "%s\n%s" % [NAME_TOOLTIP_BASE, message]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ enum SeedMode {
|
|||
@onready var _empty_slots_label: Label = %EmptySlotsLabel
|
||||
@onready var _slot_name_edit: LineEdit = %SelectedSlotName
|
||||
@onready var _slot_summary: RichTextLabel = %SlotSummary
|
||||
@onready var _online_slot_button: CheckButton = %OnlineSlotButton
|
||||
@onready var _play_button: Button = %PlaySlotButton
|
||||
@onready var _rename_button: Button = %RenameSlotButton
|
||||
@onready var _duplicate_button: Button = %DuplicateSlotButton
|
||||
|
|
@ -75,6 +76,7 @@ func _ready() -> void:
|
|||
_saves_tab.pressed.connect(_select_page.bind(PAGE_SAVES, true))
|
||||
_new_slot_tab.pressed.connect(_open_fresh_slot_page)
|
||||
_play_button.pressed.connect(_request_play)
|
||||
_online_slot_button.toggled.connect(_on_online_slot_toggled)
|
||||
_rename_button.pressed.connect(_rename_selected_slot)
|
||||
_duplicate_button.pressed.connect(_prepare_duplicate)
|
||||
_export_button.pressed.connect(_choose_export_path)
|
||||
|
|
@ -285,9 +287,14 @@ func _refresh_slots() -> void:
|
|||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
button.focus_mode = Control.FOCUS_ALL
|
||||
button.toggle_mode = true
|
||||
button.text = str(slot.get("display_name", "save")) + (
|
||||
" · active" if bool(slot.get("active", false)) else ""
|
||||
)
|
||||
var markers: Array[String] = []
|
||||
if bool(slot.get("active", false)):
|
||||
markers.append("loaded")
|
||||
if bool(slot.get("online", false)):
|
||||
markers.append("online")
|
||||
button.text = str(slot.get("display_name", "save"))
|
||||
if not markers.is_empty():
|
||||
button.text += " · " + " · ".join(markers)
|
||||
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
UtilityPageStyle.apply_ocean_button(button)
|
||||
button.pressed.connect(_select_slot.bind(slot_id, false))
|
||||
|
|
@ -321,10 +328,14 @@ func _refresh_selected_slot() -> void:
|
|||
_slot_name_edit.text = ""
|
||||
_slot_name_edit.editable = false
|
||||
_slot_summary.text = "[center]create or import a save slot to begin.[/center]"
|
||||
_online_slot_button.set_pressed_no_signal(false)
|
||||
else:
|
||||
_slot_name_edit.editable = true
|
||||
_slot_name_edit.text = str(slot.get("display_name", "save"))
|
||||
_slot_summary.text = _format_slot_summary(slot)
|
||||
_online_slot_button.set_pressed_no_signal(
|
||||
bool(slot.get("online", false))
|
||||
)
|
||||
_refresh_action_state()
|
||||
|
||||
|
||||
|
|
@ -337,9 +348,9 @@ func _format_slot_summary(slot: Dictionary) -> String:
|
|||
int(slot.get("last_played_at_unix", 0))
|
||||
)
|
||||
return (
|
||||
"[font_size=24]level %d[/font_size]\n\n"
|
||||
"[font_size=24]level %d[/font_size]\n"
|
||||
+ "%d catches · %d discovered\n"
|
||||
+ "%d fishcoins\n\n"
|
||||
+ "%d fishcoins\n"
|
||||
+ "%s · seed %d\n"
|
||||
+ "last played %s"
|
||||
) % [
|
||||
|
|
@ -358,6 +369,7 @@ func _refresh_action_state() -> void:
|
|||
var has_slot: bool = not slot.is_empty()
|
||||
var has_save: bool = has_slot and bool(slot.get("has_save", false))
|
||||
_play_button.disabled = _busy or not has_save
|
||||
_online_slot_button.disabled = _busy or not has_save
|
||||
_rename_button.disabled = _busy or not has_slot
|
||||
_duplicate_button.disabled = _busy or not has_save
|
||||
_export_button.disabled = _busy or not has_save
|
||||
|
|
@ -375,6 +387,21 @@ func _request_play() -> void:
|
|||
play_requested.emit(_selected_slot_id)
|
||||
|
||||
|
||||
func _on_online_slot_toggled(enabled: bool) -> void:
|
||||
if _catalog == null or _selected_slot_id.is_empty():
|
||||
return
|
||||
if not enabled:
|
||||
_online_slot_button.set_pressed_no_signal(
|
||||
_catalog.get_online_slot_id() == _selected_slot_id
|
||||
)
|
||||
return
|
||||
if not _catalog.mark_slot_for_online(_selected_slot_id):
|
||||
_status.text = "the online progression could not be changed."
|
||||
_online_slot_button.set_pressed_no_signal(false)
|
||||
return
|
||||
_status.text = "this progression will be used when joining online games."
|
||||
|
||||
|
||||
func _rename_selected_slot() -> void:
|
||||
if _catalog == null or _rename_button.disabled:
|
||||
return
|
||||
|
|
@ -644,8 +671,9 @@ func _configure_controller_focus() -> void:
|
|||
var bottom: Control = _import_button if index == _slot_buttons.size() - 1 else _slot_buttons[index + 1]
|
||||
_set_neighbors(button, button, _slot_name_edit, top, bottom)
|
||||
_set_neighbors(_import_button, _import_button, _slot_name_edit, last, _back_button)
|
||||
_set_neighbors(_slot_name_edit, first, _slot_name_edit, _saves_tab, _play_button)
|
||||
_set_neighbors(_play_button, _import_button, _play_button, _slot_name_edit, _rename_button)
|
||||
_set_neighbors(_slot_name_edit, first, _slot_name_edit, _saves_tab, _online_slot_button)
|
||||
_set_neighbors(_online_slot_button, _import_button, _online_slot_button, _slot_name_edit, _play_button)
|
||||
_set_neighbors(_play_button, _import_button, _play_button, _online_slot_button, _rename_button)
|
||||
_set_neighbors(_rename_button, _import_button, _duplicate_button, _play_button, _export_button)
|
||||
_set_neighbors(_duplicate_button, _rename_button, _duplicate_button, _play_button, _delete_button)
|
||||
_set_neighbors(_export_button, _import_button, _delete_button, _rename_button, _back_button)
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ layout_mode = 2
|
|||
[node name="DetailsColumn" type="VBoxContainer" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/SavesPage"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 10
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="Title" type="Label" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/SavesPage/DetailsColumn"]
|
||||
layout_mode = 2
|
||||
|
|
@ -165,6 +165,14 @@ scroll_active = false
|
|||
autowrap_mode = 2
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="OnlineSlotButton" type="CheckButton" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/SavesPage/DetailsColumn"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 44)
|
||||
layout_mode = 2
|
||||
focus_mode = 2
|
||||
text = "use this save online"
|
||||
tooltip_text = "Use this save when joining an online room"
|
||||
|
||||
[node name="Actions" type="VBoxContainer" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/SavesPage/DetailsColumn"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
|
@ -330,7 +338,7 @@ text = "create and play"
|
|||
|
||||
[node name="FooterGroup" type="MarginContainer" parent="MainPanel/OuterMargin/Layout"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_top = 34
|
||||
theme_override_constants/margin_top = 32
|
||||
|
||||
[node name="FooterLayout" type="VBoxContainer" parent="MainPanel/OuterMargin/Layout/FooterGroup"]
|
||||
layout_mode = 2
|
||||
|
|
|
|||
|
|
@ -628,6 +628,7 @@ func _prepare_awaiting_start_input() -> void:
|
|||
_continue_stats_drawer.hide()
|
||||
_continue_stats_drawer.modulate.a = 0.0
|
||||
_continue_stats_drawer.scale = Vector2(1.0, 0.001)
|
||||
_version_row.show()
|
||||
_main_content.show()
|
||||
_start_prompt_center.show()
|
||||
_start_prompt_center.modulate.a = 1.0
|
||||
|
|
@ -927,6 +928,7 @@ func _on_continue_pressed() -> void:
|
|||
_release_primary_menu_focus()
|
||||
_presentation_center.hide()
|
||||
_start_prompt_center.hide()
|
||||
_version_row.hide()
|
||||
_join_game_page.close_page()
|
||||
_credits_page.close_page()
|
||||
_new_game_setup_page.close_page()
|
||||
|
|
@ -935,6 +937,7 @@ func _on_continue_pressed() -> void:
|
|||
|
||||
func _close_save_slots() -> void:
|
||||
_save_slots_page.close_page()
|
||||
_version_row.show()
|
||||
_presentation_center.show()
|
||||
_button_center.show()
|
||||
_start_prompt_center.hide()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ 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
|
||||
const ACTIVE_TWEEN_META: StringName = &"typewriter_reveal_active_tween"
|
||||
|
||||
static var _characters_per_second: float = DEFAULT_CHARACTERS_PER_SECOND
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ static func start(
|
|||
) -> float:
|
||||
if label == null:
|
||||
return 0.0
|
||||
cancel(label, false)
|
||||
var character_count: int = label.get_total_character_count()
|
||||
if character_count <= 0:
|
||||
label.visible_characters = -1
|
||||
|
|
@ -38,10 +40,39 @@ static func start(
|
|||
)
|
||||
var duration := float(character_count) / resolved_characters_per_second
|
||||
var reveal_tween := label.create_tween()
|
||||
label.set_meta(ACTIVE_TWEEN_META, reveal_tween)
|
||||
reveal_tween.tween_property(
|
||||
label,
|
||||
"visible_characters",
|
||||
character_count,
|
||||
duration,
|
||||
).from(0).set_trans(Tween.TRANS_LINEAR)
|
||||
reveal_tween.finished.connect(
|
||||
_on_reveal_finished.bind(label, reveal_tween),
|
||||
CONNECT_ONE_SHOT,
|
||||
)
|
||||
return duration
|
||||
|
||||
|
||||
static func cancel(label: Label, reveal_all: bool = true) -> void:
|
||||
if label == null:
|
||||
return
|
||||
var reveal_tween: Tween
|
||||
if label.has_meta(ACTIVE_TWEEN_META):
|
||||
reveal_tween = label.get_meta(ACTIVE_TWEEN_META) as Tween
|
||||
if reveal_tween != null and reveal_tween.is_valid():
|
||||
reveal_tween.kill()
|
||||
if label.has_meta(ACTIVE_TWEEN_META):
|
||||
label.remove_meta(ACTIVE_TWEEN_META)
|
||||
if reveal_all:
|
||||
label.visible_characters = -1
|
||||
|
||||
|
||||
static func _on_reveal_finished(label: Label, reveal_tween: Tween) -> void:
|
||||
if label == null or not is_instance_valid(label):
|
||||
return
|
||||
if (
|
||||
label.has_meta(ACTIVE_TWEEN_META)
|
||||
and label.get_meta(ACTIVE_TWEEN_META) == reveal_tween
|
||||
):
|
||||
label.remove_meta(ACTIVE_TWEEN_META)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue