2026-07-29 20:10:44 -04:00
|
|
|
|
class_name MailPage
|
|
|
|
|
|
extends Control
|
|
|
|
|
|
|
|
|
|
|
|
const GREETING_LABELS := {
|
|
|
|
|
|
"dear": "Dear",
|
|
|
|
|
|
"to": "To",
|
|
|
|
|
|
"hey": "Hey",
|
|
|
|
|
|
"greetings": "Greetings",
|
|
|
|
|
|
}
|
|
|
|
|
|
const SALUTATION_LABELS := {
|
|
|
|
|
|
"love": "Love",
|
|
|
|
|
|
"from": "From",
|
|
|
|
|
|
"cheers": "Cheers",
|
|
|
|
|
|
"salutations": "Salutations",
|
|
|
|
|
|
"good_luck_have_fun": "Good Luck Have Fun",
|
|
|
|
|
|
}
|
2026-08-02 17:44:11 -04:00
|
|
|
|
const FishQualityType = preload("res://fish/fish_quality.gd")
|
2026-08-13 14:53:39 -04:00
|
|
|
|
const CurrencyPresentationType = preload(
|
|
|
|
|
|
"res://ui/currency_presentation.gd"
|
|
|
|
|
|
)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
|
|
|
|
|
|
var _service: NetworkMailService
|
|
|
|
|
|
var _reservations: PlayerAssetReservationService
|
|
|
|
|
|
var _inventory: FishInventory
|
|
|
|
|
|
var _wallet: PlayerWallet
|
|
|
|
|
|
var _bag: PlayerBag
|
|
|
|
|
|
var _catalog: ItemCatalog
|
|
|
|
|
|
|
|
|
|
|
|
var _inbox: Control
|
|
|
|
|
|
var _compose: Control
|
|
|
|
|
|
var _letter: Control
|
|
|
|
|
|
var _inbox_list: VBoxContainer
|
|
|
|
|
|
var _empty_label: Label
|
|
|
|
|
|
var _recipient: OptionButton
|
|
|
|
|
|
var _greeting: OptionButton
|
|
|
|
|
|
var _body: TextEdit
|
|
|
|
|
|
var _salutation: OptionButton
|
|
|
|
|
|
var _signature: Label
|
|
|
|
|
|
var _attachment_kind: OptionButton
|
|
|
|
|
|
var _attachment_choice: OptionButton
|
2026-07-30 14:36:48 -04:00
|
|
|
|
var _attachment_amount: LineEdit
|
2026-08-13 14:53:39 -04:00
|
|
|
|
var _coin_available_heading: Label
|
|
|
|
|
|
var _coin_available: CurrencyAmount
|
|
|
|
|
|
var _attachment_amount_currency_icon: TextureRect
|
2026-07-30 14:36:48 -04:00
|
|
|
|
var _amount_minus: Button
|
|
|
|
|
|
var _amount_plus: Button
|
2026-08-13 14:53:39 -04:00
|
|
|
|
var _attachment_summary: RichTextLabel
|
2026-07-29 20:10:44 -04:00
|
|
|
|
var _send_button: Button
|
|
|
|
|
|
var _status: Label
|
|
|
|
|
|
var _letter_text: Label
|
2026-08-13 14:53:39 -04:00
|
|
|
|
var _letter_gift: RichTextLabel
|
2026-07-29 20:10:44 -04:00
|
|
|
|
var _accept: Button
|
|
|
|
|
|
var _decline: Button
|
2026-07-30 14:36:48 -04:00
|
|
|
|
var _archive: Button
|
|
|
|
|
|
var _delete: Button
|
2026-07-29 20:10:44 -04:00
|
|
|
|
var _current_mail_id := ""
|
2026-07-30 14:36:48 -04:00
|
|
|
|
var _showing_archive := false
|
2026-07-29 20:10:44 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _ready() -> void:
|
|
|
|
|
|
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
2026-07-30 10:46:15 -04:00
|
|
|
|
UtilityPageStyle.apply_page(self)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
_build_ui()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func setup(
|
|
|
|
|
|
service: NetworkMailService,
|
|
|
|
|
|
reservations: PlayerAssetReservationService,
|
|
|
|
|
|
inventory: FishInventory,
|
|
|
|
|
|
wallet: PlayerWallet,
|
|
|
|
|
|
bag: PlayerBag,
|
|
|
|
|
|
catalog: ItemCatalog,
|
|
|
|
|
|
) -> void:
|
|
|
|
|
|
_service = service
|
|
|
|
|
|
_reservations = reservations
|
|
|
|
|
|
_inventory = inventory
|
|
|
|
|
|
_wallet = wallet
|
|
|
|
|
|
_bag = bag
|
|
|
|
|
|
_catalog = catalog
|
|
|
|
|
|
_service.mailbox_changed.connect(_refresh_inbox)
|
|
|
|
|
|
_service.peers_changed.connect(_refresh_recipients)
|
|
|
|
|
|
_service.operation_finished.connect(_on_operation_finished)
|
|
|
|
|
|
_refresh_inbox()
|
|
|
|
|
|
_refresh_recipients()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func activate() -> void:
|
|
|
|
|
|
_show_inbox()
|
2026-07-30 10:46:15 -04:00
|
|
|
|
UtilityPageStyle.animate_in(self)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
call_deferred("_focus_first")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func deactivate() -> void:
|
|
|
|
|
|
_current_mail_id = ""
|
|
|
|
|
|
_show_inbox()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func consume_escape() -> bool:
|
|
|
|
|
|
if _compose.visible or _letter.visible:
|
|
|
|
|
|
_show_inbox()
|
|
|
|
|
|
return true
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-30 23:16:28 -04:00
|
|
|
|
func is_composing_letter() -> bool:
|
|
|
|
|
|
return _compose != null and _compose.visible
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-29 20:10:44 -04:00
|
|
|
|
func set_interactive(value: bool) -> void:
|
|
|
|
|
|
mouse_filter = Control.MOUSE_FILTER_PASS if value else Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
for node: Node in find_children("*", "BaseButton", true, false):
|
|
|
|
|
|
(node as BaseButton).focus_mode = (
|
|
|
|
|
|
Control.FOCUS_ALL if value and node.is_visible_in_tree()
|
|
|
|
|
|
else Control.FOCUS_NONE
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _build_ui() -> void:
|
2026-08-03 00:43:43 -04:00
|
|
|
|
var margin: MarginContainer = UtilityPageStyle.build_laptop_screen(self)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
var root := Control.new()
|
|
|
|
|
|
margin.add_child(root)
|
|
|
|
|
|
_inbox = _build_inbox()
|
|
|
|
|
|
_compose = _build_compose()
|
|
|
|
|
|
_letter = _build_letter()
|
|
|
|
|
|
for page: Control in [_inbox, _compose, _letter]:
|
|
|
|
|
|
page.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
|
|
|
|
root.add_child(page)
|
|
|
|
|
|
_status = Label.new()
|
2026-08-03 00:43:43 -04:00
|
|
|
|
_status.position = Vector2(18, 408)
|
|
|
|
|
|
_status.size = Vector2(1024, 28)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
_status.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
2026-07-31 13:30:36 -04:00
|
|
|
|
_status.add_theme_color_override(
|
|
|
|
|
|
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
|
|
|
|
|
)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
root.add_child(_status)
|
2026-07-30 10:46:15 -04:00
|
|
|
|
_style_controls(root)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
_show_inbox()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _build_inbox() -> Control:
|
|
|
|
|
|
var page := Control.new()
|
|
|
|
|
|
var title := Label.new()
|
2026-08-03 00:43:43 -04:00
|
|
|
|
title.text = "mail"
|
2026-07-29 20:10:44 -04:00
|
|
|
|
title.position = Vector2(12, 0)
|
|
|
|
|
|
title.size = Vector2(500, 42)
|
|
|
|
|
|
title.add_theme_font_size_override("font_size", 30)
|
|
|
|
|
|
page.add_child(title)
|
|
|
|
|
|
var send := Button.new()
|
|
|
|
|
|
send.text = "send mail"
|
|
|
|
|
|
send.position = Vector2(820, 0)
|
|
|
|
|
|
send.size = Vector2(150, 48)
|
|
|
|
|
|
send.pressed.connect(_show_compose)
|
|
|
|
|
|
page.add_child(send)
|
2026-07-30 14:36:48 -04:00
|
|
|
|
var archive_view := Button.new()
|
|
|
|
|
|
archive_view.text = "archive"
|
|
|
|
|
|
archive_view.position = Vector2(654, 0)
|
|
|
|
|
|
archive_view.size = Vector2(154, 48)
|
|
|
|
|
|
archive_view.pressed.connect(func() -> void:
|
|
|
|
|
|
_showing_archive = not _showing_archive
|
|
|
|
|
|
archive_view.text = "inbox" if _showing_archive else "archive"
|
|
|
|
|
|
_refresh_inbox()
|
|
|
|
|
|
)
|
|
|
|
|
|
page.add_child(archive_view)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
var scroll := ScrollContainer.new()
|
|
|
|
|
|
scroll.position = Vector2(12, 62)
|
2026-08-03 00:43:43 -04:00
|
|
|
|
scroll.size = Vector2(1036, 334)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
page.add_child(scroll)
|
|
|
|
|
|
_inbox_list = VBoxContainer.new()
|
2026-08-03 00:43:43 -04:00
|
|
|
|
_inbox_list.custom_minimum_size = Vector2(1008, 0)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
_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
|
2026-08-03 00:43:43 -04:00
|
|
|
|
_empty_label.custom_minimum_size = Vector2(1008, 80)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
_inbox_list.add_child(_empty_label)
|
|
|
|
|
|
return page
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _build_compose() -> Control:
|
|
|
|
|
|
var page := Control.new()
|
|
|
|
|
|
var title := Label.new()
|
|
|
|
|
|
title.text = "write a letter"
|
|
|
|
|
|
title.position = Vector2(12, 0)
|
|
|
|
|
|
title.size = Vector2(300, 36)
|
|
|
|
|
|
title.add_theme_font_size_override("font_size", 26)
|
|
|
|
|
|
page.add_child(title)
|
|
|
|
|
|
_greeting = OptionButton.new()
|
|
|
|
|
|
_greeting.position = Vector2(12, 48)
|
|
|
|
|
|
_greeting.size = Vector2(180, 46)
|
|
|
|
|
|
for id: String in NetworkMailProtocol.GREETINGS:
|
|
|
|
|
|
_greeting.add_item(GREETING_LABELS[id])
|
|
|
|
|
|
_greeting.set_item_metadata(_greeting.item_count - 1, id)
|
|
|
|
|
|
page.add_child(_greeting)
|
|
|
|
|
|
_recipient = OptionButton.new()
|
|
|
|
|
|
_recipient.position = Vector2(204, 48)
|
|
|
|
|
|
_recipient.size = Vector2(330, 46)
|
|
|
|
|
|
page.add_child(_recipient)
|
|
|
|
|
|
_body = TextEdit.new()
|
|
|
|
|
|
_body.position = Vector2(12, 106)
|
|
|
|
|
|
_body.size = Vector2(650, 218)
|
|
|
|
|
|
_body.placeholder_text = "write your letter…"
|
|
|
|
|
|
_body.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY
|
|
|
|
|
|
_body.text_changed.connect(_update_send_state)
|
|
|
|
|
|
page.add_child(_body)
|
|
|
|
|
|
_salutation = OptionButton.new()
|
|
|
|
|
|
_salutation.position = Vector2(12, 336)
|
|
|
|
|
|
_salutation.size = Vector2(250, 46)
|
|
|
|
|
|
for id: String in NetworkMailProtocol.SALUTATIONS:
|
|
|
|
|
|
_salutation.add_item(SALUTATION_LABELS[id])
|
|
|
|
|
|
_salutation.set_item_metadata(_salutation.item_count - 1, id)
|
|
|
|
|
|
page.add_child(_salutation)
|
|
|
|
|
|
_signature = Label.new()
|
|
|
|
|
|
_signature.position = Vector2(274, 342)
|
|
|
|
|
|
_signature.size = Vector2(360, 36)
|
|
|
|
|
|
page.add_child(_signature)
|
|
|
|
|
|
_attachment_kind = OptionButton.new()
|
|
|
|
|
|
_attachment_kind.position = Vector2(688, 48)
|
|
|
|
|
|
_attachment_kind.size = Vector2(280, 46)
|
2026-08-13 14:53:39 -04:00
|
|
|
|
for label: String in ["No gift", "Currency", "Fish", "Consumable"]:
|
2026-07-29 20:10:44 -04:00
|
|
|
|
_attachment_kind.add_item(label)
|
|
|
|
|
|
_attachment_kind.item_selected.connect(_refresh_attachment_choices)
|
|
|
|
|
|
page.add_child(_attachment_kind)
|
|
|
|
|
|
_attachment_choice = OptionButton.new()
|
|
|
|
|
|
_attachment_choice.position = Vector2(688, 106)
|
|
|
|
|
|
_attachment_choice.size = Vector2(280, 46)
|
|
|
|
|
|
_attachment_choice.item_selected.connect(
|
|
|
|
|
|
func(_index: int) -> void:
|
|
|
|
|
|
_update_attachment_amount_limit()
|
|
|
|
|
|
_update_attachment_summary()
|
|
|
|
|
|
)
|
|
|
|
|
|
page.add_child(_attachment_choice)
|
2026-08-13 14:53:39 -04:00
|
|
|
|
_coin_available_heading = Label.new()
|
|
|
|
|
|
_coin_available_heading.text = "Available"
|
|
|
|
|
|
_coin_available_heading.position = Vector2(688, 158)
|
|
|
|
|
|
_coin_available_heading.size = Vector2(80, 28)
|
|
|
|
|
|
page.add_child(_coin_available_heading)
|
|
|
|
|
|
_coin_available = CurrencyPresentationType.instantiate_amount(0, 18.0)
|
|
|
|
|
|
_coin_available.position = Vector2(770, 158)
|
|
|
|
|
|
_coin_available.size = Vector2(198, 28)
|
|
|
|
|
|
_coin_available.alignment = BoxContainer.ALIGNMENT_BEGIN
|
2026-07-30 14:36:48 -04:00
|
|
|
|
page.add_child(_coin_available)
|
2026-08-13 14:53:39 -04:00
|
|
|
|
_attachment_amount_currency_icon = TextureRect.new()
|
|
|
|
|
|
_attachment_amount_currency_icon.position = Vector2(746, 204)
|
|
|
|
|
|
_attachment_amount_currency_icon.size = Vector2(18, 18)
|
|
|
|
|
|
_attachment_amount_currency_icon.texture = preload(
|
|
|
|
|
|
"res://items/icons/shop/32_currency.png"
|
|
|
|
|
|
)
|
|
|
|
|
|
_attachment_amount_currency_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
|
|
|
|
_attachment_amount_currency_icon.stretch_mode = (
|
|
|
|
|
|
TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
|
|
|
|
)
|
|
|
|
|
|
_attachment_amount_currency_icon.texture_filter = (
|
|
|
|
|
|
CanvasItem.TEXTURE_FILTER_NEAREST
|
|
|
|
|
|
)
|
|
|
|
|
|
_attachment_amount_currency_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
page.add_child(_attachment_amount_currency_icon)
|
2026-07-30 14:36:48 -04:00
|
|
|
|
_attachment_amount = LineEdit.new()
|
2026-08-13 14:53:39 -04:00
|
|
|
|
_attachment_amount.position = Vector2(770, 190)
|
|
|
|
|
|
_attachment_amount.size = Vector2(140, 46)
|
2026-07-30 14:36:48 -04:00
|
|
|
|
_attachment_amount.placeholder_text = "0"
|
|
|
|
|
|
_attachment_amount.text = "0"
|
|
|
|
|
|
_attachment_amount.text_changed.connect(_on_attachment_amount_changed)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
page.add_child(_attachment_amount)
|
2026-07-30 14:36:48 -04:00
|
|
|
|
_amount_minus = Button.new()
|
|
|
|
|
|
_amount_minus.text = "−"
|
|
|
|
|
|
_amount_minus.position = Vector2(688, 190)
|
|
|
|
|
|
_amount_minus.size = Vector2(48, 46)
|
|
|
|
|
|
_amount_minus.pressed.connect(_step_attachment_amount.bind(-1))
|
|
|
|
|
|
page.add_child(_amount_minus)
|
|
|
|
|
|
_amount_plus = Button.new()
|
|
|
|
|
|
_amount_plus.text = "+"
|
|
|
|
|
|
_amount_plus.position = Vector2(920, 190)
|
|
|
|
|
|
_amount_plus.size = Vector2(48, 46)
|
|
|
|
|
|
_amount_plus.pressed.connect(_step_attachment_amount.bind(1))
|
|
|
|
|
|
page.add_child(_amount_plus)
|
2026-08-13 14:53:39 -04:00
|
|
|
|
_attachment_summary = RichTextLabel.new()
|
2026-07-30 14:36:48 -04:00
|
|
|
|
_attachment_summary.position = Vector2(688, 246)
|
|
|
|
|
|
_attachment_summary.size = Vector2(280, 78)
|
2026-08-13 14:53:39 -04:00
|
|
|
|
_attachment_summary.bbcode_enabled = true
|
|
|
|
|
|
_attachment_summary.fit_content = true
|
|
|
|
|
|
_attachment_summary.scroll_active = false
|
2026-07-29 20:10:44 -04:00
|
|
|
|
_attachment_summary.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
|
|
|
|
page.add_child(_attachment_summary)
|
|
|
|
|
|
var cancel := Button.new()
|
|
|
|
|
|
cancel.text = "cancel"
|
|
|
|
|
|
cancel.position = Vector2(688, 342)
|
|
|
|
|
|
cancel.size = Vector2(126, 48)
|
|
|
|
|
|
cancel.pressed.connect(_show_inbox)
|
|
|
|
|
|
page.add_child(cancel)
|
|
|
|
|
|
_send_button = Button.new()
|
|
|
|
|
|
_send_button.text = "send letter"
|
|
|
|
|
|
_send_button.position = Vector2(826, 342)
|
|
|
|
|
|
_send_button.size = Vector2(142, 48)
|
|
|
|
|
|
_send_button.pressed.connect(_send)
|
|
|
|
|
|
page.add_child(_send_button)
|
2026-07-30 14:36:48 -04:00
|
|
|
|
_recipient.item_selected.connect(func(_i: int) -> void:
|
|
|
|
|
|
_reset_attachment_controls()
|
|
|
|
|
|
_update_send_state()
|
|
|
|
|
|
)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
_refresh_attachment_choices(0)
|
|
|
|
|
|
return page
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _build_letter() -> Control:
|
|
|
|
|
|
var page := Control.new()
|
|
|
|
|
|
_letter_text = Label.new()
|
|
|
|
|
|
_letter_text.position = Vector2(26, 20)
|
|
|
|
|
|
_letter_text.size = Vector2(680, 350)
|
|
|
|
|
|
_letter_text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
|
|
|
|
_letter_text.vertical_alignment = VERTICAL_ALIGNMENT_TOP
|
|
|
|
|
|
page.add_child(_letter_text)
|
2026-08-13 14:53:39 -04:00
|
|
|
|
_letter_gift = RichTextLabel.new()
|
2026-07-29 20:10:44 -04:00
|
|
|
|
_letter_gift.position = Vector2(730, 46)
|
|
|
|
|
|
_letter_gift.size = Vector2(230, 180)
|
2026-08-13 14:53:39 -04:00
|
|
|
|
_letter_gift.bbcode_enabled = true
|
|
|
|
|
|
_letter_gift.fit_content = true
|
|
|
|
|
|
_letter_gift.scroll_active = false
|
2026-07-29 20:10:44 -04:00
|
|
|
|
_letter_gift.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
|
|
|
|
page.add_child(_letter_gift)
|
|
|
|
|
|
_accept = Button.new()
|
|
|
|
|
|
_accept.text = "accept gift"
|
|
|
|
|
|
_accept.position = Vector2(730, 250)
|
|
|
|
|
|
_accept.size = Vector2(230, 48)
|
|
|
|
|
|
_accept.pressed.connect(func() -> void:
|
|
|
|
|
|
_service.accept_gift(_current_mail_id)
|
|
|
|
|
|
)
|
|
|
|
|
|
page.add_child(_accept)
|
|
|
|
|
|
_decline = Button.new()
|
|
|
|
|
|
_decline.text = "decline gift"
|
|
|
|
|
|
_decline.position = Vector2(730, 308)
|
|
|
|
|
|
_decline.size = Vector2(230, 48)
|
|
|
|
|
|
_decline.pressed.connect(func() -> void:
|
|
|
|
|
|
_service.decline_gift(_current_mail_id)
|
|
|
|
|
|
)
|
|
|
|
|
|
page.add_child(_decline)
|
|
|
|
|
|
var close := Button.new()
|
|
|
|
|
|
close.text = "close"
|
|
|
|
|
|
close.position = Vector2(26, 370)
|
|
|
|
|
|
close.size = Vector2(150, 48)
|
|
|
|
|
|
close.pressed.connect(_show_inbox)
|
|
|
|
|
|
page.add_child(close)
|
2026-07-30 14:36:48 -04:00
|
|
|
|
_archive = Button.new()
|
|
|
|
|
|
_archive.text = "archive"
|
|
|
|
|
|
_archive.position = Vector2(550, 370)
|
|
|
|
|
|
_archive.size = Vector2(150, 48)
|
|
|
|
|
|
_archive.pressed.connect(_archive_current)
|
|
|
|
|
|
page.add_child(_archive)
|
|
|
|
|
|
_delete = Button.new()
|
|
|
|
|
|
_delete.text = "delete"
|
|
|
|
|
|
_delete.position = Vector2(710, 370)
|
|
|
|
|
|
_delete.size = Vector2(150, 48)
|
|
|
|
|
|
_delete.pressed.connect(_delete_current)
|
|
|
|
|
|
page.add_child(_delete)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
return page
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _show_inbox() -> void:
|
|
|
|
|
|
_inbox.show()
|
|
|
|
|
|
_compose.hide()
|
|
|
|
|
|
_letter.hide()
|
|
|
|
|
|
_current_mail_id = ""
|
|
|
|
|
|
_refresh_inbox()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _show_compose() -> void:
|
|
|
|
|
|
_inbox.hide()
|
|
|
|
|
|
_compose.show()
|
|
|
|
|
|
_letter.hide()
|
2026-07-30 14:36:48 -04:00
|
|
|
|
_reset_compose()
|
2026-07-29 20:10:44 -04:00
|
|
|
|
_signature.text = _service.get_local_display_name()
|
|
|
|
|
|
_refresh_recipients()
|
|
|
|
|
|
_update_send_state()
|
|
|
|
|
|
_greeting.grab_focus()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-30 14:36:48 -04:00
|
|
|
|
func _reset_compose() -> void:
|
|
|
|
|
|
_recipient.select(0)
|
|
|
|
|
|
_greeting.select(0)
|
|
|
|
|
|
_body.clear()
|
|
|
|
|
|
_salutation.select(0)
|
|
|
|
|
|
_status.text = ""
|
|
|
|
|
|
_reset_attachment_controls()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _reset_attachment_controls() -> void:
|
|
|
|
|
|
_attachment_kind.select(0)
|
|
|
|
|
|
_attachment_choice.clear()
|
|
|
|
|
|
_attachment_choice.add_item("No attachment")
|
|
|
|
|
|
_attachment_amount.text = "0"
|
|
|
|
|
|
_refresh_attachment_choices(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-29 20:10:44 -04:00
|
|
|
|
func _open_letter(mail_id: String) -> void:
|
|
|
|
|
|
var letter := _service.get_letter(mail_id)
|
|
|
|
|
|
if letter.is_empty():
|
|
|
|
|
|
return
|
|
|
|
|
|
_current_mail_id = mail_id
|
|
|
|
|
|
_service.mark_read(mail_id)
|
|
|
|
|
|
_inbox.hide()
|
|
|
|
|
|
_compose.hide()
|
|
|
|
|
|
_letter.show()
|
|
|
|
|
|
_letter_text.text = "%s %s,\n\n%s\n\n%s,\n%s" % [
|
|
|
|
|
|
GREETING_LABELS.get(letter["greeting_id"], "Dear"),
|
|
|
|
|
|
letter.get("recipient_display_name", "you"),
|
|
|
|
|
|
letter["body"],
|
|
|
|
|
|
SALUTATION_LABELS.get(letter["salutation_id"], "From"),
|
|
|
|
|
|
letter["sender_display_name"],
|
|
|
|
|
|
]
|
|
|
|
|
|
_letter_gift.text = _attachment_state_text(letter)
|
|
|
|
|
|
var pending := (
|
|
|
|
|
|
int(letter["attachment"].get("type", 0)) != 0
|
|
|
|
|
|
and int(letter["state"]) in [
|
|
|
|
|
|
NetworkMailProtocol.State.SENT_UNREAD,
|
|
|
|
|
|
NetworkMailProtocol.State.READ,
|
|
|
|
|
|
]
|
|
|
|
|
|
and _service.is_local_recipient(letter)
|
|
|
|
|
|
)
|
|
|
|
|
|
_accept.visible = pending
|
|
|
|
|
|
_decline.visible = pending
|
2026-07-30 14:36:48 -04:00
|
|
|
|
_archive.text = (
|
|
|
|
|
|
"restore to inbox"
|
|
|
|
|
|
if _service.is_letter_archived(mail_id)
|
|
|
|
|
|
else "archive"
|
|
|
|
|
|
)
|
|
|
|
|
|
_delete.disabled = pending
|
|
|
|
|
|
_delete.tooltip_text = (
|
|
|
|
|
|
"Resolve this gift before deleting the letter." if pending else ""
|
|
|
|
|
|
)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
if pending:
|
|
|
|
|
|
_accept.grab_focus()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _refresh_inbox() -> void:
|
|
|
|
|
|
if _service == null or _inbox_list == null:
|
|
|
|
|
|
return
|
|
|
|
|
|
for child: Node in _inbox_list.get_children():
|
|
|
|
|
|
child.queue_free()
|
2026-07-30 14:36:48 -04:00
|
|
|
|
var letters := _service.get_local_letters(_showing_archive)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
if letters.is_empty():
|
|
|
|
|
|
var empty := Label.new()
|
2026-07-30 14:36:48 -04:00
|
|
|
|
empty.text = (
|
|
|
|
|
|
"No archived letters." if _showing_archive else "No letters yet."
|
|
|
|
|
|
)
|
2026-08-03 00:43:43 -04:00
|
|
|
|
empty.custom_minimum_size = Vector2(1008, 80)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
empty.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
_inbox_list.add_child(empty)
|
|
|
|
|
|
return
|
|
|
|
|
|
for letter: Dictionary in letters:
|
|
|
|
|
|
var button := Button.new()
|
|
|
|
|
|
var first_line: String = str(letter["body"]).split("\n", false)[0]
|
2026-07-30 14:36:48 -04:00
|
|
|
|
var unread := (
|
|
|
|
|
|
_service.is_local_recipient(letter)
|
|
|
|
|
|
and int(letter["state"]) == NetworkMailProtocol.State.SENT_UNREAD
|
|
|
|
|
|
)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
var gift := int(letter["attachment"].get("type", 0)) != 0
|
2026-07-30 14:36:48 -04:00
|
|
|
|
var local_is_sender := (
|
|
|
|
|
|
int(letter["sender_peer_id"]) != 0
|
|
|
|
|
|
and not _service.is_local_recipient(letter)
|
|
|
|
|
|
)
|
|
|
|
|
|
button.text = "%s%s%s — %s%s" % [
|
2026-07-29 20:10:44 -04:00
|
|
|
|
"new · " if unread else "",
|
2026-07-30 14:36:48 -04:00
|
|
|
|
"to " if local_is_sender else "",
|
|
|
|
|
|
(
|
|
|
|
|
|
letter["recipient_display_name"]
|
|
|
|
|
|
if local_is_sender
|
|
|
|
|
|
else letter["sender_display_name"]
|
|
|
|
|
|
),
|
2026-07-29 20:10:44 -04:00
|
|
|
|
first_line.left(72),
|
|
|
|
|
|
" · gift enclosed" if gift else "",
|
|
|
|
|
|
]
|
2026-08-03 00:43:43 -04:00
|
|
|
|
button.custom_minimum_size = Vector2(1008, 54)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
|
|
|
|
|
button.pressed.connect(_open_letter.bind(str(letter["mail_id"])))
|
2026-07-31 13:30:36 -04:00
|
|
|
|
UtilityPageStyle.apply_ocean_button(button)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
_inbox_list.add_child(button)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _refresh_recipients() -> void:
|
|
|
|
|
|
if _service == null or _recipient == null:
|
|
|
|
|
|
return
|
|
|
|
|
|
var selected_peer := (
|
|
|
|
|
|
int(_recipient.get_selected_metadata())
|
|
|
|
|
|
if _recipient.selected >= 0
|
|
|
|
|
|
and _recipient.get_selected_metadata() != null else 0
|
|
|
|
|
|
)
|
|
|
|
|
|
_recipient.clear()
|
|
|
|
|
|
_recipient.add_item("select a player")
|
|
|
|
|
|
_recipient.set_item_metadata(0, 0)
|
|
|
|
|
|
var retained := false
|
|
|
|
|
|
for choice: Dictionary in _service.get_recipient_choices():
|
|
|
|
|
|
_recipient.add_item(choice["label"])
|
|
|
|
|
|
_recipient.set_item_metadata(
|
|
|
|
|
|
_recipient.item_count - 1, choice["peer_id"]
|
|
|
|
|
|
)
|
|
|
|
|
|
if int(choice["peer_id"]) == selected_peer:
|
|
|
|
|
|
_recipient.select(_recipient.item_count - 1)
|
|
|
|
|
|
retained = true
|
|
|
|
|
|
if selected_peer > 0 and not retained:
|
|
|
|
|
|
_recipient.select(0)
|
|
|
|
|
|
if _status != null:
|
|
|
|
|
|
_status.text = "That player is no longer connected."
|
|
|
|
|
|
_update_send_state()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _refresh_attachment_choices(_index: int) -> void:
|
|
|
|
|
|
if _attachment_choice == null:
|
|
|
|
|
|
return
|
|
|
|
|
|
_attachment_choice.clear()
|
2026-07-30 14:36:48 -04:00
|
|
|
|
var amount_visible := _attachment_kind.selected in [1, 3]
|
|
|
|
|
|
_attachment_amount.visible = amount_visible
|
|
|
|
|
|
_amount_minus.visible = amount_visible
|
|
|
|
|
|
_amount_plus.visible = amount_visible
|
2026-08-13 14:53:39 -04:00
|
|
|
|
var currency_selected: bool = _attachment_kind.selected == 1
|
|
|
|
|
|
_coin_available_heading.visible = currency_selected
|
|
|
|
|
|
_coin_available.visible = currency_selected
|
|
|
|
|
|
_attachment_amount_currency_icon.visible = currency_selected
|
|
|
|
|
|
_attachment_amount.position.x = 770.0 if currency_selected else 746.0
|
|
|
|
|
|
_attachment_amount.size.x = 140.0 if currency_selected else 164.0
|
2026-07-29 20:10:44 -04:00
|
|
|
|
match _attachment_kind.selected:
|
|
|
|
|
|
0:
|
|
|
|
|
|
_attachment_choice.add_item("No attachment")
|
|
|
|
|
|
1:
|
2026-08-13 14:53:39 -04:00
|
|
|
|
_attachment_choice.add_item("Currency")
|
|
|
|
|
|
_coin_available.set_amount(
|
2026-07-30 14:36:48 -04:00
|
|
|
|
_reservations.get_available_fish_coin()
|
2026-07-29 20:10:44 -04:00
|
|
|
|
)
|
|
|
|
|
|
2:
|
|
|
|
|
|
for fish_catch: FishCatch in _inventory.get_all_catches():
|
|
|
|
|
|
if (
|
|
|
|
|
|
not fish_catch.is_favorited
|
|
|
|
|
|
and not _reservations.is_fish_reserved(fish_catch.catch_id)
|
|
|
|
|
|
):
|
|
|
|
|
|
_attachment_choice.add_item(
|
|
|
|
|
|
"%s — %.1f lb" % [
|
2026-08-02 17:44:11 -04:00
|
|
|
|
FishQualityType.qualified_name(
|
|
|
|
|
|
fish_catch.fish.display_name,
|
|
|
|
|
|
fish_catch.quality,
|
|
|
|
|
|
),
|
|
|
|
|
|
fish_catch.weight_lb,
|
2026-07-29 20:10:44 -04:00
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
_attachment_choice.set_item_metadata(
|
|
|
|
|
|
_attachment_choice.item_count - 1, str(fish_catch.catch_id)
|
|
|
|
|
|
)
|
|
|
|
|
|
3:
|
|
|
|
|
|
for item: ItemData in _catalog.get_valid_items():
|
|
|
|
|
|
var available := _reservations.get_available_item_quantity(
|
|
|
|
|
|
item.item_id
|
|
|
|
|
|
)
|
|
|
|
|
|
if item.category == ItemData.Category.CONSUMABLE and available > 0:
|
|
|
|
|
|
_attachment_choice.add_item(
|
|
|
|
|
|
"%s ×%d" % [item.display_name, available]
|
|
|
|
|
|
)
|
|
|
|
|
|
_attachment_choice.set_item_metadata(
|
|
|
|
|
|
_attachment_choice.item_count - 1, str(item.item_id)
|
|
|
|
|
|
)
|
|
|
|
|
|
_update_attachment_amount_limit()
|
|
|
|
|
|
_update_attachment_summary()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _update_attachment_amount_limit() -> void:
|
2026-07-30 14:36:48 -04:00
|
|
|
|
_set_attachment_amount(_attachment_amount_value())
|
2026-07-29 20:10:44 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _make_attachment() -> Dictionary:
|
|
|
|
|
|
match _attachment_kind.selected:
|
|
|
|
|
|
1:
|
|
|
|
|
|
return {
|
|
|
|
|
|
"type": PlayerAssetReservationService.AttachmentType.FISH_COIN,
|
2026-07-30 14:36:48 -04:00
|
|
|
|
"amount": _attachment_amount_value(),
|
2026-07-29 20:10:44 -04:00
|
|
|
|
}
|
|
|
|
|
|
2:
|
|
|
|
|
|
if _attachment_choice.selected < 0:
|
|
|
|
|
|
return {}
|
|
|
|
|
|
var catch_id := StringName(str(_attachment_choice.get_selected_metadata()))
|
|
|
|
|
|
var fish_catch := _inventory.get_catch_by_id(catch_id)
|
|
|
|
|
|
if fish_catch == null:
|
|
|
|
|
|
return {}
|
|
|
|
|
|
return {
|
|
|
|
|
|
"type": PlayerAssetReservationService.AttachmentType.FISH,
|
|
|
|
|
|
"catch_id": str(catch_id),
|
|
|
|
|
|
"catch": fish_catch.to_network_dict(),
|
|
|
|
|
|
}
|
|
|
|
|
|
3:
|
|
|
|
|
|
if _attachment_choice.selected < 0:
|
|
|
|
|
|
return {}
|
|
|
|
|
|
return {
|
|
|
|
|
|
"type": PlayerAssetReservationService.AttachmentType.CONSUMABLE,
|
|
|
|
|
|
"item_id": str(_attachment_choice.get_selected_metadata()),
|
2026-07-30 14:36:48 -04:00
|
|
|
|
"quantity": _attachment_amount_value(),
|
2026-07-29 20:10:44 -04:00
|
|
|
|
}
|
|
|
|
|
|
return {"type": PlayerAssetReservationService.AttachmentType.NONE}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _update_attachment_summary() -> void:
|
|
|
|
|
|
if _attachment_summary == null:
|
|
|
|
|
|
return
|
|
|
|
|
|
var attachment := _make_attachment()
|
|
|
|
|
|
_attachment_summary.text = _attachment_text(attachment)
|
|
|
|
|
|
_update_send_state()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _update_send_state() -> void:
|
|
|
|
|
|
if _send_button == null:
|
|
|
|
|
|
return
|
|
|
|
|
|
var recipient_id := (
|
|
|
|
|
|
int(_recipient.get_selected_metadata())
|
|
|
|
|
|
if _recipient.selected >= 0 and _recipient.get_selected_metadata() != null
|
|
|
|
|
|
else 0
|
|
|
|
|
|
)
|
|
|
|
|
|
var attachment := _make_attachment()
|
|
|
|
|
|
_send_button.disabled = (
|
|
|
|
|
|
recipient_id <= 0
|
|
|
|
|
|
or NetworkMailProtocol.sanitize_body(_body.text).is_empty()
|
|
|
|
|
|
or attachment.is_empty()
|
|
|
|
|
|
or not _reservations.validate_attachment(attachment)
|
|
|
|
|
|
or not _attachment_is_available(attachment)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _attachment_is_available(attachment: Dictionary) -> bool:
|
|
|
|
|
|
match int(attachment.get("type", 0)):
|
|
|
|
|
|
PlayerAssetReservationService.AttachmentType.NONE:
|
|
|
|
|
|
return true
|
|
|
|
|
|
PlayerAssetReservationService.AttachmentType.FISH_COIN:
|
|
|
|
|
|
return (
|
|
|
|
|
|
int(attachment["amount"])
|
|
|
|
|
|
<= _reservations.get_available_fish_coin()
|
|
|
|
|
|
)
|
|
|
|
|
|
PlayerAssetReservationService.AttachmentType.FISH:
|
|
|
|
|
|
return not _reservations.is_fish_reserved(
|
|
|
|
|
|
StringName(str(attachment["catch_id"]))
|
|
|
|
|
|
)
|
|
|
|
|
|
PlayerAssetReservationService.AttachmentType.CONSUMABLE:
|
|
|
|
|
|
return (
|
|
|
|
|
|
int(attachment["quantity"])
|
|
|
|
|
|
<= _reservations.get_available_item_quantity(
|
|
|
|
|
|
StringName(str(attachment["item_id"]))
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _send() -> void:
|
2026-07-30 14:36:48 -04:00
|
|
|
|
_set_attachment_amount(_attachment_amount_value())
|
2026-07-29 20:10:44 -04:00
|
|
|
|
var recipient_id := int(_recipient.get_selected_metadata())
|
|
|
|
|
|
var greeting_id := str(_greeting.get_selected_metadata())
|
|
|
|
|
|
var salutation_id := str(_salutation.get_selected_metadata())
|
|
|
|
|
|
if _service.send_letter(
|
|
|
|
|
|
recipient_id, greeting_id, _body.text, salutation_id, _make_attachment()
|
|
|
|
|
|
):
|
|
|
|
|
|
_send_button.disabled = true
|
|
|
|
|
|
_status.text = "Sending letter…"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_operation_finished(success: bool, message: String) -> void:
|
|
|
|
|
|
_status.text = message
|
|
|
|
|
|
if success and _compose.visible:
|
|
|
|
|
|
_show_inbox()
|
|
|
|
|
|
elif _letter.visible and not _current_mail_id.is_empty():
|
|
|
|
|
|
_open_letter(_current_mail_id)
|
|
|
|
|
|
else:
|
|
|
|
|
|
_update_send_state()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-30 14:36:48 -04:00
|
|
|
|
func _attachment_amount_value() -> int:
|
|
|
|
|
|
return int(_attachment_amount.text) if _attachment_amount.text.is_valid_int() else 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _attachment_amount_maximum() -> int:
|
|
|
|
|
|
if _attachment_kind.selected == 1:
|
|
|
|
|
|
return _reservations.get_available_fish_coin()
|
|
|
|
|
|
if _attachment_kind.selected == 3 and _attachment_choice.selected >= 0:
|
|
|
|
|
|
return _reservations.get_available_item_quantity(
|
|
|
|
|
|
StringName(str(_attachment_choice.get_selected_metadata()))
|
|
|
|
|
|
)
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_attachment_amount(value: int) -> void:
|
|
|
|
|
|
var clamped := clampi(value, 0, _attachment_amount_maximum())
|
|
|
|
|
|
var text := str(clamped)
|
|
|
|
|
|
if _attachment_amount.text != text:
|
|
|
|
|
|
_attachment_amount.text = text
|
|
|
|
|
|
_attachment_amount.caret_column = text.length()
|
|
|
|
|
|
_amount_minus.disabled = clamped <= 0
|
|
|
|
|
|
_amount_plus.disabled = clamped >= _attachment_amount_maximum()
|
|
|
|
|
|
_update_attachment_summary()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_attachment_amount_changed(value: String) -> void:
|
|
|
|
|
|
var digits := ""
|
|
|
|
|
|
for character: String in value:
|
|
|
|
|
|
if character >= "0" and character <= "9":
|
|
|
|
|
|
digits += character
|
|
|
|
|
|
_set_attachment_amount(int(digits) if not digits.is_empty() else 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _step_attachment_amount(delta: int) -> void:
|
|
|
|
|
|
_set_attachment_amount(_attachment_amount_value() + delta)
|
|
|
|
|
|
_attachment_amount.grab_focus()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _archive_current() -> void:
|
|
|
|
|
|
if _current_mail_id.is_empty():
|
|
|
|
|
|
return
|
|
|
|
|
|
var archived := not _service.is_letter_archived(_current_mail_id)
|
|
|
|
|
|
if _service.archive_local_letter(_current_mail_id, archived):
|
|
|
|
|
|
_status.text = "Letter archived." if archived else "Letter restored."
|
|
|
|
|
|
_show_inbox()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _delete_current() -> void:
|
|
|
|
|
|
if _current_mail_id.is_empty():
|
|
|
|
|
|
return
|
|
|
|
|
|
if _service.delete_local_letter(_current_mail_id):
|
|
|
|
|
|
_status.text = "Letter deleted locally."
|
|
|
|
|
|
_show_inbox()
|
|
|
|
|
|
else:
|
|
|
|
|
|
_status.text = "Resolve the gift before deleting this letter."
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-29 20:10:44 -04:00
|
|
|
|
func _attachment_state_text(letter: Dictionary) -> String:
|
|
|
|
|
|
var attachment: Dictionary = letter["attachment"]
|
|
|
|
|
|
if int(attachment.get("type", 0)) == 0:
|
|
|
|
|
|
return ""
|
|
|
|
|
|
match int(letter["state"]):
|
|
|
|
|
|
NetworkMailProtocol.State.ACCEPTED:
|
|
|
|
|
|
return "Gift accepted.\n\n%s" % _attachment_text(attachment)
|
|
|
|
|
|
NetworkMailProtocol.State.DECLINED:
|
|
|
|
|
|
return "Gift declined."
|
|
|
|
|
|
NetworkMailProtocol.State.ATTACHMENT_RECALLED, NetworkMailProtocol.State.CANCELLED:
|
|
|
|
|
|
return "Gift returned to sender."
|
|
|
|
|
|
NetworkMailProtocol.State.ACCEPTANCE_PENDING:
|
|
|
|
|
|
return "Transferring gift…"
|
|
|
|
|
|
return "Gift enclosed\n\n%s" % _attachment_text(attachment)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _attachment_text(attachment: Dictionary) -> String:
|
|
|
|
|
|
match int(attachment.get("type", 0)):
|
|
|
|
|
|
PlayerAssetReservationService.AttachmentType.NONE:
|
|
|
|
|
|
return "No gift attached."
|
|
|
|
|
|
PlayerAssetReservationService.AttachmentType.FISH_COIN:
|
2026-08-13 14:53:39 -04:00
|
|
|
|
return CurrencyPresentationType.bbcode_amount(
|
|
|
|
|
|
int(attachment.get("amount", 0)), 18
|
|
|
|
|
|
)
|
2026-07-29 20:10:44 -04:00
|
|
|
|
PlayerAssetReservationService.AttachmentType.FISH:
|
|
|
|
|
|
var fish_catch := _inventory.get_catch_by_id(
|
|
|
|
|
|
StringName(str(attachment.get("catch_id", "")))
|
|
|
|
|
|
)
|
|
|
|
|
|
if fish_catch != null:
|
|
|
|
|
|
return "%s — %.1f lb" % [
|
2026-08-02 17:44:11 -04:00
|
|
|
|
FishQualityType.qualified_name(
|
|
|
|
|
|
fish_catch.fish.display_name,
|
|
|
|
|
|
fish_catch.quality,
|
|
|
|
|
|
),
|
|
|
|
|
|
fish_catch.weight_lb,
|
2026-07-29 20:10:44 -04:00
|
|
|
|
]
|
|
|
|
|
|
var data: Dictionary = attachment.get("catch", {})
|
|
|
|
|
|
return "%s — %.1f lb" % [
|
2026-08-02 17:44:11 -04:00
|
|
|
|
FishQualityType.qualified_name(
|
|
|
|
|
|
str(data.get("fish_id", "fish")).capitalize(),
|
|
|
|
|
|
int(data.get("quality", FishQualityType.Tier.BORING)),
|
|
|
|
|
|
),
|
2026-07-29 20:10:44 -04:00
|
|
|
|
float(data.get("weight_lb", 0.0)),
|
|
|
|
|
|
]
|
|
|
|
|
|
PlayerAssetReservationService.AttachmentType.CONSUMABLE:
|
|
|
|
|
|
var item := _catalog.get_item_by_id(
|
|
|
|
|
|
StringName(str(attachment.get("item_id", "")))
|
|
|
|
|
|
)
|
|
|
|
|
|
return "%s ×%d" % [
|
|
|
|
|
|
item.display_name if item != null else "Consumable",
|
|
|
|
|
|
int(attachment.get("quantity", 0)),
|
|
|
|
|
|
]
|
|
|
|
|
|
return "Invalid gift."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _focus_first() -> void:
|
|
|
|
|
|
var buttons := _inbox_list.find_children("*", "Button", true, false)
|
|
|
|
|
|
if not buttons.is_empty():
|
|
|
|
|
|
(buttons.front() as Button).grab_focus()
|
2026-07-30 10:46:15 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _style_controls(root: Node) -> void:
|
|
|
|
|
|
for node: Node in root.find_children("*", "", true, false):
|
|
|
|
|
|
if node is BaseButton:
|
2026-07-31 13:30:36 -04:00
|
|
|
|
UtilityPageStyle.apply_ocean_button(node)
|
2026-07-30 10:46:15 -04:00
|
|
|
|
elif node is LineEdit:
|
2026-07-31 13:30:36 -04:00
|
|
|
|
UtilityPageStyle.apply_ocean_line_edit(node)
|
2026-07-30 10:46:15 -04:00
|
|
|
|
elif node is Label:
|
|
|
|
|
|
node.add_theme_color_override(
|
2026-07-31 13:30:36 -04:00
|
|
|
|
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
2026-07-30 10:46:15 -04:00
|
|
|
|
)
|
|
|
|
|
|
elif node is TextEdit:
|
2026-07-31 13:30:36 -04:00
|
|
|
|
UtilityPageStyle.apply_text_edit(node)
|