Add synchronized Fishnet jobs and laptop UI

This commit is contained in:
Alexander Sellite 2026-08-03 00:43:43 -04:00
parent 46df74361a
commit 26fbd41132
39 changed files with 3257 additions and 188 deletions

View file

@ -60,6 +60,7 @@ signal shop_backdrop_visibility_changed(is_visible: bool)
@onready var _status_label: Label = %StatusLabel
@onready var _gameplay_transient_hud: Control = %GameplayTransientHUD
@onready var _experience_presentation: Control = %ExperiencePresentation
@onready var _catch_track: Control = %CatchTrack
@onready var _green_catch_progress: ProgressBar = %GreenCatchProgress
@onready var _red_chase_progress: ProgressBar = %RedChaseProgress
@ -117,6 +118,15 @@ var _experience_panel_rest_y: float = 18.0
func _ready() -> void:
# Reward feedback must remain above full-screen canonical menus. Keeping the
# overlay as the final stage child makes that ownership explicit instead of
# relying on scene declaration order when another menu adds high-z children.
var presentation_parent: Node = _experience_presentation.get_parent()
if presentation_parent != null:
presentation_parent.move_child(
_experience_presentation,
presentation_parent.get_child_count() - 1,
)
_emote_radial_menu.emote_selected.connect(_on_emote_selected)
_chat_ui.text_entry_ownership_changed.connect(
_on_chat_text_entry_ownership_changed
@ -171,6 +181,7 @@ func setup(
art_unlocks: PlayerArtUnlocks,
world_time: WorldTimeServiceType,
world_weather: WorldWeatherServiceType,
player_jobs: PlayerJobService,
) -> void:
_player = player
_fishing_spot = fishing_spot
@ -211,6 +222,7 @@ func setup(
player,
inventory,
collection_log,
experience,
wallet,
sale_service,
default_buyer,
@ -226,6 +238,8 @@ func setup(
reservations,
network_profile_service,
network_player_list,
player_jobs,
world_time,
)
_hotbar_ui.setup(hotbar, bag, item_catalog, fishing_spot, inventory)
_fishing_shop.setup(
@ -386,6 +400,7 @@ func get_pause_menu() -> PauseMenuType:
func set_gameplay_ui_enabled(enabled: bool) -> void:
_gameplay_ui_enabled = enabled
_gameplay_transient_hud.visible = enabled and not _player_menu_open
_experience_presentation.visible = enabled
_refresh_chat_availability()
if not enabled:
if _surface_drawing != null:
@ -642,6 +657,7 @@ func _on_experience_awarded(
"previous_level": previous_level,
"new_level": new_level,
})
_start_next_experience_animation()
func _start_next_experience_animation() -> void:
@ -785,6 +801,13 @@ func _update_experience_progress(total_experience: int) -> void:
func _update_experience_bubble_position() -> void:
if not _experience_animation_active or _player == null:
return
if _player_menu_open:
_experience_bubble.show()
_experience_bubble.position = Vector2(
(_canonical_stage.size.x - _experience_bubble.size.x) * 0.5,
_experience_panel_rest_y + _experience_panel.size.y + 10.0,
)
return
var camera: Camera3D = _player.get_gameplay_camera()
var anchor_position: Vector3 = _player.get_chat_anchor_position()
if camera == null or camera.is_position_behind(anchor_position):

View file

@ -80,7 +80,18 @@ grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="ExperienceProgressPanel" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
[node name="ExperiencePresentation" type="Control" parent="UIRoot/CanonicalStage"]
unique_name_in_owner = true
z_index = 200
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="ExperienceProgressPanel" type="PanelContainer" parent="UIRoot/CanonicalStage/ExperiencePresentation"]
unique_name_in_owner = true
visible = false
z_index = 63
@ -96,40 +107,40 @@ mouse_filter = 2
theme = ExtResource("3_theme")
theme_override_styles/panel = SubResource("StyleBox_experience_panel")
[node name="Margin" type="MarginContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceProgressPanel"]
[node name="Margin" type="MarginContainer" parent="UIRoot/CanonicalStage/ExperiencePresentation/ExperienceProgressPanel"]
layout_mode = 2
theme_override_constants/margin_left = 14
theme_override_constants/margin_top = 7
theme_override_constants/margin_right = 14
theme_override_constants/margin_bottom = 9
[node name="Layout" type="VBoxContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceProgressPanel/Margin"]
[node name="Layout" type="VBoxContainer" parent="UIRoot/CanonicalStage/ExperiencePresentation/ExperienceProgressPanel/Margin"]
layout_mode = 2
theme_override_constants/separation = 4
[node name="Header" type="HBoxContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceProgressPanel/Margin/Layout"]
[node name="Header" type="HBoxContainer" parent="UIRoot/CanonicalStage/ExperiencePresentation/ExperienceProgressPanel/Margin/Layout"]
layout_mode = 2
[node name="ExperienceLevelLabel" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceProgressPanel/Margin/Layout/Header"]
[node name="ExperienceLevelLabel" type="Label" parent="UIRoot/CanonicalStage/ExperiencePresentation/ExperienceProgressPanel/Margin/Layout/Header"]
unique_name_in_owner = true
layout_mode = 2
text = "level 1"
theme_override_colors/font_color = Color(0.95, 0.98, 1, 1)
theme_override_font_sizes/font_size = 16
[node name="Spacer" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceProgressPanel/Margin/Layout/Header"]
[node name="Spacer" type="Control" parent="UIRoot/CanonicalStage/ExperiencePresentation/ExperienceProgressPanel/Margin/Layout/Header"]
layout_mode = 2
size_flags_horizontal = 3
mouse_filter = 2
[node name="ExperienceAwardLabel" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceProgressPanel/Margin/Layout/Header"]
[node name="ExperienceAwardLabel" type="Label" parent="UIRoot/CanonicalStage/ExperiencePresentation/ExperienceProgressPanel/Margin/Layout/Header"]
unique_name_in_owner = true
layout_mode = 2
text = "+0 xp"
theme_override_colors/font_color = Color(1, 0.82, 0.4, 1)
theme_override_font_sizes/font_size = 16
[node name="ExperienceProgress" type="ProgressBar" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceProgressPanel/Margin/Layout"]
[node name="ExperienceProgress" type="ProgressBar" parent="UIRoot/CanonicalStage/ExperiencePresentation/ExperienceProgressPanel/Margin/Layout"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
@ -139,7 +150,7 @@ theme_override_styles/fill = SubResource("StyleBox_experience_fill")
value = 0.0
show_percentage = false
[node name="ExperienceBubble" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
[node name="ExperienceBubble" type="PanelContainer" parent="UIRoot/CanonicalStage/ExperiencePresentation"]
unique_name_in_owner = true
visible = false
z_index = 63
@ -149,14 +160,14 @@ mouse_filter = 2
theme = ExtResource("3_theme")
theme_override_styles/panel = SubResource("StyleBox_experience_panel")
[node name="Margin" type="MarginContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceBubble"]
[node name="Margin" type="MarginContainer" parent="UIRoot/CanonicalStage/ExperiencePresentation/ExperienceBubble"]
layout_mode = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 5
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 5
[node name="ExperienceBubbleLabel" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ExperienceBubble/Margin"]
[node name="ExperienceBubbleLabel" type="Label" parent="UIRoot/CanonicalStage/ExperiencePresentation/ExperienceBubble/Margin"]
unique_name_in_owner = true
layout_mode = 2
text = "+0 xp!"

View file

@ -113,17 +113,7 @@ func set_interactive(value: bool) -> void:
func _build_ui() -> void:
var paper := PanelContainer.new()
paper.position = Vector2(116, 126)
paper.size = Vector2(1048, 540)
paper.add_theme_stylebox_override("panel", UtilityPageStyle.panel_style())
add_child(paper)
var margin := MarginContainer.new()
margin.add_theme_constant_override("margin_left", 34)
margin.add_theme_constant_override("margin_right", 34)
margin.add_theme_constant_override("margin_top", 28)
margin.add_theme_constant_override("margin_bottom", 28)
paper.add_child(margin)
var margin: MarginContainer = UtilityPageStyle.build_laptop_screen(self)
var root := Control.new()
margin.add_child(root)
_inbox = _build_inbox()
@ -133,8 +123,8 @@ func _build_ui() -> void:
page.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
root.add_child(page)
_status = Label.new()
_status.position = Vector2(18, 448)
_status.size = Vector2(900, 28)
_status.position = Vector2(18, 408)
_status.size = Vector2(1024, 28)
_status.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_status.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
@ -147,7 +137,7 @@ func _build_ui() -> void:
func _build_inbox() -> Control:
var page := Control.new()
var title := Label.new()
title.text = "Mail"
title.text = "mail"
title.position = Vector2(12, 0)
title.size = Vector2(500, 42)
title.add_theme_font_size_override("font_size", 30)
@ -170,16 +160,16 @@ func _build_inbox() -> Control:
page.add_child(archive_view)
var scroll := ScrollContainer.new()
scroll.position = Vector2(12, 62)
scroll.size = Vector2(958, 374)
scroll.size = Vector2(1036, 334)
page.add_child(scroll)
_inbox_list = VBoxContainer.new()
_inbox_list.custom_minimum_size = Vector2(930, 0)
_inbox_list.custom_minimum_size = Vector2(1008, 0)
_inbox_list.add_theme_constant_override("separation", 8)
scroll.add_child(_inbox_list)
_empty_label = Label.new()
_empty_label.text = "No letters yet."
_empty_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_empty_label.custom_minimum_size = Vector2(930, 80)
_empty_label.custom_minimum_size = Vector2(1008, 80)
_inbox_list.add_child(_empty_label)
return page
@ -422,7 +412,7 @@ func _refresh_inbox() -> void:
empty.text = (
"No archived letters." if _showing_archive else "No letters yet."
)
empty.custom_minimum_size = Vector2(930, 80)
empty.custom_minimum_size = Vector2(1008, 80)
empty.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_inbox_list.add_child(empty)
return
@ -449,7 +439,7 @@ func _refresh_inbox() -> void:
first_line.left(72),
" · gift enclosed" if gift else "",
]
button.custom_minimum_size = Vector2(930, 54)
button.custom_minimum_size = Vector2(1008, 54)
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
button.pressed.connect(_open_letter.bind(str(letter["mail_id"])))
UtilityPageStyle.apply_ocean_button(button)

View file

@ -11,7 +11,8 @@ enum Mode {
}
const ADDRESS_FORMAT_HELP: String = (
"Hostname, IPv4, or [IPv6]. Port 7777 is used when omitted."
"Hostname, IPv4, or [IPv6]. Port 7777 is used when omitted; "
+ "include the port shown by a host when it differs."
)
const DIRECT_WORKFLOW_HELP: String = (
"%s\nJoin Now connects once; Save Server stores this address locally."
@ -435,10 +436,19 @@ func _refresh() -> void:
and _network_session.state != NetworkSession.State.INACTIVE
)
if _session_summary.visible:
_session_summary.text = "%d / %d players" % [
_network_session.get_player_count(),
_network_session.get_session_max_players(),
]
_session_summary.text = (
"UDP %d%d / %d players"
% [
_network_session.get_host_port(),
_network_session.get_player_count(),
_network_session.get_session_max_players(),
]
if _network_session.is_host()
else "%d / %d players" % [
_network_session.get_player_count(),
_network_session.get_session_max_players(),
]
)
if not direct:
if selected:
_details.text = _format_entry_details(_selected_entry)

View file

@ -163,7 +163,7 @@ unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_color = Color(0.624, 0.812, 0.824, 1)
theme_override_font_sizes/font_size = 13
text = "Hostname, IPv4, or [IPv6]. Port 7777 is used when omitted.\nJoin Now connects once; Save Server stores this address locally."
text = "Hostname, IPv4, or [IPv6]. Port 7777 is used when omitted; include the port shown by a host when it differs.\nJoin Now connects once; Save Server stores this address locally."
horizontal_alignment = 1
autowrap_mode = 2

View file

@ -27,6 +27,9 @@ const PlayerHotbarType = preload("res://inventory/player_hotbar.gd")
const PlayerCoolerCapacityType = preload(
"res://progression/player_cooler_capacity.gd"
)
const PlayerExperienceType = preload(
"res://progression/player_experience.gd"
)
const FishBatchSelectionType = preload(
"res://ui/fish_batch_selection.gd"
)
@ -87,6 +90,7 @@ enum Section {
BAG,
TACKLE_BOX,
LOGBOOK,
NET,
MAIL,
PROFILE,
PLAYERS,
@ -197,6 +201,7 @@ const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0)
@onready var _bag_sprite_detail_data: Label = %BagSpriteDetailData
@onready var _logbook_page: Control = %LogbookPage
@onready var _catalog_logbook: LogbookPage = %CatalogLogbook
@onready var _the_net_page: TheNetPage = %TheNetPage
@onready var _mail_page: MailPage = %MailPage
@onready var _profile_page: ProfilePage = %ProfilePage
@onready var _players_page: PlayersPage = %PlayersPage
@ -215,6 +220,7 @@ const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0)
@onready var _logbook_page_status: BubbleStatusBubbleType = %LogbookPageStatus
@onready var _inventory_tab: BubbleButtonType = %InventoryTab
@onready var _logbook_tab: BubbleButtonType = %LogbookTab
@onready var _the_net_tab: BubbleButtonType = %TheNetTab
@onready var _mail_tab: BubbleButtonType = %MailTab
@onready var _profile_tab: BubbleButtonType = %ProfileTab
@onready var _players_tab: BubbleButtonType = %PlayersTab
@ -319,6 +325,7 @@ var _cooler_rest_position: Vector2 = Vector2.ZERO
var _bag_rest_position: Vector2 = Vector2.ZERO
var _tackle_rest_position: Vector2 = Vector2.ZERO
var _logbook_rest_position: Vector2 = Vector2.ZERO
var _the_net_rest_position: Vector2 = Vector2.ZERO
var _mail_rest_position: Vector2 = Vector2.ZERO
var _profile_rest_position: Vector2 = Vector2.ZERO
var _page_outgoing_root: Control
@ -360,6 +367,7 @@ func _ready() -> void:
_logbook_tab.pressed.connect(
_show_section.bind(Section.LOGBOOK)
)
_the_net_tab.pressed.connect(_show_section.bind(Section.NET))
_mail_tab.pressed.connect(_show_section.bind(Section.MAIL))
_profile_tab.pressed.connect(_show_section.bind(Section.PROFILE))
_players_tab.pressed.connect(_show_section.bind(Section.PLAYERS))
@ -387,6 +395,7 @@ func _ready() -> void:
_navigation_cluster.configure([
_inventory_tab,
_logbook_tab,
_the_net_tab,
_mail_tab,
_profile_tab,
_players_tab,
@ -477,6 +486,7 @@ func setup(
player: PlayerType,
inventory: FishInventoryType,
collection_log: CollectionLogType,
experience: PlayerExperienceType,
wallet: PlayerWalletType,
sale_service: FishSaleServiceType,
default_buyer: FishBuyerProfileType,
@ -492,6 +502,8 @@ func setup(
reservations: PlayerAssetReservationService,
network_profile_service: NetworkProfileService,
network_player_list: NetworkPlayerListService,
player_jobs: PlayerJobService,
world_time: WorldTimeService,
) -> void:
_player = player
_inventory = inventory
@ -514,9 +526,10 @@ func setup(
_mail_page.setup(
network_mail_service, reservations, inventory, wallet, bag, item_catalog
)
_profile_page.setup(network_profile_service)
_profile_page.setup(network_profile_service, experience)
_players_page.setup(network_player_list)
_catalog_logbook.setup(collection_log, inventory, catalog)
_the_net_page.setup(player_jobs, world_time)
_network_mail_service.unread_count_changed.connect(
_on_mail_unread_count_changed
)
@ -898,6 +911,7 @@ func _show_section_immediate(section: Section) -> void:
_tackle_box_page.visible = section == Section.TACKLE_BOX
_inventory_sub_tabs.visible = _is_inventory_section(section)
_logbook_page.visible = section == Section.LOGBOOK
_the_net_page.visible = section == Section.NET
_mail_page.visible = section == Section.MAIL
_profile_page.visible = section == Section.PROFILE
_players_page.visible = section == Section.PLAYERS
@ -922,6 +936,10 @@ func _show_section_immediate(section: Section) -> void:
_catalog_logbook.deactivate()
else:
_catalog_logbook.activate()
if section == Section.NET:
_the_net_page.activate()
else:
_the_net_page.deactivate()
if section == Section.MAIL:
_mail_page.activate()
else:
@ -940,6 +958,7 @@ func _show_section_immediate(section: Section) -> void:
_tackle_sub_tab.set_selected(section == Section.TACKLE_BOX)
_refresh_inventory_organizer_tabs()
_logbook_tab.button_pressed = section == Section.LOGBOOK
_the_net_tab.button_pressed = section == Section.NET
_mail_tab.button_pressed = section == Section.MAIL
_profile_tab.button_pressed = section == Section.PROFILE
_players_tab.button_pressed = section == Section.PLAYERS
@ -1000,6 +1019,8 @@ func _focus_current_section() -> void:
_tackle_sub_tab.grab_focus()
elif _current_section == Section.LOGBOOK:
_catalog_logbook.focus_initial()
elif _current_section == Section.NET:
_the_net_page.focus_initial()
elif _current_section == Section.MAIL:
_mail_tab.grab_focus()
elif _current_section == Section.PLAYERS:
@ -1032,6 +1053,7 @@ func _configure_navigation_focus() -> void:
var navigation: Array[BubbleButtonType] = [
_inventory_tab,
_logbook_tab,
_the_net_tab,
_mail_tab,
_profile_tab,
_players_tab,
@ -1122,6 +1144,8 @@ func _configure_active_page_focus() -> void:
_bait_filter.grab_focus()
Section.LOGBOOK:
_catalog_logbook.focus_initial()
Section.NET:
_the_net_page.focus_initial()
Section.MAIL:
_mail_page.activate()
Section.PROFILE:
@ -1349,6 +1373,7 @@ func _apply_navigation_styles() -> void:
for bubble: BubbleButtonType in [
_inventory_tab,
_logbook_tab,
_the_net_tab,
_mail_tab,
_profile_tab,
_players_tab,
@ -1380,6 +1405,7 @@ func _apply_navigation_selection_presentation() -> void:
for bubble: BubbleButtonType in [
_inventory_tab,
_logbook_tab,
_the_net_tab,
_mail_tab,
_profile_tab,
_players_tab,
@ -1465,6 +1491,7 @@ func _on_mail_unread_count_changed(count: int) -> void:
func _set_navigation_target(section: Section) -> void:
_inventory_tab.button_pressed = _is_inventory_section(section)
_logbook_tab.button_pressed = section == Section.LOGBOOK
_the_net_tab.button_pressed = section == Section.NET
_mail_tab.button_pressed = section == Section.MAIL
_profile_tab.button_pressed = section == Section.PROFILE
_players_tab.button_pressed = section == Section.PLAYERS
@ -1643,14 +1670,18 @@ func _update_shell_layout() -> void:
_logbook_page.size = reference_size
_logbook_page.position = Vector2.ZERO
_logbook_rest_position = Vector2.ZERO
_the_net_page.set_anchors_preset(Control.PRESET_TOP_LEFT)
_the_net_page.size = reference_size
_the_net_page.position = Vector2.ZERO
_the_net_rest_position = Vector2.ZERO
_mail_page.set_anchors_preset(Control.PRESET_TOP_LEFT)
_mail_page.size = reference_size
_mail_page.position = Vector2.ZERO
_mail_rest_position = Vector2.ZERO
_profile_page.set_anchors_preset(Control.PRESET_TOP_LEFT)
_profile_page.position = Vector2(42.0, 104.0)
_profile_page.size = Vector2(1196.0, 608.0)
_profile_rest_position = _profile_page.position
_profile_page.position = Vector2.ZERO
_profile_page.size = reference_size
_profile_rest_position = Vector2.ZERO
_players_page.set_anchors_preset(Control.PRESET_TOP_LEFT)
_players_page.position = Vector2.ZERO
_players_page.size = reference_size
@ -1673,6 +1704,7 @@ func _update_shell_layout() -> void:
_bag_page.position = _bag_rest_position
_tackle_box_page.position = _tackle_rest_position
_logbook_page.position = _logbook_rest_position
_the_net_page.position = _the_net_rest_position
_mail_page.position = _mail_rest_position
_profile_page.position = _profile_rest_position
_players_page.position = Vector2.ZERO
@ -1680,6 +1712,7 @@ func _update_shell_layout() -> void:
_bag_page.modulate.a = 1.0
_tackle_box_page.modulate.a = 1.0
_logbook_page.modulate.a = 1.0
_the_net_page.modulate.a = 1.0
_mail_page.modulate.a = 1.0
_profile_page.modulate.a = 1.0
_players_page.modulate.a = 1.0
@ -1805,6 +1838,8 @@ func _begin_menu_exit(reason: CloseReason, restore_controls: bool) -> void:
_settle_inventory_tabs_for_close()
if _current_section == Section.LOGBOOK:
_catalog_logbook.deactivate()
elif _current_section == Section.NET:
_the_net_page.deactivate()
get_viewport().gui_release_focus()
var generation: int = _transition_generation
var closing_generation: int = _menu_generation
@ -1851,6 +1886,7 @@ func _finish_close(
_cancel_presentation_tween()
_cancel_page_tween()
_cancel_logbook_page_transition(true)
_the_net_page.deactivate()
_transitioning = false
_page_transitioning = false
_bag_drag_active = false
@ -1882,6 +1918,8 @@ func _get_section_root(section: Section) -> Control:
return _tackle_box_page
Section.LOGBOOK:
return _logbook_page
Section.NET:
return _the_net_page
Section.MAIL:
return _mail_page
Section.PROFILE:
@ -1900,6 +1938,8 @@ func _get_section_rest_position(section: Section) -> Vector2:
return _tackle_rest_position
Section.LOGBOOK:
return _logbook_rest_position
Section.NET:
return _the_net_rest_position
Section.MAIL:
return _mail_rest_position
Section.PROFILE:
@ -2002,6 +2042,7 @@ func _set_shell_interactive(interactive: bool) -> void:
for bubble: BubbleButtonType in [
_inventory_tab,
_logbook_tab,
_the_net_tab,
_mail_tab,
_profile_tab,
_players_tab,
@ -2058,6 +2099,9 @@ func _set_content_interactive(interactive: bool) -> void:
_catalog_logbook.set_interactive(
interactive and _current_section == Section.LOGBOOK
)
_the_net_page.set_interactive(
interactive and _current_section == Section.NET
)
_mail_page.set_interactive(
interactive and _current_section == Section.MAIL
)
@ -2133,6 +2177,7 @@ func _reset_page_transition_visuals() -> void:
Section.BAG,
Section.TACKLE_BOX,
Section.LOGBOOK,
Section.NET,
Section.MAIL,
Section.PROFILE,
Section.PLAYERS,

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=25 format=3]
[gd_scene load_steps=26 format=3]
[ext_resource type="Script" path="res://ui/player_menu.gd" id="1_menu"]
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
@ -23,6 +23,7 @@
[ext_resource type="PackedScene" path="res://ui/logbook_page.tscn" id="21_logbook_page"]
[ext_resource type="Script" path="res://ui/components/inventory_notepad.gd" id="22_inventory_notepad"]
[ext_resource type="Script" path="res://ui/components/organizer_tab.gd" id="23_organizer_tab"]
[ext_resource type="PackedScene" path="res://ui/the_net_page.tscn" id="24_the_net_page"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_collection"]
@ -722,6 +723,10 @@ mouse_filter = 1
[node name="CatalogLogbook" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage" instance=ExtResource("21_logbook_page")]
unique_name_in_owner = true
[node name="TheNetPage" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot" instance=ExtResource("24_the_net_page")]
unique_name_in_owner = true
visible = false
[node name="MailPage" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot"]
unique_name_in_owner = true
layout_mode = 0
@ -734,9 +739,8 @@ script = ExtResource("18_mail_page")
unique_name_in_owner = true
visible = false
layout_mode = 0
offset_top = 112.0
offset_right = 1280.0
offset_bottom = 710.0
offset_bottom = 720.0
[node name="PlayersPage" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot"]
visible = false
@ -1390,15 +1394,16 @@ compact_reference_size = Vector2(840, 100)
unique_name_in_owner = true
layout_mode = 0
toggle_mode = true
text = "inventory"
text = "stuff"
accessibility_name = "stuff"
script = ExtResource("6_bubble")
profile = ExtResource("4_profile")
neutral_size = Vector2(112, 108)
desktop_anchor = Vector2(70, 60)
compact_anchor = Vector2(70, 73.3333)
desktop_anchor = Vector2(60, 60)
compact_anchor = Vector2(60, 73.3333)
compact_minimum_size = Vector2(84, 82)
minimum_font_size = 20
maximum_font_size = 22
minimum_font_size = 25
maximum_font_size = 25
horizontal_amplitude = 1.2
vertical_amplitude = 2.4
motion_period = 5.6
@ -1414,8 +1419,8 @@ text = "logbook"
script = ExtResource("6_bubble")
profile = ExtResource("4_profile")
neutral_size = Vector2(112, 108)
desktop_anchor = Vector2(210, 60)
compact_anchor = Vector2(210, 73.3333)
desktop_anchor = Vector2(180, 60)
compact_anchor = Vector2(180, 73.3333)
compact_minimum_size = Vector2(84, 82)
minimum_font_size = 25
maximum_font_size = 25
@ -1426,6 +1431,27 @@ motion_phase = 2.85
deformation_amplitude = 0.012
deformation_period = 6.7
[node name="TheNetTab" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/NavigationCluster"]
unique_name_in_owner = true
layout_mode = 0
toggle_mode = true
text = "fishnet"
accessibility_name = "net"
script = ExtResource("6_bubble")
profile = ExtResource("4_profile")
neutral_size = Vector2(112, 108)
desktop_anchor = Vector2(300, 60)
compact_anchor = Vector2(300, 73.3333)
compact_minimum_size = Vector2(84, 82)
minimum_font_size = 25
maximum_font_size = 25
horizontal_amplitude = 1.2
vertical_amplitude = 2.3
motion_period = 5.75
motion_phase = 3.2
deformation_amplitude = 0.011
deformation_period = 6.45
[node name="MailTab" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/NavigationCluster"]
unique_name_in_owner = true
layout_mode = 0
@ -1434,8 +1460,8 @@ text = "mail"
script = ExtResource("6_bubble")
profile = ExtResource("4_profile")
neutral_size = Vector2(112, 108)
desktop_anchor = Vector2(350, 60)
compact_anchor = Vector2(350, 73.3333)
desktop_anchor = Vector2(420, 60)
compact_anchor = Vector2(420, 73.3333)
compact_minimum_size = Vector2(84, 82)
minimum_font_size = 25
maximum_font_size = 25
@ -1470,8 +1496,8 @@ text = "profile"
script = ExtResource("6_bubble")
profile = ExtResource("4_profile")
neutral_size = Vector2(112, 108)
desktop_anchor = Vector2(490, 60)
compact_anchor = Vector2(490, 73.3333)
desktop_anchor = Vector2(540, 60)
compact_anchor = Vector2(540, 73.3333)
compact_minimum_size = Vector2(84, 82)
minimum_font_size = 23
maximum_font_size = 25
@ -1486,12 +1512,13 @@ deformation_period = 6.5
unique_name_in_owner = true
layout_mode = 0
toggle_mode = true
text = "players"
text = "online"
accessibility_name = "online"
script = ExtResource("6_bubble")
profile = ExtResource("4_profile")
neutral_size = Vector2(112, 108)
desktop_anchor = Vector2(630, 60)
compact_anchor = Vector2(630, 73.3333)
desktop_anchor = Vector2(660, 60)
compact_anchor = Vector2(660, 73.3333)
compact_minimum_size = Vector2(84, 82)
minimum_font_size = 22
maximum_font_size = 24
@ -1509,8 +1536,8 @@ text = "close"
script = ExtResource("6_bubble")
profile = ExtResource("4_profile")
neutral_size = Vector2(96, 94)
desktop_anchor = Vector2(770, 57)
compact_anchor = Vector2(770, 73.3333)
desktop_anchor = Vector2(780, 57)
compact_anchor = Vector2(780, 73.3333)
compact_minimum_size = Vector2(76, 74)
minimum_font_size = 23
maximum_font_size = 23

View file

@ -2,7 +2,7 @@ class_name PlayersPage
extends Control
var _service: NetworkPlayerListService
var _header: Label
var _count_label: Label
var _tabs: HBoxContainer
var _list: VBoxContainer
var _status: Label
@ -36,23 +36,17 @@ func deactivate() -> void:
func _build() -> void:
var paper := PanelContainer.new()
paper.position = Vector2(58, 128)
paper.size = Vector2(1164, 538)
add_child(paper)
paper.add_theme_stylebox_override("panel", UtilityPageStyle.panel_style())
var margin: MarginContainer = UtilityPageStyle.build_laptop_screen(self)
var root := VBoxContainer.new()
root.add_theme_constant_override("separation", 10)
paper.add_child(root)
_header = Label.new()
_header.add_theme_font_size_override("font_size", 27)
_header.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
)
root.add_child(_header)
root.add_theme_constant_override("separation", 9)
margin.add_child(root)
var tab_row := HBoxContainer.new()
tab_row.add_theme_constant_override("separation", 16)
root.add_child(tab_row)
_tabs = HBoxContainer.new()
_tabs.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_tabs.add_theme_constant_override("separation", 10)
root.add_child(_tabs)
tab_row.add_child(_tabs)
for index: int in 3:
var button := Button.new()
button.text = ["players", "relationships", "banned"][index]
@ -60,12 +54,20 @@ func _build() -> void:
button.pressed.connect(_select_tab.bind(index))
UtilityPageStyle.apply_ocean_button(button)
_tabs.add_child(button)
_count_label = Label.new()
_count_label.add_theme_font_size_override("font_size", 17)
_count_label.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
)
_count_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
tab_row.add_child(_count_label)
var scroll := ScrollContainer.new()
scroll.custom_minimum_size = Vector2(0, 392)
scroll.custom_minimum_size = Vector2(0, 310)
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
root.add_child(scroll)
_list = VBoxContainer.new()
_list.custom_minimum_size = Vector2(1080, 0)
_list.custom_minimum_size = Vector2(1032, 0)
_list.add_theme_constant_override("separation", 7)
scroll.add_child(_list)
_status = Label.new()
@ -86,7 +88,7 @@ func _select_tab(index: int) -> void:
func _refresh() -> void:
if _service == null or _list == null:
return
_header.text = "Players\n%d / %d connected" % [
_count_label.text = "%d / %d connected" % [
_service.get_connected_count(), _service.get_max_players(),
]
for index: int in _tabs.get_child_count():
@ -168,7 +170,6 @@ func _build_active_rows() -> void:
ban.pressed.connect(_confirm_ban.bind(entry))
UtilityPageStyle.apply_ocean_button(ban)
row.add_child(ban)
_list.add_child(row)
func _build_session_artwork_controls() -> void:
@ -195,7 +196,6 @@ func _build_session_artwork_controls() -> void:
)
UtilityPageStyle.apply_ocean_button(reset)
row.add_child(reset)
_list.add_child(row)
func _build_relationship_rows() -> void:
@ -234,7 +234,6 @@ func _build_relationship_rows() -> void:
)
UtilityPageStyle.apply_ocean_button(unmute)
row.add_child(unmute)
_list.add_child(row)
func _build_ban_rows() -> void:
@ -262,14 +261,18 @@ func _build_ban_rows() -> void:
unban.pressed.connect(_confirm_unban.bind(fingerprint))
UtilityPageStyle.apply_ocean_button(unban)
row.add_child(unban)
_list.add_child(row)
func _make_row() -> HBoxContainer:
var panel := PanelContainer.new()
panel.add_theme_stylebox_override(
"panel", UtilityPageStyle.row_style(false)
)
_list.add_child(panel)
var row := HBoxContainer.new()
row.custom_minimum_size.y = 58
row.add_theme_constant_override("separation", 8)
row.add_theme_constant_override("outline_size", 1)
panel.add_child(row)
return row

View file

@ -4,6 +4,7 @@ extends Control
const CHECK_DEBOUNCE_SECONDS: float = 0.4
var _service: NetworkProfileService
var _experience: PlayerExperience
var _draft_name: String = ""
var _draft_appearance: Dictionary = {}
var _persisted_name: String = ""
@ -25,6 +26,9 @@ var _confirmation_label: Label
var _confirmation_confirm: Button
var _confirmation_action: String = ""
var _debounce: Timer
var _experience_level: Label
var _experience_progress: ProgressBar
var _experience_value: Label
func _ready() -> void:
@ -37,12 +41,24 @@ func _ready() -> void:
add_child(_debounce)
func setup(service: NetworkProfileService) -> void:
func setup(
service: NetworkProfileService,
experience: PlayerExperience,
) -> void:
_service = service
_experience = experience
if not _service.conflict_result.is_connected(_on_conflict_result):
_service.conflict_result.connect(_on_conflict_result)
_service.apply_finished.connect(_on_apply_finished)
if (
_experience != null
and not _experience.experience_changed.is_connected(
_on_experience_changed
)
):
_experience.experience_changed.connect(_on_experience_changed)
_load_persisted()
_refresh_experience()
var identity_value := find_child("IdentityFingerprint", true, false) as Label
if identity_value != null:
var fingerprint := _service.get_identity_fingerprint()
@ -108,134 +124,200 @@ func has_modal_confirmation() -> bool:
func _build_ui() -> void:
var paper := PanelContainer.new()
paper.set_anchors_preset(Control.PRESET_FULL_RECT)
paper.offset_left = 64.0
paper.offset_top = 28.0
paper.offset_right = -64.0
paper.offset_bottom = -28.0
paper.add_theme_stylebox_override(
"panel", UtilityPageStyle.panel_style()
)
add_child(paper)
UtilityPageStyle.apply_page(self)
var margin := MarginContainer.new()
for side: String in ["left", "right", "top", "bottom"]:
margin.add_theme_constant_override("margin_%s" % side, 18)
paper.add_child(margin)
var margin: MarginContainer = UtilityPageStyle.build_laptop_screen(self)
var layout := VBoxContainer.new()
layout.add_theme_constant_override("separation", 5)
layout.add_theme_constant_override("separation", 8)
margin.add_child(layout)
var heading := Label.new()
heading.text = "Player Profile"
heading.add_theme_font_size_override("font_size", 25)
heading.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
)
layout.add_child(heading)
var name_row := HBoxContainer.new()
name_row.add_theme_constant_override("separation", 10)
layout.add_child(name_row)
var name_stack := VBoxContainer.new()
name_stack.size_flags_horizontal = Control.SIZE_EXPAND_FILL
name_row.add_child(name_stack)
var account_row := HBoxContainer.new()
account_row.add_theme_constant_override("separation", 14)
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_row.add_child(account_stack)
var name_line := HBoxContainer.new()
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(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
)
name_stack.add_child(name_label)
name_line.add_child(name_label)
_name_edit = LineEdit.new()
_name_edit.max_length = NetworkProtocol.MAX_DISPLAY_NAME_LENGTH
_name_edit.placeholder_text = "Player"
_name_edit.custom_minimum_size = Vector2(300, 40)
_name_edit.custom_minimum_size = Vector2(280, 34)
UtilityPageStyle.apply_ocean_line_edit(_name_edit)
_name_edit.add_theme_stylebox_override(
"normal", UtilityPageStyle.ocean_button_style(
UtilityPageStyle.OCEAN_PANEL_MID
)
)
_name_edit.text_changed.connect(_on_name_changed)
name_stack.add_child(_name_edit)
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
)
name_stack.add_child(helper)
_apply_button = Button.new()
_apply_button.text = "apply"
_apply_button.custom_minimum_size = Vector2(92, 40)
UtilityPageStyle.apply_ocean_button(_apply_button)
_apply_button.pressed.connect(_apply)
name_row.add_child(_apply_button)
_revert_button = Button.new()
_revert_button.text = "revert"
_revert_button.custom_minimum_size = Vector2(88, 40)
UtilityPageStyle.apply_ocean_button(_revert_button)
_revert_button.pressed.connect(_revert)
name_row.add_child(_revert_button)
var defaults_button := Button.new()
defaults_button.text = "defaults"
defaults_button.custom_minimum_size = Vector2(88, 40)
UtilityPageStyle.apply_ocean_button(defaults_button)
defaults_button.pressed.connect(_show_confirmation.bind("defaults"))
name_row.add_child(defaults_button)
account_stack.add_child(helper)
_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
)
layout.add_child(_name_status)
account_stack.add_child(_name_status)
_suggestions = HBoxContainer.new()
_suggestions.add_theme_constant_override("separation", 8)
layout.add_child(_suggestions)
_suggestions.add_theme_constant_override("separation", 6)
account_stack.add_child(_suggestions)
var identity_value := Label.new()
identity_value.name = "IdentityFingerprint"
identity_value.text = "Identity • Stored on this device"
identity_value.text = "identity • stored on this device"
identity_value.tooltip_text = (
"This identity helps other players recognize you between sessions."
)
identity_value.add_theme_font_size_override("font_size", 12)
identity_value.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
)
layout.add_child(identity_value)
account_stack.add_child(identity_value)
var experience_row := HBoxContainer.new()
experience_row.add_theme_constant_override("separation", 9)
account_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)
_experience_level.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
)
experience_row.add_child(_experience_level)
_experience_progress = ProgressBar.new()
_experience_progress.custom_minimum_size = Vector2(210.0, 14.0)
_experience_progress.max_value = 1.0
_experience_progress.show_percentage = false
_experience_progress.add_theme_stylebox_override(
"background", UtilityPageStyle.rounded_style(
UtilityPageStyle.OCEAN_PANEL_MID, 7
)
)
_experience_progress.add_theme_stylebox_override(
"fill", UtilityPageStyle.rounded_style(
UtilityPageStyle.OCEAN_SELECTED, 7
)
)
experience_row.add_child(_experience_progress)
_experience_value = Label.new()
_experience_value.add_theme_font_size_override("font_size", 14)
_experience_value.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
)
experience_row.add_child(_experience_value)
var actions := HBoxContainer.new()
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)
_apply_button = Button.new()
_apply_button.text = "apply"
_apply_button.custom_minimum_size.x = 72.0
UtilityPageStyle.apply_compact_ocean_button(_apply_button)
_apply_button.pressed.connect(_apply)
actions.add_child(_apply_button)
_revert_button = Button.new()
_revert_button.text = "revert"
_revert_button.custom_minimum_size.x = 76.0
UtilityPageStyle.apply_compact_ocean_button(_revert_button)
_revert_button.pressed.connect(_revert)
actions.add_child(_revert_button)
var defaults_button := Button.new()
defaults_button.text = "defaults"
defaults_button.custom_minimum_size.x = 82.0
UtilityPageStyle.apply_compact_ocean_button(defaults_button)
defaults_button.pressed.connect(_show_confirmation.bind("defaults"))
actions.add_child(defaults_button)
var body_panel := PanelContainer.new()
body_panel.size_flags_vertical = Control.SIZE_EXPAND_FILL
body_panel.add_theme_stylebox_override(
"panel", UtilityPageStyle.row_style(false)
)
layout.add_child(body_panel)
var body_margin := MarginContainer.new()
body_margin.add_theme_constant_override("margin_left", 14)
body_margin.add_theme_constant_override("margin_top", 10)
body_margin.add_theme_constant_override("margin_right", 14)
body_margin.add_theme_constant_override("margin_bottom", 10)
body_panel.add_child(body_margin)
var body := HBoxContainer.new()
body.size_flags_vertical = Control.SIZE_EXPAND_FILL
body.add_theme_constant_override("separation", 12)
layout.add_child(body)
body.alignment = BoxContainer.ALIGNMENT_BEGIN
body.add_theme_constant_override("separation", 16)
body_margin.add_child(body)
_category_list = VBoxContainer.new()
_category_list.custom_minimum_size = Vector2(140, 0)
_category_list.custom_minimum_size = Vector2(120, 0)
_category_list.add_theme_constant_override("separation", 5)
body.add_child(_category_list)
_option_list = VBoxContainer.new()
_option_list.custom_minimum_size = Vector2(190, 0)
_option_list.custom_minimum_size = Vector2(170, 0)
_option_list.add_theme_constant_override("separation", 5)
body.add_child(_option_list)
var body_spacer := Control.new()
body_spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
body.add_child(body_spacer)
var preview_stack := VBoxContainer.new()
preview_stack.size_flags_horizontal = Control.SIZE_EXPAND_FILL
preview_stack.custom_minimum_size.x = 180.0
preview_stack.alignment = BoxContainer.ALIGNMENT_CENTER
preview_stack.add_theme_constant_override("separation", 7)
body.add_child(preview_stack)
var preview_frame := PanelContainer.new()
preview_frame.custom_minimum_size = Vector2(180.0, 220.0)
preview_frame.add_theme_stylebox_override(
"panel", UtilityPageStyle.rounded_style(
UtilityPageStyle.OCEAN_FIELD, 18
)
)
preview_stack.add_child(preview_frame)
_preview = preload("res://ui/profile_preview.tscn").instantiate()
preview_stack.add_child(_preview)
var preview_actions := HBoxContainer.new()
_preview.custom_minimum_size = Vector2(180.0, 220.0)
preview_frame.add_child(_preview)
var preview_actions := VBoxContainer.new()
preview_actions.alignment = BoxContainer.ALIGNMENT_CENTER
preview_actions.add_theme_constant_override("separation", 10)
preview_actions.add_theme_constant_override("separation", 4)
preview_stack.add_child(preview_actions)
var preview_note := Label.new()
preview_note.text = "Current player • drag or use left / right"
preview_note.text = "drag or use left / right"
preview_note.custom_minimum_size.x = 180.0
preview_note.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
preview_note.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
preview_note.add_theme_font_size_override("font_size", 12)
preview_note.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
)
preview_actions.add_child(preview_note)
var reset_view := Button.new()
reset_view.text = "reset view"
reset_view.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
reset_view.pressed.connect(_preview.reset_view)
UtilityPageStyle.apply_ocean_button(reset_view)
UtilityPageStyle.apply_compact_ocean_button(reset_view)
preview_actions.add_child(reset_view)
_discard_confirmation = PanelContainer.new()
_discard_confirmation.visible = false
_discard_confirmation.set_anchors_and_offsets_preset(Control.PRESET_CENTER)
_discard_confirmation.custom_minimum_size = Vector2(430, 190)
_discard_confirmation.add_theme_stylebox_override(
"panel", UtilityPageStyle.rounded_style(
UtilityPageStyle.OCEAN_PANEL_MID, 14
)
)
add_child(_discard_confirmation)
var confirm_stack := VBoxContainer.new()
confirm_stack.alignment = BoxContainer.ALIGNMENT_CENTER
@ -249,6 +331,7 @@ func _build_ui() -> void:
confirm_stack.add_child(confirm_buttons)
_confirmation_confirm = Button.new()
_confirmation_confirm.pressed.connect(_confirm_pending_action)
UtilityPageStyle.apply_ocean_button(_confirmation_confirm)
confirm_buttons.add_child(_confirmation_confirm)
var keep := Button.new()
keep.name = "KeepEditing"
@ -258,6 +341,7 @@ func _build_ui() -> void:
_discard_confirmation.visible = false
_name_edit.grab_focus()
)
UtilityPageStyle.apply_ocean_button(keep)
confirm_buttons.add_child(keep)
_build_categories()
@ -270,10 +354,10 @@ func _build_categories() -> void:
var button := Button.new()
button.text = CharacterCustomizationCatalog.category_label(category_id)
button.toggle_mode = true
button.custom_minimum_size = Vector2(0, 34)
button.custom_minimum_size.x = 120.0
button.button_pressed = category_id == _category_id
button.pressed.connect(_select_category.bind(category_id))
UtilityPageStyle.apply_ocean_button(button)
UtilityPageStyle.apply_compact_ocean_button(button)
_category_list.add_child(button)
_refresh_options()
@ -294,7 +378,7 @@ func _refresh_options() -> void:
child.queue_free()
var title := Label.new()
title.text = CharacterCustomizationCatalog.category_label(_category_id)
title.add_theme_font_size_override("font_size", 22)
title.add_theme_font_size_override("font_size", 18)
title.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
)
@ -311,10 +395,10 @@ func _refresh_options() -> void:
var button := Button.new()
button.text = str(option["label"])
button.toggle_mode = true
button.custom_minimum_size = Vector2(0, 34)
button.custom_minimum_size.x = 170.0
button.button_pressed = _draft_appearance.get(_category_id) == option_id
button.pressed.connect(_select_option.bind(_category_id, option_id))
UtilityPageStyle.apply_ocean_button(button)
UtilityPageStyle.apply_compact_ocean_button(button)
_option_list.add_child(button)
@ -358,6 +442,7 @@ func _on_conflict_result(
var button := Button.new()
button.text = suggestion
button.pressed.connect(_use_suggestion.bind(suggestion))
UtilityPageStyle.apply_compact_ocean_button(button)
_suggestions.add_child(button)
var anyway := Button.new()
anyway.text = "use anyway"
@ -365,9 +450,33 @@ func _on_conflict_result(
_allow_duplicate = true
_name_status.text = "Duplicate name allowed for this apply."
)
UtilityPageStyle.apply_compact_ocean_button(anyway)
_suggestions.add_child(anyway)
func _on_experience_changed(_total_experience: int, _level: int) -> void:
_refresh_experience()
func _refresh_experience() -> void:
if _experience_level == null:
return
if _experience == null:
_experience_level.text = "level 1"
_experience_progress.value = 0.0
_experience_value.text = "0 / 100 xp"
return
var level: int = _experience.get_level()
var current: int = _experience.get_experience_in_level()
var required: int = _experience.get_experience_for_next_level()
_experience_level.text = "level %d" % level
_experience_progress.value = _experience.get_level_progress()
_experience_value.text = "%d / %d xp" % [current, required]
_experience_value.tooltip_text = "%d total xp" % (
_experience.get_total_experience()
)
func _use_suggestion(value: String) -> void:
_name_edit.text = value
_draft_name = value

View file

@ -18,7 +18,7 @@ stretch = true
script = ExtResource("1")
[node name="Viewport" type="SubViewport" parent="."]
transparent_bg = false
transparent_bg = true
size = Vector2i(320, 330)
render_target_update_mode = 4
world_3d = SubResource("PreviewWorld")

489
ui/the_net_page.gd Normal file
View file

@ -0,0 +1,489 @@
class_name TheNetPage
extends Control
enum View {
DAILY,
LIFETIME,
PAYMENTS,
}
var _jobs: PlayerJobService
var _world_time: WorldTimeService
var _header: Label
var _refresh_label: Label
var _forecast_list: HBoxContainer
var _tabs: HBoxContainer
var _list: VBoxContainer
var _status: Label
var _current_view: View = View.DAILY
var _forecast_start_index: int = -1
var _active: bool = false
var _interactive: bool = false
func _ready() -> void:
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
UtilityPageStyle.apply_page(self)
_build_laptop()
set_process(false)
func setup(jobs: PlayerJobService, world_time: WorldTimeService) -> void:
_jobs = jobs
_world_time = world_time
_jobs.changed.connect(_refresh)
_jobs.status_changed.connect(_on_status_changed)
_refresh()
func activate() -> void:
_active = true
set_process(true)
_refresh()
focus_initial()
func deactivate() -> void:
_active = false
set_process(false)
set_interactive(false)
_status.text = ""
func set_interactive(interactive: bool) -> void:
_interactive = interactive
mouse_filter = (
Control.MOUSE_FILTER_PASS
if interactive
else Control.MOUSE_FILTER_IGNORE
)
if _tabs != null:
for child: Node in _tabs.get_children():
var button := child as Button
if button != null:
button.focus_mode = (
Control.FOCUS_ALL
if interactive
else Control.FOCUS_NONE
)
button.mouse_filter = (
Control.MOUSE_FILTER_STOP
if interactive
else Control.MOUSE_FILTER_IGNORE
)
_refresh()
func focus_initial() -> void:
if _interactive and _tabs != null and _tabs.get_child_count() > 0:
var button := _tabs.get_child(int(_current_view)) as Button
if button != null:
button.grab_focus()
func _process(_delta: float) -> void:
if _active and _jobs != null:
_refresh_label.text = _daily_refresh_text()
_refresh_forecast(false)
func _build_laptop() -> void:
var margin: MarginContainer = UtilityPageStyle.build_laptop_screen(self)
var layout := VBoxContainer.new()
layout.add_theme_constant_override("separation", 9)
margin.add_child(layout)
var title_row := HBoxContainer.new()
title_row.add_theme_constant_override("separation", 16)
layout.add_child(title_row)
var header_left := VBoxContainer.new()
header_left.size_flags_horizontal = Control.SIZE_EXPAND_FILL
header_left.add_theme_constant_override("separation", 0)
title_row.add_child(header_left)
_header = Label.new()
_header.text = "fishnet"
_header.add_theme_font_size_override("font_size", 32)
_header.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
)
header_left.add_child(_header)
var header_spacer := Control.new()
header_spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL
header_spacer.mouse_filter = Control.MOUSE_FILTER_IGNORE
header_left.add_child(header_spacer)
_tabs = HBoxContainer.new()
_tabs.add_theme_constant_override("separation", 8)
header_left.add_child(_tabs)
for view_index: int in 3:
var tab := Button.new()
tab.text = ["daily jobs", "lifetime jobs", "payments"][view_index]
tab.toggle_mode = true
tab.pressed.connect(_select_view.bind(view_index as View))
UtilityPageStyle.apply_ocean_button(tab)
_tabs.add_child(tab)
var refresh_alignment_spacer := Control.new()
refresh_alignment_spacer.custom_minimum_size.y = 20.0
refresh_alignment_spacer.mouse_filter = Control.MOUSE_FILTER_IGNORE
header_left.add_child(refresh_alignment_spacer)
var forecast_column := VBoxContainer.new()
forecast_column.custom_minimum_size.x = 292.0
forecast_column.size_flags_horizontal = Control.SIZE_SHRINK_END
forecast_column.add_theme_constant_override("separation", 3)
title_row.add_child(forecast_column)
var forecast_panel := PanelContainer.new()
forecast_panel.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
forecast_panel.add_theme_stylebox_override(
"panel", UtilityPageStyle.row_style(false)
)
forecast_column.add_child(forecast_panel)
var forecast_margin := MarginContainer.new()
forecast_margin.add_theme_constant_override("margin_left", 10)
forecast_margin.add_theme_constant_override("margin_top", 8)
forecast_margin.add_theme_constant_override("margin_right", 10)
forecast_margin.add_theme_constant_override("margin_bottom", 8)
forecast_panel.add_child(forecast_margin)
var forecast_stack := VBoxContainer.new()
forecast_stack.add_theme_constant_override("separation", 7)
forecast_margin.add_child(forecast_stack)
var forecast_heading := Label.new()
forecast_heading.text = "weather forecast"
forecast_heading.add_theme_font_size_override("font_size", 20)
forecast_heading.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
)
forecast_stack.add_child(forecast_heading)
_forecast_list = HBoxContainer.new()
_forecast_list.alignment = BoxContainer.ALIGNMENT_CENTER
_forecast_list.add_theme_constant_override("separation", 6)
forecast_stack.add_child(_forecast_list)
_refresh_label = Label.new()
_refresh_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
_refresh_label.add_theme_font_size_override("font_size", 14)
_refresh_label.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
)
forecast_column.add_child(_refresh_label)
var jobs_column := VBoxContainer.new()
jobs_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
jobs_column.size_flags_vertical = Control.SIZE_EXPAND_FILL
jobs_column.add_theme_constant_override("separation", 8)
layout.add_child(jobs_column)
var scroll := ScrollContainer.new()
scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
jobs_column.add_child(scroll)
_list = VBoxContainer.new()
_list.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_list.add_theme_constant_override("separation", 7)
scroll.add_child(_list)
_status = Label.new()
_status.add_theme_font_size_override("font_size", 16)
_status.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
)
jobs_column.add_child(_status)
func _select_view(view: View) -> void:
_current_view = view
_refresh()
focus_initial()
func _refresh() -> void:
if _jobs == null or _list == null:
return
_refresh_label.text = _daily_refresh_text()
_refresh_forecast(true)
for index: int in _tabs.get_child_count():
var tab := _tabs.get_child(index) as Button
if tab != null:
tab.button_pressed = index == int(_current_view)
for child: Node in _list.get_children():
_list.remove_child(child)
child.queue_free()
match _current_view:
View.DAILY:
_build_job_rows(_jobs.get_daily_jobs(), "no daily jobs available")
View.LIFETIME:
_build_job_rows(
_jobs.get_lifetime_jobs(), "all lifetime jobs complete"
)
View.PAYMENTS:
_build_payment_rows()
func _build_job_rows(jobs: Array[Dictionary], empty_text: String) -> void:
if jobs.is_empty():
_add_empty(empty_text)
return
for job: Dictionary in jobs:
var row := PanelContainer.new()
row.add_theme_stylebox_override(
"panel", UtilityPageStyle.row_style(false)
)
var content := HBoxContainer.new()
content.add_theme_constant_override("separation", 14)
row.add_child(content)
var text_column := VBoxContainer.new()
text_column.custom_minimum_size.x = 245.0
text_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
content.add_child(text_column)
var title := Label.new()
title.text = str(job.get("title", "job"))
title.add_theme_font_size_override("font_size", 20)
title.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
)
text_column.add_child(title)
var description := Label.new()
description.text = str(job.get("description", ""))
description.add_theme_font_size_override("font_size", 16)
description.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
)
text_column.add_child(description)
var target: int = int(job.get("target", 1))
var progress: int = int(job.get("progress", 0))
var progress_bar := ProgressBar.new()
progress_bar.custom_minimum_size = Vector2(100.0, 24.0)
progress_bar.max_value = float(target)
progress_bar.value = float(progress)
progress_bar.show_percentage = false
progress_bar.add_theme_stylebox_override(
"background", UtilityPageStyle.rounded_style(
UtilityPageStyle.OCEAN_PANEL_DEEP, 9
)
)
progress_bar.add_theme_stylebox_override(
"fill", UtilityPageStyle.rounded_style(
UtilityPageStyle.OCEAN_SELECTED, 9
)
)
content.add_child(progress_bar)
var count := Label.new()
count.custom_minimum_size.x = 54.0
count.text = "%d / %d" % [progress, target]
count.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
count.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
count.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
)
content.add_child(count)
var reward := Label.new()
reward.custom_minimum_size.x = 98.0
reward.text = "$%d%d xp" % [
int(job.get("fish_coin", 0)), int(job.get("experience", 0)),
]
reward.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
reward.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
)
content.add_child(reward)
var claim := Button.new()
claim.custom_minimum_size = Vector2(76.0, 36.0)
var claimable: bool = bool(job.get("claimable", false))
claim.text = "claim" if claimable else (
"done" if int(job.get("completed_count", 0)) > 0 else "working"
)
claim.disabled = not claimable or not _interactive
claim.focus_mode = (
Control.FOCUS_ALL if not claim.disabled else Control.FOCUS_NONE
)
claim.mouse_filter = (
Control.MOUSE_FILTER_STOP
if not claim.disabled
else Control.MOUSE_FILTER_IGNORE
)
var claim_id: String = str(
job.get("claim_id", job.get("id", ""))
)
claim.pressed.connect(_claim.bind(claim_id))
UtilityPageStyle.apply_compact_ocean_button(claim)
content.add_child(claim)
_list.add_child(row)
func _build_payment_rows() -> void:
var rewards: Array[Dictionary] = _jobs.get_pending_rewards()
if rewards.is_empty():
_add_empty("no completed payments waiting")
return
for reward: Dictionary in rewards:
var row := PanelContainer.new()
row.add_theme_stylebox_override(
"panel", UtilityPageStyle.row_style(false)
)
var content := HBoxContainer.new()
content.add_theme_constant_override("separation", 14)
row.add_child(content)
var label := Label.new()
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
label.text = "%s\n$%d%d xp" % [
str(reward.get("title", "completed job")),
int(reward.get("fish_coin", 0)),
int(reward.get("experience", 0)),
]
label.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
)
content.add_child(label)
var claim := Button.new()
claim.text = "claim"
claim.custom_minimum_size = Vector2(110.0, 42.0)
claim.disabled = not _interactive
claim.focus_mode = (
Control.FOCUS_ALL if _interactive else Control.FOCUS_NONE
)
claim.mouse_filter = (
Control.MOUSE_FILTER_STOP
if _interactive
else Control.MOUSE_FILTER_IGNORE
)
claim.pressed.connect(
_claim.bind(str(reward.get("claim_id", "")))
)
UtilityPageStyle.apply_ocean_button(claim)
content.add_child(claim)
_list.add_child(row)
func _claim(claim_id: String) -> void:
if not _jobs.claim(claim_id):
_status.text = "payment could not be completed"
_refresh()
func _add_empty(message: String) -> void:
var empty := Label.new()
empty.text = message
empty.custom_minimum_size = Vector2(0.0, 92.0)
empty.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
empty.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
empty.add_theme_font_size_override("font_size", 20)
empty.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
)
_list.add_child(empty)
func _refresh_forecast(force: bool) -> void:
if _forecast_list == null:
return
var current_index: int = _current_forecast_index()
if not force and current_index == _forecast_start_index:
return
_forecast_start_index = current_index
for child: Node in _forecast_list.get_children():
_forecast_list.remove_child(child)
child.queue_free()
if _jobs == null or _world_time == null:
_add_forecast_empty("forecast unavailable")
return
var forecast: Array[Dictionary] = _jobs.get_forecast()
if forecast.is_empty():
_add_forecast_empty("forecast unavailable")
return
current_index = clampi(current_index, 0, forecast.size() - 1)
var visible_count: int = mini(4, forecast.size())
for offset: int in visible_count:
var index: int = (current_index + offset) % forecast.size()
var entry: Dictionary = forecast[index]
var start_hour: float = float(entry.get("start_hour", 0.0))
var weather: WorldWeatherService.Weather = (
int(entry.get("weather", 0)) as WorldWeatherService.Weather
)
var slot := Control.new()
slot.custom_minimum_size = Vector2(63.0, 66.0)
slot.mouse_filter = Control.MOUSE_FILTER_IGNORE
_forecast_list.add_child(slot)
var icon := WeatherIcon.new()
icon.position = Vector2(1.0, 7.0)
icon.size = Vector2(56.0, 56.0)
icon.custom_minimum_size = Vector2(56.0, 56.0)
icon.set_weather(weather)
icon.set_nighttime(
WorldTimeService.phase_for_hour(start_hour)
== WorldTimeService.Phase.NIGHT
)
slot.add_child(icon)
var time_label := Label.new()
time_label.position = Vector2(34.0, 0.0)
time_label.size = Vector2(29.0, 18.0)
time_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
time_label.text = _forecast_exponent_time(start_hour)
time_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
time_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
time_label.add_theme_font_size_override("font_size", 10)
time_label.add_theme_constant_override("outline_size", 3)
time_label.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
)
time_label.add_theme_color_override(
"font_outline_color", UtilityPageStyle.OCEAN_PANEL_DEEP
)
slot.add_child(time_label)
func _current_forecast_index() -> int:
if _world_time == null:
return -1
var elapsed: float = fposmod(
_world_time.get_time_hours() - WorldTimeService.DAY_START_HOUR,
WorldTimeService.HOURS_PER_DAY,
)
return floori(elapsed / JobCatalog.WEATHER_SEGMENT_HOURS)
func _compact_clock_time(time_hours: float) -> String:
var normalized: float = fposmod(
time_hours, WorldTimeService.HOURS_PER_DAY
)
var total_minutes: int = floori(normalized * 60.0)
var hour_24: int = floori(float(total_minutes) / 60.0)
var minute: int = total_minutes % 60
var hour_12: int = hour_24 % 12
if hour_12 == 0:
hour_12 = 12
var suffix: String = "AM" if hour_24 < 12 else "PM"
if minute == 0:
return "%d%s" % [hour_12, suffix]
return "%d:%02d%s" % [hour_12, minute, suffix]
func _forecast_exponent_time(time_hours: float) -> String:
return _compact_clock_time(time_hours).to_lower()
func _daily_refresh_text() -> String:
if _jobs == null:
return "daily jobs unavailable"
var countdown: String = _jobs.get_time_until_refresh_text()
if countdown.begins_with("refreshes in "):
return "daily jobs refresh in %s" % countdown.trim_prefix(
"refreshes in "
)
return countdown
func _add_forecast_empty(message: String) -> void:
var label := Label.new()
label.text = message
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
label.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
)
_forecast_list.add_child(label)
func _on_status_changed(message: String) -> void:
_status.text = message

1
ui/the_net_page.gd.uid Normal file
View file

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

13
ui/the_net_page.tscn Normal file
View file

@ -0,0 +1,13 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://ui/the_net_page.gd" id="1_net"]
[node name="TheNetPage" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 1
script = ExtResource("1_net")

View file

@ -26,12 +26,65 @@ const GREEN: Color = Color("31594d")
const LIGHT_TEXT: Color = Color("f5eed9")
const DISABLED_TEXT: Color = Color(0.33, 0.36, 0.35, 0.72)
const MOTION_TWEEN_META: StringName = &"utility_page_motion_tween"
const LAPTOP_RECT: Rect2 = Rect2(66.0, 132.0, 1148.0, 520.0)
static func apply_page(root: Control) -> void:
root.add_theme_font_override("font", TuffyFont)
static func rounded_style(color: Color, radius: int) -> StyleBoxFlat:
var style := StyleBoxFlat.new()
style.bg_color = color
style.set_border_width_all(0)
style.set_corner_radius_all(radius)
return style
static func build_laptop_screen(
root: Control,
laptop_rect: Rect2 = LAPTOP_RECT,
) -> MarginContainer:
var laptop := PanelContainer.new()
laptop.position = laptop_rect.position
laptop.size = laptop_rect.size
laptop.add_theme_stylebox_override(
"panel", rounded_style(OCEAN_PANEL_MID, 24)
)
root.add_child(laptop)
var shell_margin := MarginContainer.new()
shell_margin.add_theme_constant_override("margin_left", 20)
shell_margin.add_theme_constant_override("margin_top", 18)
shell_margin.add_theme_constant_override("margin_right", 20)
shell_margin.add_theme_constant_override("margin_bottom", 24)
laptop.add_child(shell_margin)
var screen := PanelContainer.new()
screen.add_theme_stylebox_override(
"panel", rounded_style(OCEAN_FIELD, 16)
)
shell_margin.add_child(screen)
var content_margin := MarginContainer.new()
content_margin.add_theme_constant_override("margin_left", 24)
content_margin.add_theme_constant_override("margin_top", 18)
content_margin.add_theme_constant_override("margin_right", 24)
content_margin.add_theme_constant_override("margin_bottom", 18)
screen.add_child(content_margin)
var base := ColorRect.new()
base.position = Vector2(
laptop_rect.position.x + 82.0,
laptop_rect.end.y - 6.0,
)
base.size = Vector2(laptop_rect.size.x - 164.0, 14.0)
base.color = OCEAN_PANEL_MID
base.mouse_filter = Control.MOUSE_FILTER_IGNORE
root.add_child(base)
return content_margin
static func panel_style() -> StyleBoxFlat:
var style := StyleBoxFlat.new()
style.bg_color = OCEAN_PANEL_DEEP
@ -120,6 +173,22 @@ static func apply_ocean_button(button: BaseButton) -> void:
button.custom_minimum_size.y = maxf(button.custom_minimum_size.y, 40.0)
static func apply_compact_ocean_button(button: BaseButton) -> void:
apply_ocean_button(button)
for state: StringName in [
&"normal", &"hover", &"pressed", &"focus", &"disabled",
]:
var style := button.get_theme_stylebox(state).duplicate() as StyleBoxFlat
if style == null:
continue
style.content_margin_left = 10.0
style.content_margin_right = 10.0
style.content_margin_top = 4.0
style.content_margin_bottom = 4.0
button.add_theme_stylebox_override(state, style)
button.custom_minimum_size.y = 34.0
static func apply_line_edit(edit: LineEdit) -> void:
edit.add_theme_font_override("font", TuffyFont)
edit.add_theme_color_override("font_color", LIGHT_TEXT)