forked from woofmeow/straywild
Add gathering, unified inventory, and real-time world
This commit is contained in:
parent
173371e6fc
commit
fa57edca83
113 changed files with 6700 additions and 1307 deletions
|
|
@ -193,7 +193,6 @@ func setup(
|
|||
_service.local_message_confirmed.connect(_on_local_message_confirmed)
|
||||
_service.history_replaced.connect(_on_history)
|
||||
_service.send_rejected.connect(_on_rejected)
|
||||
_service.world_command_finished.connect(_on_world_command_finished)
|
||||
_session.peer_removed.connect(_on_peer_removed)
|
||||
_entry.text = _settings.current_settings.chat_draft
|
||||
_entry.caret_column = _entry.text.length()
|
||||
|
|
@ -835,8 +834,6 @@ func _send() -> void:
|
|||
if body.strip_edges().is_empty():
|
||||
close_chat()
|
||||
return
|
||||
if _handle_chat_command(body):
|
||||
return
|
||||
_send_pending = true
|
||||
_pending_send_body = NetworkChatProtocol.sanitize_body(body)
|
||||
_entry.editable = false
|
||||
|
|
@ -852,85 +849,6 @@ func _send() -> void:
|
|||
_set_status("Sending…")
|
||||
|
||||
|
||||
func _handle_chat_command(body: String) -> bool:
|
||||
var command_text := body.strip_edges()
|
||||
if not command_text.begins_with("/"):
|
||||
return false
|
||||
var parts: PackedStringArray = command_text.split(" ", false)
|
||||
var command := String(parts[0]).trim_prefix("/").to_lower()
|
||||
match command:
|
||||
"time":
|
||||
_handle_time_command(parts)
|
||||
"weather":
|
||||
_handle_weather_command(parts)
|
||||
"":
|
||||
_show_command_error("Enter a command after /.")
|
||||
_:
|
||||
_show_command_error("Unknown command: /%s" % command)
|
||||
_entry.clear()
|
||||
_flush_draft()
|
||||
return true
|
||||
|
||||
|
||||
func _show_command_error(message: String) -> void:
|
||||
_set_status(message)
|
||||
close_chat(true)
|
||||
|
||||
|
||||
func _handle_time_command(parts: PackedStringArray) -> void:
|
||||
if parts.size() != 2:
|
||||
_show_command_error("Usage: /time [dawn, day, dusk, night]")
|
||||
return
|
||||
if _service == null or _world_time == null:
|
||||
_show_command_error("World time is unavailable.")
|
||||
return
|
||||
var phase_name := String(parts[1]).to_lower()
|
||||
if phase_name not in ["dawn", "day", "dusk", "night"]:
|
||||
_show_command_error("Usage: /time [dawn, day, dusk, night]")
|
||||
return
|
||||
if not _service.request_world_time_change(phase_name):
|
||||
_show_command_error(
|
||||
"Only the host or an operator can change world time."
|
||||
)
|
||||
return
|
||||
close_chat()
|
||||
|
||||
|
||||
func _handle_weather_command(parts: PackedStringArray) -> void:
|
||||
if parts.size() != 2:
|
||||
_show_command_error(
|
||||
"Usage: /weather [clear, cloudy, rainy, foggy]"
|
||||
)
|
||||
return
|
||||
if _service == null or _world_weather == null:
|
||||
_show_command_error("World weather is unavailable.")
|
||||
return
|
||||
var weather_name := String(parts[1]).to_lower()
|
||||
match weather_name:
|
||||
"clear", "sunny":
|
||||
weather_name = "clear"
|
||||
"cloudy", "rainy", "foggy":
|
||||
pass
|
||||
_:
|
||||
_show_command_error(
|
||||
"Usage: /weather [clear, cloudy, rainy, foggy]"
|
||||
)
|
||||
return
|
||||
if not _service.request_world_weather_change(weather_name):
|
||||
_show_command_error(
|
||||
"Only the host or an operator can change world weather."
|
||||
)
|
||||
return
|
||||
close_chat()
|
||||
|
||||
|
||||
func _on_world_command_finished(success: bool, message: String) -> void:
|
||||
if success:
|
||||
_set_status("")
|
||||
elif not message.is_empty():
|
||||
_set_status(message)
|
||||
|
||||
|
||||
func _on_local_message_confirmed(message: Dictionary) -> void:
|
||||
if (
|
||||
not _send_pending
|
||||
|
|
|
|||
159
ui/components/general_inventory_grid.gd
Normal file
159
ui/components/general_inventory_grid.gd
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
class_name GeneralInventoryGrid
|
||||
extends Control
|
||||
|
||||
signal entry_selected(kind: int, identity: StringName)
|
||||
signal slot_activated(slot: GeneralInventorySlot)
|
||||
signal context_requested(slot: GeneralInventorySlot)
|
||||
signal context_changed(slot: GeneralInventorySlot, text: String, active: bool)
|
||||
|
||||
const DEFAULT_SLOT_SIZE := Vector2(52.0, 52.0)
|
||||
const DEFAULT_SLOT_SEPARATION: int = 5
|
||||
const COLUMNS: int = PlayerInventoryLayout.INVENTORY_COLUMNS
|
||||
|
||||
var _layout: PlayerInventoryLayout
|
||||
var _bag: PlayerBag
|
||||
var _fish_inventory: FishInventory
|
||||
var _hotbar: PlayerHotbar
|
||||
var _item_catalog: ItemCatalog
|
||||
var _container: int = PlayerInventoryLayout.InventoryContainer.INVENTORY
|
||||
var _grid: GridContainer
|
||||
var _slots: Array[GeneralInventorySlot] = []
|
||||
var _slot_size: Vector2 = DEFAULT_SLOT_SIZE
|
||||
var _slot_separation: int = DEFAULT_SLOT_SEPARATION
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_grid = GridContainer.new()
|
||||
_grid.columns = COLUMNS
|
||||
_apply_grid_separation()
|
||||
add_child(_grid)
|
||||
|
||||
|
||||
func set_slot_presentation(slot_size: Vector2, separation: int) -> void:
|
||||
_slot_size = Vector2(maxf(1.0, slot_size.x), maxf(1.0, slot_size.y))
|
||||
_slot_separation = maxi(0, separation)
|
||||
if _grid != null:
|
||||
_apply_grid_separation()
|
||||
if is_node_ready() and _layout != null:
|
||||
var active_capacity := (
|
||||
_layout.get_inventory_capacity()
|
||||
if _container == PlayerInventoryLayout.InventoryContainer.INVENTORY
|
||||
else _layout.get_storage_capacity()
|
||||
)
|
||||
_rebuild(
|
||||
PlayerInventoryLayout.MAX_INVENTORY_SLOT_COUNT
|
||||
if _container == PlayerInventoryLayout.InventoryContainer.INVENTORY
|
||||
else PlayerInventoryLayout.MAX_STORAGE_SLOT_COUNT,
|
||||
active_capacity,
|
||||
)
|
||||
|
||||
|
||||
func setup(
|
||||
layout: PlayerInventoryLayout,
|
||||
bag: PlayerBag,
|
||||
fish_inventory: FishInventory,
|
||||
hotbar: PlayerHotbar,
|
||||
item_catalog: ItemCatalog,
|
||||
container: int,
|
||||
) -> void:
|
||||
_layout = layout
|
||||
_bag = bag
|
||||
_fish_inventory = fish_inventory
|
||||
_hotbar = hotbar
|
||||
_item_catalog = item_catalog
|
||||
_container = container
|
||||
if _layout != null and not _layout.layout_changed.is_connected(refresh):
|
||||
_layout.layout_changed.connect(refresh)
|
||||
refresh()
|
||||
|
||||
|
||||
func refresh() -> void:
|
||||
if not is_node_ready() or _layout == null:
|
||||
return
|
||||
var active_capacity := (
|
||||
_layout.get_inventory_capacity()
|
||||
if _container == PlayerInventoryLayout.InventoryContainer.INVENTORY
|
||||
else _layout.get_storage_capacity()
|
||||
)
|
||||
var visible_capacity := (
|
||||
PlayerInventoryLayout.MAX_INVENTORY_SLOT_COUNT
|
||||
if _container == PlayerInventoryLayout.InventoryContainer.INVENTORY
|
||||
else PlayerInventoryLayout.MAX_STORAGE_SLOT_COUNT
|
||||
)
|
||||
if _slots.size() != visible_capacity:
|
||||
_rebuild(visible_capacity, active_capacity)
|
||||
else:
|
||||
for index: int in _slots.size():
|
||||
_slots[index].set_locked(index >= active_capacity)
|
||||
for slot: GeneralInventorySlot in _slots:
|
||||
slot.refresh()
|
||||
|
||||
|
||||
func get_slots() -> Array[GeneralInventorySlot]:
|
||||
var active: Array[GeneralInventorySlot] = []
|
||||
for slot: GeneralInventorySlot in _slots:
|
||||
if not slot.disabled:
|
||||
active.append(slot)
|
||||
return active
|
||||
|
||||
|
||||
func get_first_occupied_slot() -> GeneralInventorySlot:
|
||||
for slot: GeneralInventorySlot in _slots:
|
||||
if not slot.entry_identity.is_empty():
|
||||
return slot
|
||||
return _slots.front() if not _slots.is_empty() else null
|
||||
|
||||
|
||||
func _rebuild(visible_capacity: int, active_capacity: int) -> void:
|
||||
for child: Node in _grid.get_children():
|
||||
child.queue_free()
|
||||
_slots.clear()
|
||||
for slot_index: int in visible_capacity:
|
||||
var slot := GeneralInventorySlot.new()
|
||||
slot.custom_minimum_size = _slot_size
|
||||
slot.entry_selected.connect(
|
||||
func(kind: int, identity: StringName) -> void:
|
||||
entry_selected.emit(kind, identity)
|
||||
)
|
||||
slot.pressed.connect(func() -> void: slot_activated.emit(slot))
|
||||
slot.context_requested.connect(
|
||||
func(source: GeneralInventorySlot) -> void:
|
||||
context_requested.emit(source)
|
||||
)
|
||||
slot.context_changed.connect(
|
||||
func(
|
||||
source: GeneralInventorySlot,
|
||||
text: String,
|
||||
active: bool,
|
||||
) -> void:
|
||||
context_changed.emit(source, text, active)
|
||||
)
|
||||
_grid.add_child(slot)
|
||||
slot.set_presentation_size(_slot_size)
|
||||
slot.configure(
|
||||
slot_index,
|
||||
_container,
|
||||
slot_index >= active_capacity,
|
||||
_layout,
|
||||
_bag,
|
||||
_fish_inventory,
|
||||
_hotbar,
|
||||
_item_catalog,
|
||||
)
|
||||
_slots.append(slot)
|
||||
custom_minimum_size = Vector2(
|
||||
float(COLUMNS) * _slot_size.x
|
||||
+ float(COLUMNS - 1) * _slot_separation,
|
||||
ceilf(float(visible_capacity) / float(COLUMNS)) * _slot_size.y
|
||||
+ maxf(
|
||||
0.0,
|
||||
ceilf(float(visible_capacity) / float(COLUMNS)) - 1.0,
|
||||
) * _slot_separation,
|
||||
)
|
||||
size = custom_minimum_size
|
||||
ControllerFocusNavigation.configure_spatial_neighbors(_slots)
|
||||
|
||||
|
||||
func _apply_grid_separation() -> void:
|
||||
_grid.add_theme_constant_override("h_separation", _slot_separation)
|
||||
_grid.add_theme_constant_override("v_separation", _slot_separation)
|
||||
1
ui/components/general_inventory_grid.gd.uid
Normal file
1
ui/components/general_inventory_grid.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://k6h255orqr6o
|
||||
346
ui/components/general_inventory_slot.gd
Normal file
346
ui/components/general_inventory_slot.gd
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
class_name GeneralInventorySlot
|
||||
extends Button
|
||||
|
||||
signal entry_selected(kind: int, identity: StringName)
|
||||
signal entry_moved
|
||||
signal context_requested(slot: GeneralInventorySlot)
|
||||
signal context_changed(slot: GeneralInventorySlot, text: String, active: bool)
|
||||
|
||||
const ItemDataType = preload("res://items/item_data.gd")
|
||||
const LOCK_ICON: Texture2D = preload("res://ui/icons/pictograms/lock_light.png")
|
||||
|
||||
var slot_index: int = -1
|
||||
var container: int = -1
|
||||
var entry_kind: int = -1
|
||||
var entry_identity: StringName
|
||||
var _locked: bool = false
|
||||
|
||||
var _layout: PlayerInventoryLayout
|
||||
var _bag: PlayerBag
|
||||
var _fish_inventory: FishInventory
|
||||
var _hotbar: PlayerHotbar
|
||||
var _item_catalog: ItemCatalog
|
||||
var _icon: TextureRect
|
||||
var _quantity: Label
|
||||
var _presentation_size := Vector2(52.0, 52.0)
|
||||
var _context_text: String = ""
|
||||
var _context_hovered: bool = false
|
||||
var _context_focused: bool = false
|
||||
var _staged: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
focus_mode = Control.FOCUS_ALL
|
||||
mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
pressed.connect(_on_pressed)
|
||||
focus_entered.connect(_set_context_focused.bind(true))
|
||||
focus_exited.connect(_set_context_focused.bind(false))
|
||||
mouse_entered.connect(_set_context_hovered.bind(true))
|
||||
mouse_exited.connect(_set_context_hovered.bind(false))
|
||||
gui_input.connect(_on_gui_input)
|
||||
_icon = TextureRect.new()
|
||||
_icon.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
_icon.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_icon)
|
||||
_quantity = Label.new()
|
||||
_quantity.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||||
_quantity.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
_quantity.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
_quantity.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||
)
|
||||
_quantity.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_quantity)
|
||||
_apply_presentation()
|
||||
|
||||
|
||||
func set_presentation_size(presentation_size: Vector2) -> void:
|
||||
_presentation_size = presentation_size
|
||||
custom_minimum_size = presentation_size
|
||||
if is_node_ready():
|
||||
_apply_presentation()
|
||||
|
||||
|
||||
func configure(
|
||||
new_slot_index: int,
|
||||
new_container: int,
|
||||
is_locked: bool,
|
||||
layout: PlayerInventoryLayout,
|
||||
bag: PlayerBag,
|
||||
fish_inventory: FishInventory,
|
||||
hotbar: PlayerHotbar,
|
||||
item_catalog: ItemCatalog,
|
||||
) -> void:
|
||||
slot_index = new_slot_index
|
||||
container = new_container
|
||||
_locked = is_locked
|
||||
_layout = layout
|
||||
_bag = bag
|
||||
_fish_inventory = fish_inventory
|
||||
_hotbar = hotbar
|
||||
_item_catalog = item_catalog
|
||||
name = "InventorySlot%d" % slot_index
|
||||
refresh()
|
||||
|
||||
|
||||
func set_locked(is_locked: bool) -> void:
|
||||
if _locked == is_locked:
|
||||
return
|
||||
_locked = is_locked
|
||||
refresh()
|
||||
|
||||
|
||||
func refresh() -> void:
|
||||
call_deferred("_update_context_presence")
|
||||
entry_kind = -1
|
||||
entry_identity = StringName()
|
||||
_context_text = ""
|
||||
_icon.texture = null
|
||||
_quantity.text = ""
|
||||
disabled = _locked
|
||||
_apply_icon_geometry()
|
||||
if _locked:
|
||||
_icon.texture = LOCK_ICON
|
||||
_icon.modulate = Color(UtilityPageStyle.OCEAN_DISABLED, 0.18)
|
||||
var storage_slot := (
|
||||
container == PlayerInventoryLayout.InventoryContainer.STORAGE
|
||||
)
|
||||
_context_text = (
|
||||
"locked storage slot" if storage_slot else "locked backpack slot"
|
||||
)
|
||||
tooltip_text = ""
|
||||
accessibility_name = "%s %d" % [_context_text, slot_index + 1]
|
||||
return
|
||||
_icon.modulate = Color.WHITE
|
||||
_context_text = "empty slot"
|
||||
tooltip_text = ""
|
||||
accessibility_name = "empty %s slot %d" % [
|
||||
"storage"
|
||||
if container == PlayerInventoryLayout.InventoryContainer.STORAGE
|
||||
else "inventory",
|
||||
slot_index + 1,
|
||||
]
|
||||
if _layout == null:
|
||||
return
|
||||
var entry := _layout.get_entry_at(container, slot_index)
|
||||
if entry.is_empty():
|
||||
return
|
||||
entry_kind = int(entry.get("kind", -1))
|
||||
entry_identity = StringName(str(entry.get("identity", "")))
|
||||
if entry_kind == PlayerInventoryLayout.EntryKind.ITEM:
|
||||
var item: ItemDataType = (
|
||||
_item_catalog.get_item_by_id(entry_identity)
|
||||
if _item_catalog != null else null
|
||||
)
|
||||
if item == null:
|
||||
return
|
||||
_icon.texture = item.icon
|
||||
var quantity := _bag.get_quantity(entry_identity) if _bag != null else 0
|
||||
_quantity.text = "×%d" % quantity if quantity > 1 else ""
|
||||
_context_text = _item_context_text(item, quantity)
|
||||
accessibility_name = "%s, slot %d" % [item.display_name, slot_index + 1]
|
||||
elif entry_kind == PlayerInventoryLayout.EntryKind.CATCH:
|
||||
var fish_catch = (
|
||||
_fish_inventory.get_catch_by_id(entry_identity)
|
||||
if _fish_inventory != null else null
|
||||
)
|
||||
if fish_catch == null:
|
||||
return
|
||||
_icon.texture = fish_catch.fish.display_texture
|
||||
_context_text = _catch_context_text(fish_catch)
|
||||
var catch_name: String = FishQuality.qualified_name(
|
||||
fish_catch.fish.display_name, fish_catch.quality
|
||||
)
|
||||
accessibility_name = "%s, slot %d" % [catch_name, slot_index + 1]
|
||||
|
||||
|
||||
func _on_pressed() -> void:
|
||||
if _locked:
|
||||
return
|
||||
entry_selected.emit(entry_kind, entry_identity)
|
||||
|
||||
|
||||
func _on_gui_input(event: InputEvent) -> void:
|
||||
var mouse_event := event as InputEventMouseButton
|
||||
if (
|
||||
mouse_event != null
|
||||
and mouse_event.button_index == MOUSE_BUTTON_RIGHT
|
||||
and mouse_event.pressed
|
||||
and not _locked
|
||||
and not entry_identity.is_empty()
|
||||
):
|
||||
context_requested.emit(self)
|
||||
accept_event()
|
||||
|
||||
|
||||
func _set_context_hovered(active: bool) -> void:
|
||||
_context_hovered = active
|
||||
_update_context_presence()
|
||||
|
||||
|
||||
func _set_context_focused(active: bool) -> void:
|
||||
_context_focused = active
|
||||
_update_context_presence()
|
||||
|
||||
|
||||
func _update_context_presence() -> void:
|
||||
if _locked or entry_identity.is_empty():
|
||||
context_changed.emit(self, "", false)
|
||||
return
|
||||
var active: bool = _context_hovered or _context_focused
|
||||
context_changed.emit(self, _context_text if active else "", active)
|
||||
|
||||
|
||||
func set_move_source(active: bool) -> void:
|
||||
modulate = Color(1.0, 1.0, 1.0, 0.42) if active else Color.WHITE
|
||||
|
||||
|
||||
func set_staged(active: bool) -> void:
|
||||
if _staged == active:
|
||||
return
|
||||
_staged = active
|
||||
_apply_style()
|
||||
|
||||
|
||||
func _item_context_text(item: ItemDataType, quantity: int) -> String:
|
||||
var lines: Array[String] = [item.display_name]
|
||||
lines.append("%s • quantity %d" % [item.get_category_name(), quantity])
|
||||
if item.equippable:
|
||||
lines.append("equippable")
|
||||
elif item.usable:
|
||||
lines.append("usable")
|
||||
if not item.description.strip_edges().is_empty():
|
||||
lines.append(item.description.strip_edges())
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
func _catch_context_text(fish_catch: FishCatch) -> String:
|
||||
var catch_name: String = FishQuality.qualified_name(
|
||||
fish_catch.fish.display_name,
|
||||
fish_catch.quality,
|
||||
)
|
||||
return "%s\n%0.2f lb • value %d\n%s" % [
|
||||
catch_name,
|
||||
fish_catch.weight_lb,
|
||||
fish_catch.sale_value,
|
||||
fish_catch.fish.logbook_fact,
|
||||
]
|
||||
|
||||
|
||||
func _get_drag_data(_at_position: Vector2) -> Variant:
|
||||
if _locked or entry_identity.is_empty() or _icon.texture == null:
|
||||
return null
|
||||
var preview := TextureRect.new()
|
||||
preview.custom_minimum_size = Vector2(56.0, 56.0)
|
||||
preview.texture = _icon.texture
|
||||
preview.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
preview.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
preview.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
set_drag_preview(preview)
|
||||
if entry_kind == PlayerInventoryLayout.EntryKind.CATCH:
|
||||
return {
|
||||
"kind": "cooler_fish",
|
||||
"catch_id": String(entry_identity),
|
||||
}
|
||||
return {
|
||||
"kind": "bag_item",
|
||||
"item_id": String(entry_identity),
|
||||
}
|
||||
|
||||
|
||||
func _can_drop_data(_at_position: Vector2, data: Variant) -> bool:
|
||||
if _locked or typeof(data) != TYPE_DICTIONARY or _layout == null:
|
||||
return false
|
||||
var kind := str((data as Dictionary).get("kind", ""))
|
||||
return kind in ["bag_item", "cooler_fish", "hotbar_slot"]
|
||||
|
||||
|
||||
func _drop_data(_at_position: Vector2, data: Variant) -> void:
|
||||
if not _can_drop_data(Vector2.ZERO, data):
|
||||
return
|
||||
var payload := data as Dictionary
|
||||
var payload_kind := str(payload.get("kind", ""))
|
||||
var moved: bool = false
|
||||
if payload_kind == "hotbar_slot":
|
||||
moved = (
|
||||
_hotbar != null
|
||||
and _hotbar.move_slot_to_container(
|
||||
int(payload.get("slot_index", -1)),
|
||||
container,
|
||||
slot_index,
|
||||
)
|
||||
)
|
||||
else:
|
||||
var kind := (
|
||||
PlayerInventoryLayout.EntryKind.CATCH
|
||||
if payload_kind == "cooler_fish"
|
||||
else PlayerInventoryLayout.EntryKind.ITEM
|
||||
)
|
||||
var identity := StringName(str(
|
||||
payload.get(
|
||||
"catch_id" if payload_kind == "cooler_fish" else "item_id",
|
||||
"",
|
||||
)
|
||||
))
|
||||
moved = _layout.move_entry(kind, identity, container, slot_index)
|
||||
if moved:
|
||||
entry_moved.emit()
|
||||
|
||||
|
||||
func _apply_style() -> void:
|
||||
var radius: int = roundi(minf(_presentation_size.x, _presentation_size.y) * 0.5)
|
||||
var normal := UtilityPageStyle.rounded_style(
|
||||
Color(
|
||||
UtilityPageStyle.OCEAN_SELECTED
|
||||
if _staged else UtilityPageStyle.OCEAN_FIELD,
|
||||
0.92 if _staged else 0.88,
|
||||
),
|
||||
radius,
|
||||
)
|
||||
var hover := UtilityPageStyle.rounded_style(
|
||||
Color(UtilityPageStyle.OCEAN_SELECTED, 0.92), radius
|
||||
)
|
||||
var locked := UtilityPageStyle.rounded_style(
|
||||
Color(UtilityPageStyle.OCEAN_FIELD, 0.38), radius
|
||||
)
|
||||
for state: StringName in [&"normal", &"pressed"]:
|
||||
add_theme_stylebox_override(state, normal)
|
||||
add_theme_stylebox_override("disabled", locked)
|
||||
for state: StringName in [&"hover", &"focus"]:
|
||||
add_theme_stylebox_override(state, hover)
|
||||
|
||||
|
||||
func _apply_presentation() -> void:
|
||||
_apply_icon_geometry()
|
||||
var is_large: bool = _presentation_size.x >= 70.0
|
||||
var quantity_width: float = 42.0 if is_large else 34.0
|
||||
var quantity_height: float = 20.0 if is_large else 17.0
|
||||
_quantity.position = Vector2(
|
||||
-quantity_width - (8.0 if is_large else 5.0),
|
||||
-quantity_height - (6.0 if is_large else 4.0),
|
||||
)
|
||||
_quantity.size = Vector2(quantity_width, quantity_height)
|
||||
_quantity.add_theme_font_size_override("font_size", 14 if is_large else 11)
|
||||
_apply_style()
|
||||
|
||||
|
||||
func _apply_icon_geometry() -> void:
|
||||
var is_large: bool = _presentation_size.x >= 70.0
|
||||
var margin: float
|
||||
if _locked:
|
||||
var lock_size: float = 24.0 if is_large else 18.0
|
||||
margin = maxf(
|
||||
(_presentation_size.x - lock_size) * 0.5,
|
||||
0.0,
|
||||
)
|
||||
else:
|
||||
margin = 10.0 if is_large else 7.0
|
||||
_icon.offset_left = margin
|
||||
_icon.offset_top = margin
|
||||
_icon.offset_right = -margin
|
||||
_icon.offset_bottom = -margin
|
||||
1
ui/components/general_inventory_slot.gd.uid
Normal file
1
ui/components/general_inventory_slot.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cgaypm0vokkup
|
||||
|
|
@ -6,7 +6,10 @@ const CONTENT_MARGIN_LEFT := 18.0
|
|||
const CONTENT_MARGIN_TOP := 98.0
|
||||
const CONTENT_MARGIN_RIGHT := 18.0
|
||||
const CONTENT_MARGIN_BOTTOM := 16.0
|
||||
# Retained for the Logbook's separate handwritten presentation. Inventory
|
||||
# notepads deliberately prioritize legibility with Tuffy.
|
||||
const HANDWRITTEN_FONT: Font = preload("res://ui/fonts/seattle_avenue.otf")
|
||||
const NOTEPAD_FONT: Font = preload("res://ui/fonts/Tuffy_Bold.otf")
|
||||
const NOTEPAD_TEXTURE: Texture2D = preload("res://art/ui/ui_notepad.png")
|
||||
const NOTEPAD_CANONICAL_OVERSCAN: float = 1.2
|
||||
const NOTEPAD_ART_OFFSET := Vector2(10.0, 20.0)
|
||||
|
|
@ -24,11 +27,11 @@ func _ready() -> void:
|
|||
|
||||
|
||||
static func apply_handwritten_to(root: Control) -> void:
|
||||
root.add_theme_font_override("font", HANDWRITTEN_FONT)
|
||||
root.add_theme_font_override("font", NOTEPAD_FONT)
|
||||
for descendant: Node in root.find_children("*", "Control", true, false):
|
||||
var control := descendant as Control
|
||||
control.add_theme_font_override(
|
||||
"font", HANDWRITTEN_FONT
|
||||
"font", NOTEPAD_FONT
|
||||
)
|
||||
if control is Label:
|
||||
control.add_theme_color_override("font_color", INK_COLOR)
|
||||
|
|
@ -37,7 +40,7 @@ static func apply_handwritten_to(root: Control) -> void:
|
|||
func _draw() -> void:
|
||||
draw_texture_rect(NOTEPAD_TEXTURE, get_art_rect(), false)
|
||||
|
||||
var font: Font = HANDWRITTEN_FONT
|
||||
var font: Font = NOTEPAD_FONT
|
||||
draw_string(
|
||||
font,
|
||||
Vector2(20.0, 58.0),
|
||||
|
|
|
|||
71
ui/components/shop_sale_tray_slot.gd
Normal file
71
ui/components/shop_sale_tray_slot.gd
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
class_name ShopSaleTraySlot
|
||||
extends Button
|
||||
|
||||
signal remove_requested(key: String)
|
||||
signal drop_requested(payload: Dictionary)
|
||||
|
||||
var entry_key: String = ""
|
||||
var _icon: TextureRect
|
||||
var _quantity: Label
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
custom_minimum_size = GeneralInventoryGrid.DEFAULT_SLOT_SIZE
|
||||
focus_mode = Control.FOCUS_ALL
|
||||
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
pressed.connect(func() -> void: remove_requested.emit(entry_key))
|
||||
_icon = TextureRect.new()
|
||||
_icon.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_icon.offset_left = 6.0
|
||||
_icon.offset_top = 4.0
|
||||
_icon.offset_right = -6.0
|
||||
_icon.offset_bottom = -4.0
|
||||
_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
_icon.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_icon)
|
||||
_quantity = Label.new()
|
||||
_quantity.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||||
_quantity.position = Vector2(-34.0, -20.0)
|
||||
_quantity.size = Vector2(30.0, 16.0)
|
||||
_quantity.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
_quantity.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_quantity)
|
||||
var normal := UtilityPageStyle.rounded_style(
|
||||
Color(UtilityPageStyle.OCEAN_FIELD, 0.96), 26
|
||||
)
|
||||
var hover := UtilityPageStyle.rounded_style(
|
||||
Color(UtilityPageStyle.OCEAN_SELECTED, 0.96), 26
|
||||
)
|
||||
for state: StringName in [&"normal", &"pressed", &"disabled"]:
|
||||
add_theme_stylebox_override(state, normal)
|
||||
for state: StringName in [&"hover", &"focus"]:
|
||||
add_theme_stylebox_override(state, hover)
|
||||
|
||||
|
||||
func configure(
|
||||
key: String,
|
||||
icon: Texture2D,
|
||||
label: String,
|
||||
quantity: int = 1,
|
||||
) -> void:
|
||||
entry_key = key
|
||||
_icon.texture = icon
|
||||
_quantity.text = "×%d" % quantity if quantity > 1 else ""
|
||||
tooltip_text = "%s · select to remove" % label
|
||||
accessibility_name = tooltip_text
|
||||
|
||||
|
||||
func _can_drop_data(_at_position: Vector2, data: Variant) -> bool:
|
||||
return (
|
||||
typeof(data) == TYPE_DICTIONARY
|
||||
and str((data as Dictionary).get("kind", "")) in [
|
||||
"bag_item", "cooler_fish"
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
func _drop_data(_at_position: Vector2, data: Variant) -> void:
|
||||
if _can_drop_data(Vector2.ZERO, data):
|
||||
drop_requested.emit((data as Dictionary).duplicate(true))
|
||||
1
ui/components/shop_sale_tray_slot.gd.uid
Normal file
1
ui/components/shop_sale_tray_slot.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://w87v5n2xm64p
|
||||
|
|
@ -104,7 +104,7 @@ const SHOP_SECTION_LABELS: Array[String] = [
|
|||
"Snacks",
|
||||
"Equipment",
|
||||
"Art Supplies",
|
||||
"Sell Fish",
|
||||
"Sell",
|
||||
]
|
||||
const SUPPLY_ICON_GRID_COLUMNS: int = 9
|
||||
const SUPPLY_ICON_TILE_SIZE := Vector2(72.0, 72.0)
|
||||
|
|
@ -154,6 +154,9 @@ const ROD_PRICE_ICON_SIZE: float = 22.0
|
|||
@onready var _cooler_cost: CurrencyAmount = %CoolerCost
|
||||
@onready var _cooler_price_bubble: PanelContainer = %CoolerPriceBubble
|
||||
@onready var _cooler_purchase: Button = %CoolerPurchase
|
||||
@onready var _backpack_cost: CurrencyAmount = %BackpackCost
|
||||
@onready var _backpack_price_bubble: PanelContainer = %BackpackPriceBubble
|
||||
@onready var _backpack_purchase: Button = %BackpackPurchase
|
||||
|
||||
var _player: PlayerType
|
||||
var _wallet: PlayerWalletType
|
||||
|
|
@ -162,10 +165,16 @@ var _upgrades: PlayerFishingUpgradesType
|
|||
var _fishing_spot: FishingSpotType
|
||||
var _interaction: ShopInteractionType
|
||||
var _bag: PlayerBagType
|
||||
var _inventory: FishInventory
|
||||
var _hotbar: PlayerHotbar
|
||||
var _inventory_layout: PlayerInventoryLayout
|
||||
var _item_catalog: ItemCatalogType
|
||||
var _cooler_capacity: PlayerCoolerCapacityType
|
||||
var _art_unlocks: PlayerArtUnlocksType
|
||||
var _network_shop: NetworkShopService
|
||||
var _network_sale: NetworkSaleService
|
||||
var _reservations: PlayerAssetReservationService
|
||||
var _sell_inventory: ShopSellInventory
|
||||
var _prior_movement_enabled: bool = true
|
||||
var _prior_camera_enabled: bool = true
|
||||
var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
|
||||
|
|
@ -191,6 +200,7 @@ func _ready() -> void:
|
|||
_reel_purchase.pressed.connect(_purchase_reel_speed)
|
||||
_barrier_purchase.pressed.connect(_purchase_barrier_power)
|
||||
_cooler_purchase.pressed.connect(_purchase_cooler_capacity)
|
||||
_backpack_purchase.pressed.connect(_purchase_backpack_capacity)
|
||||
|
||||
|
||||
func setup_controller_mapping(
|
||||
|
|
@ -327,7 +337,9 @@ func _request_shop_cooler() -> bool:
|
|||
if not _is_transaction_context_valid():
|
||||
_set_feedback("The fishing shop is no longer available.")
|
||||
return false
|
||||
sell_fish_requested.emit()
|
||||
activate_shop_cooler_page()
|
||||
if _sell_inventory != null:
|
||||
_sell_inventory.activate()
|
||||
return _cooler_page_active
|
||||
|
||||
|
||||
|
|
@ -339,6 +351,7 @@ func _focus_shop_section() -> void:
|
|||
_reel_purchase,
|
||||
_barrier_purchase,
|
||||
_cooler_purchase,
|
||||
_backpack_purchase,
|
||||
]:
|
||||
if not upgrade_button.disabled:
|
||||
upgrade_button.grab_focus()
|
||||
|
|
@ -403,7 +416,7 @@ func _select_shop_section(section_index: int, focus_content: bool) -> void:
|
|||
_update_shop_tab_selection()
|
||||
return
|
||||
if _cooler_page_active:
|
||||
shop_cooler_return_requested.emit()
|
||||
deactivate_shop_cooler_page()
|
||||
_shop_section = section_index as ShopSection
|
||||
var showing_upgrades: bool = _shop_section == ShopSection.UPGRADES
|
||||
_upgrades_content.visible = showing_upgrades
|
||||
|
|
@ -494,6 +507,7 @@ func _apply_shop_styles() -> void:
|
|||
_reel_price_bubble,
|
||||
_barrier_price_bubble,
|
||||
_cooler_price_bubble,
|
||||
_backpack_price_bubble,
|
||||
]:
|
||||
var price_style := UtilityPageStyleType.rounded_style(
|
||||
UtilityPageStyleType.OCEAN_FIELD,
|
||||
|
|
@ -510,6 +524,7 @@ func _apply_shop_styles() -> void:
|
|||
_reel_cost,
|
||||
_barrier_cost,
|
||||
_cooler_cost,
|
||||
_backpack_cost,
|
||||
]:
|
||||
var amount_label := price.get_amount_label()
|
||||
amount_label.add_theme_color_override(
|
||||
|
|
@ -521,6 +536,7 @@ func _apply_shop_styles() -> void:
|
|||
_reel_purchase,
|
||||
_barrier_purchase,
|
||||
_cooler_purchase,
|
||||
_backpack_purchase,
|
||||
]:
|
||||
UtilityPageStyleType.apply_ocean_button(button)
|
||||
_feedback.add_theme_color_override(
|
||||
|
|
@ -536,10 +552,15 @@ func setup(
|
|||
fishing_spot: FishingSpotType,
|
||||
interaction: ShopInteractionType,
|
||||
bag: PlayerBagType,
|
||||
inventory: FishInventory,
|
||||
hotbar: PlayerHotbar,
|
||||
inventory_layout: PlayerInventoryLayout,
|
||||
item_catalog: ItemCatalogType,
|
||||
cooler_capacity: PlayerCoolerCapacityType,
|
||||
art_unlocks: PlayerArtUnlocksType,
|
||||
network_shop: NetworkShopService,
|
||||
network_sale: NetworkSaleService,
|
||||
reservations: PlayerAssetReservationService,
|
||||
) -> void:
|
||||
_player = player
|
||||
_wallet = wallet
|
||||
|
|
@ -548,10 +569,28 @@ func setup(
|
|||
_fishing_spot = fishing_spot
|
||||
_interaction = interaction
|
||||
_bag = bag
|
||||
_inventory = inventory
|
||||
_hotbar = hotbar
|
||||
_inventory_layout = inventory_layout
|
||||
_item_catalog = item_catalog
|
||||
_cooler_capacity = cooler_capacity
|
||||
_art_unlocks = art_unlocks
|
||||
_network_shop = network_shop
|
||||
_network_sale = network_sale
|
||||
_reservations = reservations
|
||||
_sell_inventory = ShopSellInventory.new()
|
||||
_sell_inventory.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_shop_cooler_mount.add_child(_sell_inventory)
|
||||
_sell_inventory.setup(
|
||||
_inventory_layout,
|
||||
_bag,
|
||||
_inventory,
|
||||
_hotbar,
|
||||
_item_catalog,
|
||||
_buyer,
|
||||
_reservations,
|
||||
_network_sale,
|
||||
)
|
||||
if (
|
||||
_network_shop != null
|
||||
and not _network_shop.local_purchase_pending.is_connected(
|
||||
|
|
@ -574,6 +613,12 @@ func setup(
|
|||
_on_cooler_capacity_changed
|
||||
):
|
||||
_cooler_capacity.capacity_changed.connect(_on_cooler_capacity_changed)
|
||||
if not _inventory_layout.backpack_capacity_changed.is_connected(
|
||||
_on_backpack_capacity_changed
|
||||
):
|
||||
_inventory_layout.backpack_capacity_changed.connect(
|
||||
_on_backpack_capacity_changed
|
||||
)
|
||||
if not _art_unlocks.unlocks_changed.is_connected(_on_art_unlocks_changed):
|
||||
_art_unlocks.unlocks_changed.connect(_on_art_unlocks_changed)
|
||||
_refresh_all()
|
||||
|
|
@ -611,6 +656,8 @@ func open_shop() -> bool:
|
|||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
_set_feedback("")
|
||||
deactivate_shop_cooler_page()
|
||||
if _sell_inventory != null:
|
||||
_sell_inventory.clear_staged()
|
||||
show()
|
||||
_shop_tab_bar.show()
|
||||
_controller_zone = ControllerZone.TABS
|
||||
|
|
@ -780,6 +827,7 @@ func _refresh_all() -> void:
|
|||
_refresh_upgrades()
|
||||
_refresh_supplies()
|
||||
_refresh_cooler_capacity()
|
||||
_refresh_backpack_capacity()
|
||||
|
||||
|
||||
func _refresh_wallet() -> void:
|
||||
|
|
@ -1515,25 +1563,60 @@ func _refresh_cooler_capacity() -> void:
|
|||
)
|
||||
var cooler_effect: String
|
||||
if cost < 0:
|
||||
cooler_effect = "%d fish · maximum level" % (
|
||||
cooler_effect = "%d slots · maximum level" % (
|
||||
_cooler_capacity.get_capacity()
|
||||
)
|
||||
_cooler_price_bubble.hide()
|
||||
else:
|
||||
_cooler_price_bubble.show()
|
||||
cooler_effect = "%d → %d fish" % [
|
||||
cooler_effect = "%d → %d slots" % [
|
||||
_cooler_capacity.get_capacity(),
|
||||
next_capacity,
|
||||
]
|
||||
_cooler_cost.set_amount(cost)
|
||||
_cooler_purchase.tooltip_text = _upgrade_tooltip(
|
||||
"cooler capacity",
|
||||
"storage capacity",
|
||||
level,
|
||||
cooler_effect,
|
||||
)
|
||||
_cooler_purchase.accessibility_name = _cooler_purchase.tooltip_text
|
||||
|
||||
|
||||
func _refresh_backpack_capacity() -> void:
|
||||
if _inventory_layout == null:
|
||||
return
|
||||
var level := _inventory_layout.get_backpack_level()
|
||||
var cost := _inventory_layout.get_next_backpack_cost()
|
||||
var next_capacity := _inventory_layout.get_next_inventory_capacity()
|
||||
_backpack_purchase.disabled = (
|
||||
cost < 0
|
||||
or _transaction_in_progress
|
||||
or _closing
|
||||
or _network_shop == null
|
||||
or not _network_shop.can_request_backpack_purchase()
|
||||
or not _inventory_layout.can_purchase_backpack(_wallet)
|
||||
)
|
||||
var effect: String
|
||||
if cost < 0:
|
||||
effect = "%d slots · maximum level" % (
|
||||
_inventory_layout.get_inventory_capacity()
|
||||
)
|
||||
_backpack_price_bubble.hide()
|
||||
else:
|
||||
_backpack_price_bubble.show()
|
||||
effect = "%d → %d slots" % [
|
||||
_inventory_layout.get_inventory_capacity(),
|
||||
next_capacity,
|
||||
]
|
||||
_backpack_cost.set_amount(cost)
|
||||
_backpack_purchase.tooltip_text = _upgrade_tooltip(
|
||||
"backpack capacity",
|
||||
level,
|
||||
effect,
|
||||
)
|
||||
_backpack_purchase.accessibility_name = _backpack_purchase.tooltip_text
|
||||
|
||||
|
||||
func _purchase_reel_speed() -> void:
|
||||
_purchase_upgrade(true)
|
||||
|
||||
|
|
@ -1555,7 +1638,7 @@ func _purchase_supply(item_id: StringName) -> void:
|
|||
item_id, owned, item.max_stack
|
||||
)
|
||||
if quantity <= 0 or not _bag.can_add_item(item_id, quantity):
|
||||
_set_feedback("Your Bag is full.")
|
||||
_set_feedback("Your inventory is full.")
|
||||
return
|
||||
var total_cost: int = FishingShopStockType.get_purchase_cost(
|
||||
item_id, quantity, _bag.is_bait_unlocked(item_id)
|
||||
|
|
@ -1617,6 +1700,23 @@ func _purchase_cooler_capacity() -> void:
|
|||
_network_shop.request_cooler_capacity_upgrade()
|
||||
|
||||
|
||||
func _purchase_backpack_capacity() -> void:
|
||||
if _transaction_in_progress or not _is_transaction_context_valid():
|
||||
_set_feedback("unable to complete purchase.")
|
||||
return
|
||||
var cost := _inventory_layout.get_next_backpack_cost()
|
||||
if cost < 0:
|
||||
_set_feedback("Upgrade is already at maximum.")
|
||||
return
|
||||
if not _wallet.can_afford(cost):
|
||||
_set_feedback("Insufficient funds.")
|
||||
return
|
||||
if _network_shop == null:
|
||||
_set_feedback("Purchase could not be completed.")
|
||||
return
|
||||
_network_shop.request_backpack_capacity_upgrade()
|
||||
|
||||
|
||||
func _purchase_upgrade(is_reel_speed: bool) -> void:
|
||||
if _transaction_in_progress or not _is_transaction_context_valid():
|
||||
_set_feedback("unable to complete purchase.")
|
||||
|
|
@ -1721,6 +1821,7 @@ func _on_wallet_changed(_balance: int, _delta: int) -> void:
|
|||
_refresh_upgrades()
|
||||
_refresh_supplies()
|
||||
_refresh_cooler_capacity()
|
||||
_refresh_backpack_capacity()
|
||||
|
||||
|
||||
func _on_upgrades_changed(
|
||||
|
|
@ -1738,6 +1839,10 @@ func _on_cooler_capacity_changed(_level: int, _capacity: int) -> void:
|
|||
_refresh_cooler_capacity()
|
||||
|
||||
|
||||
func _on_backpack_capacity_changed(_level: int, _capacity: int) -> void:
|
||||
_refresh_backpack_capacity()
|
||||
|
||||
|
||||
func _on_art_unlocks_changed(_unlock_mask: int) -> void:
|
||||
_refresh_supplies()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
[gd_scene load_steps=9 format=3]
|
||||
[gd_scene load_steps=8 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/fishing_shop.gd" id="1_script"]
|
||||
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
|
||||
[ext_resource type="Texture2D" path="res://items/icons/shop/64_speed_plus.png" id="3_reel"]
|
||||
[ext_resource type="Texture2D" path="res://items/icons/shop/64_power_plus.png" id="4_barrier"]
|
||||
[ext_resource type="Texture2D" path="res://items/icons/shop/32_currency.png" id="5_coin"]
|
||||
[ext_resource type="Texture2D" path="res://items/icons/equipment/64_cooler_plus.png" id="6_cooler"]
|
||||
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/x_light.png" id="7_close"]
|
||||
[ext_resource type="PackedScene" path="res://ui/components/currency_amount.tscn" id="8_currency"]
|
||||
|
||||
|
|
@ -269,10 +268,10 @@ mouse_filter = 2
|
|||
unique_name_in_owner = true
|
||||
offset_right = 144.0
|
||||
offset_bottom = 144.0
|
||||
tooltip_text = "cooler capacity"
|
||||
icon = ExtResource("6_cooler")
|
||||
tooltip_text = "storage capacity"
|
||||
icon = ExtResource("7_close")
|
||||
expand_icon = true
|
||||
icon_max_width = 128
|
||||
icon_max_width = 92
|
||||
|
||||
[node name="CoolerPriceBubble" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/CoolerTile"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -288,3 +287,32 @@ unique_name_in_owner = true
|
|||
layout_mode = 2
|
||||
amount = 75
|
||||
icon_size = 24.0
|
||||
|
||||
[node name="BackpackTile" type="Control" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid"]
|
||||
custom_minimum_size = Vector2(144, 168)
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="BackpackPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/BackpackTile"]
|
||||
unique_name_in_owner = true
|
||||
offset_right = 144.0
|
||||
offset_bottom = 144.0
|
||||
tooltip_text = "backpack capacity"
|
||||
icon = ExtResource("7_close")
|
||||
expand_icon = true
|
||||
icon_max_width = 92
|
||||
|
||||
[node name="BackpackPriceBubble" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/BackpackTile"]
|
||||
unique_name_in_owner = true
|
||||
z_index = 2
|
||||
offset_left = 14.0
|
||||
offset_top = 128.0
|
||||
offset_right = 130.0
|
||||
offset_bottom = 160.0
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="BackpackCost" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/BackpackTile/BackpackPriceBubble" instance=ExtResource("8_currency")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
amount = 1500
|
||||
icon_size = 24.0
|
||||
|
|
|
|||
131
ui/game_ui.gd
131
ui/game_ui.gd
|
|
@ -27,6 +27,10 @@ const PlayerFishingUpgradesType = preload(
|
|||
const ShopInteractionType = preload(
|
||||
"res://world/fishing_shop_interaction.gd"
|
||||
)
|
||||
const PlayerStorageType = preload("res://ui/player_storage.gd")
|
||||
const PlayerStorageInteractionType = preload(
|
||||
"res://world/player_storage_interaction.gd"
|
||||
)
|
||||
const PlayerItemEffectsType = preload(
|
||||
"res://progression/player_item_effects.gd"
|
||||
)
|
||||
|
|
@ -134,6 +138,8 @@ const SHOP_NPC_SPEECH_COOLDOWN_MILLISECONDS: int = 5000
|
|||
@onready var _pause_menu: PauseMenuType = %PauseMenu
|
||||
@onready var _hotbar_ui: HotbarUIType = %Hotbar
|
||||
@onready var _fishing_shop: FishingShopType = %FishingShop
|
||||
@onready var _player_storage: PlayerStorageType = %PlayerStorage
|
||||
@onready var _storage_prompt: PanelContainer = %StoragePrompt
|
||||
@onready var _shop_prompt: Control = %ShopPrompt
|
||||
@onready var _shop_prompt_bubble: PanelContainer = %ShopPromptBubble
|
||||
@onready var _shop_prompt_message: Label = %ShopPromptMessage
|
||||
|
|
@ -174,10 +180,12 @@ var _gameplay_hud_hidden: bool = false
|
|||
var _fishing_spot: FishingSpotType
|
||||
var _system_menu_open: bool = false
|
||||
var _shop_open: bool = false
|
||||
var _storage_open: bool = false
|
||||
var _chat_input_open: bool = false
|
||||
var _player_menu_hotbar_visible: bool = false
|
||||
var _main_shop_buyer: FishBuyerProfileType
|
||||
var _shop_interaction: ShopInteractionType
|
||||
var _storage_interaction: PlayerStorageInteractionType
|
||||
var _surface_drawing: NetworkSurfaceDrawingService
|
||||
var _surface_drawing_hotbar_selected: bool = false
|
||||
var _experience: PlayerExperienceType
|
||||
|
|
@ -294,10 +302,12 @@ func setup(
|
|||
fishing_spot: FishingSpotType,
|
||||
bag: PlayerBagType,
|
||||
hotbar: PlayerHotbarType,
|
||||
inventory_layout: PlayerInventoryLayout,
|
||||
item_catalog: ItemCatalogType,
|
||||
main_shop_buyer: FishBuyerProfileType,
|
||||
fishing_upgrades: PlayerFishingUpgradesType,
|
||||
shop_interaction: ShopInteractionType,
|
||||
storage_interaction: PlayerStorageInteractionType,
|
||||
item_effects: PlayerItemEffectsType,
|
||||
cooler_capacity: PlayerCoolerCapacityType,
|
||||
network_session: NetworkSessionType,
|
||||
|
|
@ -398,6 +408,7 @@ func setup(
|
|||
fishing_spot,
|
||||
bag,
|
||||
hotbar,
|
||||
inventory_layout,
|
||||
item_catalog,
|
||||
cooler_capacity,
|
||||
network_session,
|
||||
|
|
@ -430,10 +441,28 @@ func setup(
|
|||
fishing_spot,
|
||||
shop_interaction,
|
||||
bag,
|
||||
inventory,
|
||||
hotbar,
|
||||
inventory_layout,
|
||||
item_catalog,
|
||||
cooler_capacity,
|
||||
art_unlocks,
|
||||
network_shop_service,
|
||||
network_sale_service,
|
||||
reservations,
|
||||
)
|
||||
_player_storage.setup(
|
||||
player,
|
||||
fishing_spot,
|
||||
storage_interaction,
|
||||
inventory_layout,
|
||||
bag,
|
||||
inventory,
|
||||
hotbar,
|
||||
item_catalog,
|
||||
)
|
||||
_player_storage.menu_visibility_changed.connect(
|
||||
_on_storage_visibility_changed
|
||||
)
|
||||
_fishing_shop.menu_visibility_changed.connect(_on_shop_visibility_changed)
|
||||
_fishing_shop.menu_exit_started.connect(_on_shop_exit_started)
|
||||
|
|
@ -446,6 +475,7 @@ func setup(
|
|||
)
|
||||
_main_shop_buyer = main_shop_buyer
|
||||
_shop_interaction = shop_interaction
|
||||
_storage_interaction = storage_interaction
|
||||
_surface_drawing = surface_drawing
|
||||
_surface_drawing_toolbar.setup(_surface_drawing, art_unlocks)
|
||||
set_edge_docks(
|
||||
|
|
@ -505,6 +535,7 @@ func _input(event: InputEvent) -> void:
|
|||
and not _system_menu_open
|
||||
and not _player_menu_open
|
||||
and not _shop_open
|
||||
and not _storage_open
|
||||
and not _chat_input_open
|
||||
and not _showcase_active
|
||||
and not _virtual_mouse_active
|
||||
|
|
@ -552,6 +583,7 @@ func _can_use_character_call() -> bool:
|
|||
and not _system_menu_open
|
||||
and not _player_menu_open
|
||||
and not _shop_open
|
||||
and not _storage_open
|
||||
and not _chat_input_open
|
||||
and not _showcase_active
|
||||
and not _virtual_mouse_active
|
||||
|
|
@ -565,8 +597,16 @@ func _character_call_yields_to_world_interaction(
|
|||
) -> bool:
|
||||
return (
|
||||
event.is_action_pressed("interact")
|
||||
and _shop_interaction != null
|
||||
and _shop_interaction.is_local_player_in_range()
|
||||
and (
|
||||
(
|
||||
_shop_interaction != null
|
||||
and _shop_interaction.is_local_player_in_range()
|
||||
)
|
||||
or (
|
||||
_storage_interaction != null
|
||||
and _storage_interaction.is_local_player_in_range()
|
||||
)
|
||||
)
|
||||
and _fishing_spot != null
|
||||
and _fishing_spot.can_open_fishing_shop()
|
||||
and not _fishing_shop.visible
|
||||
|
|
@ -614,6 +654,7 @@ func _handle_controller_chat_controls(event: InputEvent) -> bool:
|
|||
or _system_menu_open
|
||||
or _player_menu_open
|
||||
or _shop_open
|
||||
or _storage_open
|
||||
):
|
||||
return false
|
||||
var use_mapping: bool = (
|
||||
|
|
@ -825,6 +866,7 @@ func _can_start_virtual_mouse() -> bool:
|
|||
and not _system_menu_open
|
||||
and not _player_menu_open
|
||||
and not _shop_open
|
||||
and not _storage_open
|
||||
and not _chat_input_open
|
||||
and _player != null
|
||||
and _fishing_spot != null
|
||||
|
|
@ -1182,6 +1224,7 @@ func _can_surface_drawing_be_active() -> bool:
|
|||
and not _system_menu_open
|
||||
and not _player_menu_open
|
||||
and not _shop_open
|
||||
and not _storage_open
|
||||
and not _chat_input_open
|
||||
and not _showcase_active
|
||||
and _fishing_spot != null
|
||||
|
|
@ -1460,6 +1503,7 @@ func _refresh_active_bait_indicator_visibility() -> void:
|
|||
and not _system_menu_open
|
||||
and not _player_menu_open
|
||||
and not _shop_open
|
||||
and not _storage_open
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -1494,8 +1538,10 @@ func set_gameplay_ui_enabled(enabled: bool) -> void:
|
|||
_surface_drawing.deactivate()
|
||||
close_player_menu_for_session_end()
|
||||
_fishing_shop.close_for_session_end()
|
||||
_player_storage.close_for_session_end()
|
||||
_fishing_panel.visible = false
|
||||
_shop_prompt.hide()
|
||||
_storage_prompt.hide()
|
||||
_hotbar_ui.set_presentation_visible(false, false)
|
||||
_hotbar_ui.set_gameplay_input_enabled(false)
|
||||
else:
|
||||
|
|
@ -1526,6 +1572,7 @@ func _can_toggle_gameplay_hud() -> bool:
|
|||
and not _system_menu_open
|
||||
and not _player_menu_open
|
||||
and not _shop_open
|
||||
and not _storage_open
|
||||
and not _chat_input_open
|
||||
and not _showcase_active
|
||||
and not _emote_radial_menu.is_open()
|
||||
|
|
@ -1540,6 +1587,7 @@ func _refresh_gameplay_hud_visibility() -> void:
|
|||
and not _system_menu_open
|
||||
and not _player_menu_open
|
||||
and not _shop_open
|
||||
and not _storage_open
|
||||
)
|
||||
_gameplay_transient_hud.visible = show_world_hud
|
||||
_experience_presentation.visible = (
|
||||
|
|
@ -1558,11 +1606,13 @@ func set_system_menu_open(is_open: bool) -> void:
|
|||
and not is_open
|
||||
and not _player_menu_open
|
||||
and not _shop_open
|
||||
and not _storage_open
|
||||
)
|
||||
_hotbar_ui.set_drag_enabled(_player_menu_open and not is_open)
|
||||
if is_open:
|
||||
_hotbar_ui.set_drag_enabled(false)
|
||||
_shop_prompt.hide()
|
||||
_storage_prompt.hide()
|
||||
_emit_interactive_pointer_ui_changed()
|
||||
|
||||
|
||||
|
|
@ -1570,6 +1620,53 @@ func get_fishing_shop() -> FishingShopType:
|
|||
return _fishing_shop
|
||||
|
||||
|
||||
func get_player_storage() -> PlayerStorageType:
|
||||
return _player_storage
|
||||
|
||||
|
||||
func set_storage_prompt_visible(
|
||||
requested_visible: bool,
|
||||
world_anchor: Vector3 = Vector3(0.0, INF, 0.0),
|
||||
) -> void:
|
||||
if not _storage_prompt.has_meta(&"styled"):
|
||||
_storage_prompt.set_meta(&"styled", true)
|
||||
var style := UtilityPageStyle.rounded_style(
|
||||
Color(UtilityPageStyle.OCEAN_PANEL_MID, 0.96), 12
|
||||
)
|
||||
style.anti_aliasing = false
|
||||
_storage_prompt.add_theme_stylebox_override("panel", style)
|
||||
_storage_prompt.visible = (
|
||||
requested_visible
|
||||
and _gameplay_ui_enabled
|
||||
and not _system_menu_open
|
||||
and not _player_menu_open
|
||||
and not _shop_open
|
||||
and not _storage_open
|
||||
)
|
||||
if _storage_prompt.visible and world_anchor.is_finite():
|
||||
_position_storage_prompt(world_anchor)
|
||||
|
||||
|
||||
func _position_storage_prompt(world_anchor: Vector3) -> void:
|
||||
if _player == null:
|
||||
_storage_prompt.hide()
|
||||
return
|
||||
var camera := _player.get_gameplay_camera()
|
||||
if camera == null or camera.is_position_behind(world_anchor):
|
||||
_storage_prompt.hide()
|
||||
return
|
||||
var camera_size := camera.get_viewport().get_visible_rect().size
|
||||
var ui_size := _canonical_stage.size
|
||||
if camera_size.x <= 0.0 or camera_size.y <= 0.0:
|
||||
_storage_prompt.hide()
|
||||
return
|
||||
var point := camera.unproject_position(world_anchor) * ui_size / camera_size
|
||||
_storage_prompt.position = Vector2(
|
||||
clampf(point.x - _storage_prompt.size.x * 0.5, 8.0, ui_size.x - _storage_prompt.size.x - 8.0),
|
||||
clampf(point.y - _storage_prompt.size.y - 12.0, 8.0, ui_size.y - _storage_prompt.size.y - 8.0),
|
||||
)
|
||||
|
||||
|
||||
func set_shop_prompt_visible(
|
||||
requested_visible: bool,
|
||||
world_anchor: Vector3 = Vector3(0.0, INF, 0.0),
|
||||
|
|
@ -1581,6 +1678,7 @@ func set_shop_prompt_visible(
|
|||
and not _system_menu_open
|
||||
and not _player_menu_open
|
||||
and not _shop_open
|
||||
and not _storage_open
|
||||
)
|
||||
if _shop_prompt.visible:
|
||||
if world_anchor.is_finite():
|
||||
|
|
@ -2216,9 +2314,30 @@ func _on_shop_visibility_changed(is_open: bool) -> void:
|
|||
and not is_open
|
||||
and not _system_menu_open
|
||||
and not _player_menu_open
|
||||
and not _storage_open
|
||||
)
|
||||
if is_open:
|
||||
_shop_prompt.hide()
|
||||
_storage_prompt.hide()
|
||||
_emit_interactive_pointer_ui_changed()
|
||||
|
||||
|
||||
func _on_storage_visibility_changed(is_open: bool) -> void:
|
||||
_storage_open = is_open
|
||||
_refresh_surface_drawing_activation()
|
||||
_refresh_gameplay_hud_visibility()
|
||||
_refresh_chat_availability()
|
||||
_refresh_hotbar_visibility()
|
||||
_hotbar_ui.set_gameplay_input_enabled(
|
||||
_gameplay_ui_enabled
|
||||
and not is_open
|
||||
and not _system_menu_open
|
||||
and not _player_menu_open
|
||||
and not _shop_open
|
||||
)
|
||||
if is_open:
|
||||
_shop_prompt.hide()
|
||||
_storage_prompt.hide()
|
||||
_emit_interactive_pointer_ui_changed()
|
||||
|
||||
|
||||
|
|
@ -2338,6 +2457,7 @@ func _refresh_hotbar_visibility() -> void:
|
|||
)
|
||||
and not _system_menu_open
|
||||
and not _shop_open
|
||||
and not _storage_open
|
||||
and (
|
||||
not _player_menu_open
|
||||
or _player_menu_hotbar_visible
|
||||
|
|
@ -2349,7 +2469,11 @@ func _refresh_hotbar_visibility() -> void:
|
|||
func _emit_interactive_pointer_ui_changed() -> void:
|
||||
_refresh_active_bait_indicator_visibility()
|
||||
interactive_pointer_ui_changed.emit(
|
||||
_system_menu_open or _player_menu_open or _shop_open or _chat_input_open
|
||||
_system_menu_open
|
||||
or _player_menu_open
|
||||
or _shop_open
|
||||
or _storage_open
|
||||
or _chat_input_open
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -2368,6 +2492,7 @@ func _refresh_chat_availability() -> void:
|
|||
and not _system_menu_open
|
||||
and not _player_menu_open
|
||||
and not _shop_open
|
||||
and not _storage_open
|
||||
)
|
||||
_chat_ui.set_available(chat_available)
|
||||
_chat_ui.set_hud_hidden(_gameplay_hud_hidden and not _chat_input_open)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
[ext_resource type="PackedScene" path="res://ui/surface_drawing_toolbar.tscn" id="11_drawing_toolbar"]
|
||||
[ext_resource type="PackedScene" path="res://ui/quick_radial_menu.tscn" id="12_quick"]
|
||||
[ext_resource type="Script" path="res://ui/controller_virtual_cursor.gd" id="13_cursor"]
|
||||
[ext_resource type="PackedScene" path="res://ui/player_storage.tscn" id="14_storage"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBox_chase_background"]
|
||||
bg_color = Color(0.032, 0.118, 0.15, 1)
|
||||
|
|
@ -471,6 +472,32 @@ polygon = PackedVector2Array(-8, 0, 8, 0, 0, 10)
|
|||
[node name="FishingShop" parent="UIRoot/CanonicalStage" instance=ExtResource("8_shop")]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="PlayerStorage" parent="UIRoot/CanonicalStage" instance=ExtResource("14_storage")]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="StoragePrompt" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = 56
|
||||
offset_right = 220.0
|
||||
offset_bottom = 50.0
|
||||
mouse_filter = 2
|
||||
theme = ExtResource("3_theme")
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/StoragePrompt"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 12
|
||||
theme_override_constants/margin_top = 7
|
||||
theme_override_constants/margin_right = 12
|
||||
theme_override_constants/margin_bottom = 7
|
||||
|
||||
[node name="Message" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/StoragePrompt/Margin"]
|
||||
layout_mode = 2
|
||||
text = "E open storage"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
theme_override_font_sizes/font_size = 18
|
||||
|
||||
[node name="ChatUI" type="Control" parent="UIRoot"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ const DESKTOP_REFERENCE_SIZE := Vector2(1280.0, 720.0)
|
|||
const COMPACT_REFERENCE_SIZE := Vector2(640.0, 480.0)
|
||||
const HOTBAR_PRESENTATION_SCALE: float = 0.80
|
||||
const HOTBAR_CANONICAL_POSITION := Vector2.ZERO
|
||||
const HOTBAR_MENU_POSITION := Vector2(-136.0, -90.0)
|
||||
# In inventory context the row intentionally straddles the panel's bottom
|
||||
# edge, visually extending the storage layout into its nine quick-access slots.
|
||||
const HOTBAR_MENU_POSITION := Vector2(0.0, -30.0)
|
||||
const HOTBAR_GAMEPLAY_Z_INDEX: int = 35
|
||||
# PlayerMenu is z=30 and its authored inventory panels are relative z=50.
|
||||
const HOTBAR_MENU_Z_INDEX: int = 90
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ static func category_label(category: Category) -> String:
|
|||
Category.SHELLFISH:
|
||||
return "Shellfish"
|
||||
_:
|
||||
return "Misc"
|
||||
return "Insects"
|
||||
|
||||
|
||||
static func empty_state(category: Category) -> String:
|
||||
|
|
|
|||
103
ui/mail_page.gd
103
ui/mail_page.gd
|
|
@ -18,6 +18,13 @@ const FishQualityType = preload("res://fish/fish_quality.gd")
|
|||
const CurrencyPresentationType = preload(
|
||||
"res://ui/currency_presentation.gd"
|
||||
)
|
||||
const INBOX_ENTRY_WIDTH: float = 742.0
|
||||
const ATTACHMENT_COLUMN_X: float = 516.0
|
||||
const ATTACHMENT_COLUMN_WIDTH: float = 266.0
|
||||
const AMOUNT_FIELD_X_WITH_CURRENCY: float = 594.0
|
||||
const AMOUNT_FIELD_X_PLAIN: float = 570.0
|
||||
const AMOUNT_FIELD_WIDTH_WITH_CURRENCY: float = 130.0
|
||||
const AMOUNT_FIELD_WIDTH_PLAIN: float = 154.0
|
||||
|
||||
var _service: NetworkMailService
|
||||
var _reservations: PlayerAssetReservationService
|
||||
|
|
@ -434,7 +441,7 @@ func _build_ui() -> void:
|
|||
root.add_child(page)
|
||||
_status = Label.new()
|
||||
_status.position = Vector2(18, 408)
|
||||
_status.size = Vector2(1024, 28)
|
||||
_status.size = Vector2(758, 28)
|
||||
_status.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_status.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||
|
|
@ -449,19 +456,19 @@ func _build_inbox() -> Control:
|
|||
var title := Label.new()
|
||||
title.text = "mail"
|
||||
title.position = Vector2(12, 0)
|
||||
title.size = Vector2(500, 42)
|
||||
title.size = Vector2(300, 42)
|
||||
title.add_theme_font_size_override("font_size", 30)
|
||||
page.add_child(title)
|
||||
_send_mail_button = Button.new()
|
||||
_send_mail_button.text = "send mail"
|
||||
_send_mail_button.position = Vector2(820, 0)
|
||||
_send_mail_button.size = Vector2(150, 48)
|
||||
_send_mail_button.position = Vector2(642, 0)
|
||||
_send_mail_button.size = Vector2(140, 48)
|
||||
_send_mail_button.pressed.connect(_show_compose)
|
||||
page.add_child(_send_mail_button)
|
||||
_archive_view_button = Button.new()
|
||||
_archive_view_button.text = "archive"
|
||||
_archive_view_button.position = Vector2(654, 0)
|
||||
_archive_view_button.size = Vector2(154, 48)
|
||||
_archive_view_button.position = Vector2(490, 0)
|
||||
_archive_view_button.size = Vector2(140, 48)
|
||||
_archive_view_button.pressed.connect(func() -> void:
|
||||
_showing_archive = not _showing_archive
|
||||
_archive_view_button.text = "inbox" if _showing_archive else "archive"
|
||||
|
|
@ -470,16 +477,16 @@ func _build_inbox() -> Control:
|
|||
page.add_child(_archive_view_button)
|
||||
var scroll := ScrollContainer.new()
|
||||
scroll.position = Vector2(12, 62)
|
||||
scroll.size = Vector2(1036, 334)
|
||||
scroll.size = Vector2(770, 334)
|
||||
page.add_child(scroll)
|
||||
_inbox_list = VBoxContainer.new()
|
||||
_inbox_list.custom_minimum_size = Vector2(1008, 0)
|
||||
_inbox_list.custom_minimum_size = Vector2(INBOX_ENTRY_WIDTH, 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(1008, 80)
|
||||
_empty_label.custom_minimum_size = Vector2(INBOX_ENTRY_WIDTH, 80)
|
||||
_inbox_list.add_child(_empty_label)
|
||||
return page
|
||||
|
||||
|
|
@ -494,43 +501,43 @@ func _build_compose() -> Control:
|
|||
page.add_child(title)
|
||||
_greeting = OptionButton.new()
|
||||
_greeting.position = Vector2(12, 48)
|
||||
_greeting.size = Vector2(180, 46)
|
||||
_greeting.size = Vector2(140, 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)
|
||||
_recipient.position = Vector2(164, 48)
|
||||
_recipient.size = Vector2(328, 46)
|
||||
page.add_child(_recipient)
|
||||
_body = TextEdit.new()
|
||||
_body.position = Vector2(12, 106)
|
||||
_body.size = Vector2(650, 218)
|
||||
_body.size = Vector2(480, 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)
|
||||
_salutation.size = Vector2(180, 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)
|
||||
_signature.position = Vector2(204, 342)
|
||||
_signature.size = Vector2(288, 36)
|
||||
page.add_child(_signature)
|
||||
_attachment_kind = OptionButton.new()
|
||||
_attachment_kind.position = Vector2(688, 48)
|
||||
_attachment_kind.size = Vector2(280, 46)
|
||||
_attachment_kind.position = Vector2(ATTACHMENT_COLUMN_X, 48)
|
||||
_attachment_kind.size = Vector2(ATTACHMENT_COLUMN_WIDTH, 46)
|
||||
for label: String in ["No gift", "Currency", "Fish", "Item"]:
|
||||
_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.position = Vector2(ATTACHMENT_COLUMN_X, 106)
|
||||
_attachment_choice.size = Vector2(ATTACHMENT_COLUMN_WIDTH, 46)
|
||||
_attachment_choice.item_selected.connect(
|
||||
func(_index: int) -> void:
|
||||
_update_attachment_amount_limit()
|
||||
|
|
@ -539,16 +546,16 @@ func _build_compose() -> Control:
|
|||
page.add_child(_attachment_choice)
|
||||
_coin_available_heading = Label.new()
|
||||
_coin_available_heading.text = "Available"
|
||||
_coin_available_heading.position = Vector2(688, 158)
|
||||
_coin_available_heading.position = Vector2(ATTACHMENT_COLUMN_X, 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.position = Vector2(598, 158)
|
||||
_coin_available.size = Vector2(184, 28)
|
||||
_coin_available.alignment = BoxContainer.ALIGNMENT_BEGIN
|
||||
page.add_child(_coin_available)
|
||||
_attachment_amount_currency_icon = TextureRect.new()
|
||||
_attachment_amount_currency_icon.position = Vector2(746, 204)
|
||||
_attachment_amount_currency_icon.position = Vector2(570, 204)
|
||||
_attachment_amount_currency_icon.size = Vector2(18, 18)
|
||||
_attachment_amount_currency_icon.texture = preload(
|
||||
"res://items/icons/shop/32_currency.png"
|
||||
|
|
@ -563,27 +570,27 @@ func _build_compose() -> Control:
|
|||
_attachment_amount_currency_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
page.add_child(_attachment_amount_currency_icon)
|
||||
_attachment_amount = LineEdit.new()
|
||||
_attachment_amount.position = Vector2(770, 190)
|
||||
_attachment_amount.size = Vector2(140, 46)
|
||||
_attachment_amount.position = Vector2(AMOUNT_FIELD_X_WITH_CURRENCY, 190)
|
||||
_attachment_amount.size = Vector2(AMOUNT_FIELD_WIDTH_WITH_CURRENCY, 46)
|
||||
_attachment_amount.placeholder_text = "0"
|
||||
_attachment_amount.text = "0"
|
||||
_attachment_amount.text_changed.connect(_on_attachment_amount_changed)
|
||||
page.add_child(_attachment_amount)
|
||||
_amount_minus = Button.new()
|
||||
_amount_minus.text = "−"
|
||||
_amount_minus.position = Vector2(688, 190)
|
||||
_amount_minus.size = Vector2(48, 46)
|
||||
_amount_minus.position = Vector2(ATTACHMENT_COLUMN_X, 190)
|
||||
_amount_minus.size = Vector2(44, 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.position = Vector2(734, 190)
|
||||
_amount_plus.size = Vector2(48, 46)
|
||||
_amount_plus.pressed.connect(_step_attachment_amount.bind(1))
|
||||
page.add_child(_amount_plus)
|
||||
_attachment_summary = RichTextLabel.new()
|
||||
_attachment_summary.position = Vector2(688, 246)
|
||||
_attachment_summary.size = Vector2(280, 78)
|
||||
_attachment_summary.position = Vector2(ATTACHMENT_COLUMN_X, 246)
|
||||
_attachment_summary.size = Vector2(ATTACHMENT_COLUMN_WIDTH, 78)
|
||||
_attachment_summary.bbcode_enabled = true
|
||||
_attachment_summary.fit_content = true
|
||||
_attachment_summary.scroll_active = false
|
||||
|
|
@ -591,14 +598,14 @@ func _build_compose() -> Control:
|
|||
page.add_child(_attachment_summary)
|
||||
_compose_cancel = Button.new()
|
||||
_compose_cancel.text = "cancel"
|
||||
_compose_cancel.position = Vector2(688, 342)
|
||||
_compose_cancel.size = Vector2(126, 48)
|
||||
_compose_cancel.position = Vector2(ATTACHMENT_COLUMN_X, 342)
|
||||
_compose_cancel.size = Vector2(122, 48)
|
||||
_compose_cancel.pressed.connect(_show_inbox)
|
||||
page.add_child(_compose_cancel)
|
||||
_send_button = Button.new()
|
||||
_send_button.text = "send letter"
|
||||
_send_button.position = Vector2(826, 342)
|
||||
_send_button.size = Vector2(142, 48)
|
||||
_send_button.position = Vector2(646, 342)
|
||||
_send_button.size = Vector2(136, 48)
|
||||
_send_button.pressed.connect(_send)
|
||||
page.add_child(_send_button)
|
||||
_recipient.item_selected.connect(func(_i: int) -> void:
|
||||
|
|
@ -613,12 +620,12 @@ 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.size = Vector2(500, 350)
|
||||
_letter_text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_letter_text.vertical_alignment = VERTICAL_ALIGNMENT_TOP
|
||||
page.add_child(_letter_text)
|
||||
_letter_gift = RichTextLabel.new()
|
||||
_letter_gift.position = Vector2(730, 46)
|
||||
_letter_gift.position = Vector2(552, 46)
|
||||
_letter_gift.size = Vector2(230, 180)
|
||||
_letter_gift.bbcode_enabled = true
|
||||
_letter_gift.fit_content = true
|
||||
|
|
@ -627,7 +634,7 @@ func _build_letter() -> Control:
|
|||
page.add_child(_letter_gift)
|
||||
_accept = Button.new()
|
||||
_accept.text = "accept gift"
|
||||
_accept.position = Vector2(730, 250)
|
||||
_accept.position = Vector2(552, 250)
|
||||
_accept.size = Vector2(230, 48)
|
||||
_accept.pressed.connect(func() -> void:
|
||||
_service.accept_gift(_current_mail_id)
|
||||
|
|
@ -635,7 +642,7 @@ func _build_letter() -> Control:
|
|||
page.add_child(_accept)
|
||||
_decline = Button.new()
|
||||
_decline.text = "decline gift"
|
||||
_decline.position = Vector2(730, 308)
|
||||
_decline.position = Vector2(552, 308)
|
||||
_decline.size = Vector2(230, 48)
|
||||
_decline.pressed.connect(func() -> void:
|
||||
_service.decline_gift(_current_mail_id)
|
||||
|
|
@ -649,13 +656,13 @@ func _build_letter() -> Control:
|
|||
page.add_child(_letter_close)
|
||||
_archive = Button.new()
|
||||
_archive.text = "archive"
|
||||
_archive.position = Vector2(550, 370)
|
||||
_archive.position = Vector2(466, 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.position = Vector2(626, 370)
|
||||
_delete.size = Vector2(150, 48)
|
||||
_delete.pressed.connect(_delete_current)
|
||||
page.add_child(_delete)
|
||||
|
|
@ -751,7 +758,7 @@ func _refresh_inbox() -> void:
|
|||
empty.text = (
|
||||
"No archived letters." if _showing_archive else "No letters yet."
|
||||
)
|
||||
empty.custom_minimum_size = Vector2(1008, 80)
|
||||
empty.custom_minimum_size = Vector2(INBOX_ENTRY_WIDTH, 80)
|
||||
empty.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_inbox_list.add_child(empty)
|
||||
call_deferred("_refresh_controller_navigation")
|
||||
|
|
@ -779,7 +786,7 @@ func _refresh_inbox() -> void:
|
|||
first_line.left(72),
|
||||
" · gift enclosed" if gift else "",
|
||||
]
|
||||
button.custom_minimum_size = Vector2(1008, 54)
|
||||
button.custom_minimum_size = Vector2(INBOX_ENTRY_WIDTH, 54)
|
||||
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
button.pressed.connect(_open_letter.bind(str(letter["mail_id"])))
|
||||
UtilityPageStyle.apply_ocean_button(button)
|
||||
|
|
@ -826,8 +833,14 @@ func _refresh_attachment_choices(_index: int) -> void:
|
|||
_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
|
||||
_attachment_amount.position.x = (
|
||||
AMOUNT_FIELD_X_WITH_CURRENCY
|
||||
if currency_selected else AMOUNT_FIELD_X_PLAIN
|
||||
)
|
||||
_attachment_amount.size.x = (
|
||||
AMOUNT_FIELD_WIDTH_WITH_CURRENCY
|
||||
if currency_selected else AMOUNT_FIELD_WIDTH_PLAIN
|
||||
)
|
||||
match _attachment_kind.selected:
|
||||
0:
|
||||
_attachment_choice.add_item("No attachment")
|
||||
|
|
|
|||
1028
ui/player_menu.gd
1028
ui/player_menu.gd
File diff suppressed because it is too large
Load diff
|
|
@ -72,15 +72,16 @@ unique_name_in_owner = true
|
|||
visible = false
|
||||
z_index = 40
|
||||
layout_mode = 0
|
||||
offset_left = 150.0
|
||||
offset_left = 513.0
|
||||
offset_top = 136.0
|
||||
offset_right = 664.0
|
||||
offset_right = 767.0
|
||||
offset_bottom = 174.0
|
||||
mouse_filter = 1
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
[node name="CoolerSubTab" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/InventorySubTabs"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(124, 38)
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
|
|
@ -94,12 +95,13 @@ custom_minimum_size = Vector2(124, 38)
|
|||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_group = null
|
||||
text = "Equipment"
|
||||
text = "Inventory"
|
||||
script = ExtResource("23_organizer_tab")
|
||||
palette_index = 2
|
||||
|
||||
[node name="ItemsSubTab" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/InventorySubTabs"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(124, 38)
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
|
|
@ -494,10 +496,10 @@ mouse_filter = 1
|
|||
unique_name_in_owner = true
|
||||
z_index = 50
|
||||
layout_mode = 0
|
||||
offset_left = 122.0
|
||||
offset_top = 132.0
|
||||
offset_right = 1018.0
|
||||
offset_bottom = 574.0
|
||||
offset_left = 199.0
|
||||
offset_top = 166.0
|
||||
offset_right = 1081.0
|
||||
offset_bottom = 650.0
|
||||
|
||||
[node name="BagOuterMargin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagOuterWall"]
|
||||
layout_mode = 2
|
||||
|
|
@ -524,7 +526,7 @@ horizontal_scroll_mode = 0
|
|||
|
||||
[node name="BagHost" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagOuterWall/BagOuterMargin/BagInnerLiner/BagInnerMargin/BagScroll"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(788, 358)
|
||||
custom_minimum_size = Vector2(810, 420)
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
|
||||
|
|
@ -553,14 +555,26 @@ text = "your bag is empty"
|
|||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="BagModalBlocker" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = 110
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 0
|
||||
|
||||
[node name="BagDetailConstellation" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 0
|
||||
offset_left = 984.0
|
||||
offset_top = 266.0
|
||||
offset_right = 1256.0
|
||||
offset_bottom = 570.0
|
||||
offset_left = 501.0
|
||||
offset_top = 166.0
|
||||
offset_right = 779.0
|
||||
offset_bottom = 650.0
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="BagDetailBubble" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagDetailConstellation"]
|
||||
|
|
@ -572,7 +586,7 @@ anchor_bottom = 1.0
|
|||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("22_inventory_notepad")
|
||||
title_text = "bag notes"
|
||||
title_text = "inventory notes"
|
||||
|
||||
[node name="BagDetailLayout" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagDetailConstellation/BagDetailBubble"]
|
||||
layout_mode = 2
|
||||
|
|
@ -601,6 +615,25 @@ theme_override_font_sizes/font_size = 15
|
|||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="BagDetailActions" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagDetailConstellation/BagDetailBubble/BagDetailLayout"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 6
|
||||
alignment = 1
|
||||
|
||||
[node name="BagFavoriteButton" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagDetailConstellation/BagDetailBubble/BagDetailLayout/BagDetailActions" instance=ExtResource("12_ink_action")]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(104, 50)
|
||||
layout_mode = 2
|
||||
text = "favorite"
|
||||
allow_persistent_mark = true
|
||||
|
||||
[node name="BagSellButton" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagDetailConstellation/BagDetailBubble/BagDetailLayout/BagDetailActions" instance=ExtResource("12_ink_action")]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(104, 50)
|
||||
layout_mode = 2
|
||||
text = "sell fish"
|
||||
|
||||
[node name="TackleBoxPage" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
|
|
@ -613,10 +646,10 @@ mouse_filter = 1
|
|||
unique_name_in_owner = true
|
||||
z_index = 50
|
||||
layout_mode = 0
|
||||
offset_left = 54.0
|
||||
offset_left = 199.0
|
||||
offset_top = 166.0
|
||||
offset_right = 936.0
|
||||
offset_bottom = 602.0
|
||||
offset_right = 1081.0
|
||||
offset_bottom = 650.0
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel"]
|
||||
layout_mode = 2
|
||||
|
|
@ -667,7 +700,7 @@ layout_mode = 2
|
|||
size_flags_horizontal = 3
|
||||
theme_override_constants/h_separation = 28
|
||||
theme_override_constants/v_separation = 12
|
||||
columns = 3
|
||||
columns = 4
|
||||
|
||||
[node name="BaitEmpty" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleColumns/BaitColumn/BaitBody"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -720,7 +753,7 @@ layout_mode = 2
|
|||
size_flags_horizontal = 3
|
||||
theme_override_constants/h_separation = 28
|
||||
theme_override_constants/v_separation = 12
|
||||
columns = 3
|
||||
columns = 4
|
||||
|
||||
[node name="LureEmpty" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleColumns/LureColumn/LureBody"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -735,13 +768,26 @@ text = "No lures collected."
|
|||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="TackleModalBlocker" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = 110
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 0
|
||||
|
||||
[node name="TackleDetailPanel" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 0
|
||||
offset_left = 952.0
|
||||
offset_left = 501.0
|
||||
offset_top = 166.0
|
||||
offset_right = 1230.0
|
||||
offset_bottom = 602.0
|
||||
offset_right = 779.0
|
||||
offset_bottom = 650.0
|
||||
script = ExtResource("22_inventory_notepad")
|
||||
title_text = "tackle box notes"
|
||||
|
||||
|
|
@ -861,9 +907,9 @@ custom_minimum_size = Vector2(140, 44)
|
|||
[node name="NavigationCluster" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 0
|
||||
offset_left = 330.0
|
||||
offset_left = 388.0
|
||||
offset_top = 44.0
|
||||
offset_right = 1170.0
|
||||
offset_right = 1228.0
|
||||
offset_bottom = 144.0
|
||||
mouse_filter = 1
|
||||
script = ExtResource("5_cluster")
|
||||
|
|
|
|||
317
ui/player_storage.gd
Normal file
317
ui/player_storage.gd
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
class_name PlayerStorage
|
||||
extends Control
|
||||
|
||||
const INPUT_OWNER: StringName = &"player_storage"
|
||||
|
||||
signal menu_visibility_changed(is_open: bool)
|
||||
|
||||
var _player: Player
|
||||
var _fishing_spot: FishingSpot
|
||||
var _interaction: PlayerStorageInteraction
|
||||
var _layout: PlayerInventoryLayout
|
||||
var _inventory_grid: GeneralInventoryGrid
|
||||
var _storage_grid: GeneralInventoryGrid
|
||||
var _inventory_count: Label
|
||||
var _storage_count: Label
|
||||
var _feedback: Label
|
||||
var _prior_movement_enabled: bool = true
|
||||
var _prior_camera_enabled: bool = true
|
||||
var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
UtilityPageStyle.apply_page(self)
|
||||
_build_ui()
|
||||
hide()
|
||||
|
||||
|
||||
func setup(
|
||||
player: Player,
|
||||
fishing_spot: FishingSpot,
|
||||
interaction: PlayerStorageInteraction,
|
||||
layout: PlayerInventoryLayout,
|
||||
bag: PlayerBag,
|
||||
fish_inventory: FishInventory,
|
||||
hotbar: PlayerHotbar,
|
||||
item_catalog: ItemCatalog,
|
||||
) -> void:
|
||||
_player = player
|
||||
_fishing_spot = fishing_spot
|
||||
_interaction = interaction
|
||||
_layout = layout
|
||||
_inventory_grid.setup(
|
||||
layout,
|
||||
bag,
|
||||
fish_inventory,
|
||||
hotbar,
|
||||
item_catalog,
|
||||
PlayerInventoryLayout.InventoryContainer.INVENTORY,
|
||||
)
|
||||
_storage_grid.setup(
|
||||
layout,
|
||||
bag,
|
||||
fish_inventory,
|
||||
hotbar,
|
||||
item_catalog,
|
||||
PlayerInventoryLayout.InventoryContainer.STORAGE,
|
||||
)
|
||||
_inventory_grid.entry_selected.connect(
|
||||
_on_entry_selected.bind(
|
||||
PlayerInventoryLayout.InventoryContainer.STORAGE
|
||||
)
|
||||
)
|
||||
_storage_grid.entry_selected.connect(
|
||||
_on_entry_selected.bind(
|
||||
PlayerInventoryLayout.InventoryContainer.INVENTORY
|
||||
)
|
||||
)
|
||||
if _layout != null and not _layout.layout_changed.is_connected(_refresh):
|
||||
_layout.layout_changed.connect(_refresh)
|
||||
_refresh()
|
||||
|
||||
|
||||
func open_storage() -> bool:
|
||||
if (
|
||||
visible
|
||||
or _player == null
|
||||
or _interaction == null
|
||||
or not _interaction.is_local_player_in_range()
|
||||
or _fishing_spot == null
|
||||
or not _fishing_spot.can_open_fishing_shop()
|
||||
):
|
||||
return false
|
||||
_prior_movement_enabled = _player.is_movement_enabled()
|
||||
_prior_camera_enabled = _player.is_camera_input_enabled()
|
||||
_prior_mouse_mode = Input.mouse_mode
|
||||
_player.set_movement_enabled(false)
|
||||
_player.set_camera_input_enabled(false)
|
||||
_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true)
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
_feedback.text = "select an item to move it · drag to choose a slot"
|
||||
_refresh()
|
||||
show()
|
||||
call_deferred("_focus_first_slot")
|
||||
menu_visibility_changed.emit(true)
|
||||
return true
|
||||
|
||||
|
||||
func close_storage(restore_controls: bool = true) -> void:
|
||||
if not visible:
|
||||
return
|
||||
var viewport := get_viewport()
|
||||
if viewport != null:
|
||||
viewport.gui_release_focus()
|
||||
hide()
|
||||
if _fishing_spot != null:
|
||||
_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false)
|
||||
if restore_controls and _player != null:
|
||||
_player.set_movement_enabled(_prior_movement_enabled)
|
||||
_player.set_camera_input_enabled(_prior_camera_enabled)
|
||||
Input.mouse_mode = _prior_mouse_mode
|
||||
menu_visibility_changed.emit(false)
|
||||
|
||||
|
||||
func close_for_range_exit() -> void:
|
||||
close_storage()
|
||||
|
||||
|
||||
func close_for_water_recovery() -> void:
|
||||
close_storage(false)
|
||||
|
||||
|
||||
func close_for_session_end() -> void:
|
||||
close_storage(false)
|
||||
|
||||
|
||||
func consume_escape() -> bool:
|
||||
if not visible:
|
||||
return false
|
||||
close_storage()
|
||||
return true
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if not visible or not event.is_action_pressed("ui_cancel"):
|
||||
return
|
||||
close_storage()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _on_entry_selected(
|
||||
kind: int,
|
||||
identity: StringName,
|
||||
target_container: int,
|
||||
) -> void:
|
||||
if kind < 0 or identity.is_empty() or _layout == null:
|
||||
return
|
||||
if _layout.move_entry_to_first_free(kind, identity, target_container):
|
||||
_feedback.text = (
|
||||
"moved to storage"
|
||||
if target_container == PlayerInventoryLayout.InventoryContainer.STORAGE
|
||||
else "moved to inventory"
|
||||
)
|
||||
else:
|
||||
_feedback.text = (
|
||||
"storage is full"
|
||||
if target_container == PlayerInventoryLayout.InventoryContainer.STORAGE
|
||||
else "inventory is full"
|
||||
)
|
||||
|
||||
|
||||
func _refresh() -> void:
|
||||
if _layout == null:
|
||||
return
|
||||
_inventory_grid.refresh()
|
||||
_storage_grid.refresh()
|
||||
_inventory_count.text = "%d / %d" % [
|
||||
_layout.get_inventory_count(),
|
||||
_layout.get_inventory_capacity(),
|
||||
]
|
||||
_storage_count.text = "%d / %d" % [
|
||||
_layout.get_storage_count(),
|
||||
_layout.get_storage_capacity(),
|
||||
]
|
||||
call_deferred("_connect_grid_focus")
|
||||
|
||||
|
||||
func _focus_first_slot() -> void:
|
||||
var slot := _inventory_grid.get_first_occupied_slot()
|
||||
if slot == null:
|
||||
slot = _storage_grid.get_first_occupied_slot()
|
||||
if slot != null:
|
||||
slot.grab_focus()
|
||||
|
||||
|
||||
func _connect_grid_focus() -> void:
|
||||
var inventory_slots := _inventory_grid.get_slots()
|
||||
var storage_slots := _storage_grid.get_slots()
|
||||
if inventory_slots.is_empty() or storage_slots.is_empty():
|
||||
return
|
||||
var row_count := mini(
|
||||
ceili(
|
||||
float(inventory_slots.size())
|
||||
/ float(PlayerInventoryLayout.INVENTORY_COLUMNS)
|
||||
),
|
||||
ceili(
|
||||
float(storage_slots.size())
|
||||
/ float(PlayerInventoryLayout.INVENTORY_COLUMNS)
|
||||
),
|
||||
)
|
||||
for row: int in row_count:
|
||||
var inventory_index := mini(
|
||||
row * PlayerInventoryLayout.INVENTORY_COLUMNS
|
||||
+ PlayerInventoryLayout.INVENTORY_COLUMNS - 1,
|
||||
inventory_slots.size() - 1,
|
||||
)
|
||||
var storage_index := mini(
|
||||
row * PlayerInventoryLayout.INVENTORY_COLUMNS,
|
||||
storage_slots.size() - 1,
|
||||
)
|
||||
inventory_slots[inventory_index].focus_neighbor_right = (
|
||||
inventory_slots[inventory_index].get_path_to(storage_slots[storage_index])
|
||||
)
|
||||
storage_slots[storage_index].focus_neighbor_left = (
|
||||
storage_slots[storage_index].get_path_to(inventory_slots[inventory_index])
|
||||
)
|
||||
|
||||
|
||||
func _build_ui() -> void:
|
||||
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
z_index = 82
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
var blocker := ColorRect.new()
|
||||
blocker.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
blocker.color = Color(0.02, 0.08, 0.10, 0.55)
|
||||
blocker.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
add_child(blocker)
|
||||
|
||||
var panel := PanelContainer.new()
|
||||
panel.set_anchors_preset(Control.PRESET_CENTER)
|
||||
panel.position = Vector2(-580.0, -310.0)
|
||||
panel.size = Vector2(1160.0, 620.0)
|
||||
panel.add_theme_stylebox_override(
|
||||
"panel",
|
||||
UtilityPageStyle.rounded_style(UtilityPageStyle.OCEAN_PANEL_DEEP, 22),
|
||||
)
|
||||
add_child(panel)
|
||||
var outer := MarginContainer.new()
|
||||
for side: StringName in [&"margin_left", &"margin_top", &"margin_right", &"margin_bottom"]:
|
||||
outer.add_theme_constant_override(side, 20)
|
||||
panel.add_child(outer)
|
||||
var layout := VBoxContainer.new()
|
||||
layout.add_theme_constant_override("separation", 12)
|
||||
outer.add_child(layout)
|
||||
|
||||
var header := HBoxContainer.new()
|
||||
layout.add_child(header)
|
||||
var title := Label.new()
|
||||
title.text = "private storage"
|
||||
title.add_theme_font_size_override("font_size", 32)
|
||||
title.add_theme_color_override("font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY)
|
||||
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
header.add_child(title)
|
||||
var close := Button.new()
|
||||
close.text = "×"
|
||||
close.tooltip_text = "Close storage"
|
||||
close.custom_minimum_size = Vector2(48.0, 48.0)
|
||||
close.flat = true
|
||||
close.add_theme_font_size_override("font_size", 30)
|
||||
close.pressed.connect(close_storage)
|
||||
header.add_child(close)
|
||||
|
||||
var columns := HBoxContainer.new()
|
||||
columns.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
columns.add_theme_constant_override("separation", 18)
|
||||
layout.add_child(columns)
|
||||
var inventory_column := _build_column(columns, "inventory")
|
||||
_inventory_count = inventory_column["count"]
|
||||
_inventory_grid = GeneralInventoryGrid.new()
|
||||
(inventory_column["scroll"] as ScrollContainer).add_child(_inventory_grid)
|
||||
var storage_column := _build_column(columns, "storage")
|
||||
_storage_count = storage_column["count"]
|
||||
_storage_grid = GeneralInventoryGrid.new()
|
||||
(storage_column["scroll"] as ScrollContainer).add_child(_storage_grid)
|
||||
|
||||
_feedback = Label.new()
|
||||
_feedback.text = "select an item to move it · drag to choose a slot"
|
||||
_feedback.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_feedback.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||
)
|
||||
layout.add_child(_feedback)
|
||||
|
||||
|
||||
func _build_column(parent: HBoxContainer, title_text: String) -> Dictionary:
|
||||
var panel := PanelContainer.new()
|
||||
panel.custom_minimum_size = Vector2(550.0, 0.0)
|
||||
panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
panel.add_theme_stylebox_override(
|
||||
"panel",
|
||||
UtilityPageStyle.rounded_style(UtilityPageStyle.OCEAN_PANEL_MID, 16),
|
||||
)
|
||||
parent.add_child(panel)
|
||||
var margin := MarginContainer.new()
|
||||
for side: StringName in [&"margin_left", &"margin_top", &"margin_right", &"margin_bottom"]:
|
||||
margin.add_theme_constant_override(side, 12)
|
||||
panel.add_child(margin)
|
||||
var column := VBoxContainer.new()
|
||||
column.add_theme_constant_override("separation", 8)
|
||||
margin.add_child(column)
|
||||
var header := HBoxContainer.new()
|
||||
column.add_child(header)
|
||||
var title := Label.new()
|
||||
title.text = title_text
|
||||
title.add_theme_font_size_override("font_size", 22)
|
||||
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
header.add_child(title)
|
||||
var count := Label.new()
|
||||
count.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
count.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||
)
|
||||
header.add_child(count)
|
||||
var scroll := ScrollContainer.new()
|
||||
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
column.add_child(scroll)
|
||||
return {"count": count, "scroll": scroll}
|
||||
1
ui/player_storage.gd.uid
Normal file
1
ui/player_storage.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://d3g0sjl7sepnx
|
||||
14
ui/player_storage.tscn
Normal file
14
ui/player_storage.tscn
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/player_storage.gd" id="1_script"]
|
||||
|
||||
[node name="PlayerStorage" type="Control"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_script")
|
||||
|
|
@ -191,7 +191,10 @@ func _build() -> void:
|
|||
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
root.add_child(scroll)
|
||||
_list = VBoxContainer.new()
|
||||
_list.custom_minimum_size = Vector2(1032, 0)
|
||||
_list.custom_minimum_size = Vector2(
|
||||
UtilityPageStyle.LAPTOP_CONTENT_SIZE.x,
|
||||
0.0,
|
||||
)
|
||||
_list.add_theme_constant_override("separation", 7)
|
||||
scroll.add_child(_list)
|
||||
_status = Label.new()
|
||||
|
|
@ -207,18 +210,22 @@ func _build_host_settings(root: VBoxContainer) -> void:
|
|||
"panel", UtilityPageStyle.row_style(false)
|
||||
)
|
||||
root.add_child(_host_settings_panel)
|
||||
var row := HBoxContainer.new()
|
||||
row.custom_minimum_size.y = 58.0
|
||||
row.add_theme_constant_override("separation", 10)
|
||||
_host_settings_panel.add_child(row)
|
||||
var settings := VBoxContainer.new()
|
||||
settings.custom_minimum_size.y = 104.0
|
||||
settings.add_theme_constant_override("separation", 8)
|
||||
_host_settings_panel.add_child(settings)
|
||||
var identity_row := HBoxContainer.new()
|
||||
identity_row.add_theme_constant_override("separation", 10)
|
||||
settings.add_child(identity_row)
|
||||
var label := Label.new()
|
||||
label.text = "room name"
|
||||
label.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||
)
|
||||
row.add_child(label)
|
||||
identity_row.add_child(label)
|
||||
_room_name_edit = LineEdit.new()
|
||||
_room_name_edit.custom_minimum_size = Vector2(300.0, 42.0)
|
||||
_room_name_edit.custom_minimum_size = Vector2(0.0, 42.0)
|
||||
_room_name_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_room_name_edit.max_length = DiscoveryClient.MAX_ROOM_NAME_LENGTH
|
||||
_room_name_edit.placeholder_text = "Player Name's Server"
|
||||
UtilityPageStyle.apply_ocean_line_edit(_room_name_edit)
|
||||
|
|
@ -226,11 +233,14 @@ func _build_host_settings(root: VBoxContainer) -> void:
|
|||
func(_value: String) -> void: _commit_room_name()
|
||||
)
|
||||
_room_name_edit.focus_exited.connect(_commit_room_name)
|
||||
row.add_child(_room_name_edit)
|
||||
_online_toggle = _build_host_toggle("online", row)
|
||||
identity_row.add_child(_room_name_edit)
|
||||
var access_row := HBoxContainer.new()
|
||||
access_row.add_theme_constant_override("separation", 10)
|
||||
settings.add_child(access_row)
|
||||
_online_toggle = _build_host_toggle("online", access_row)
|
||||
_online_state_label = _online_toggle.get_node("StateBadge/State") as Label
|
||||
_online_toggle.pressed.connect(_on_online_pressed)
|
||||
_discoverable_toggle = _build_host_toggle("discovery", row)
|
||||
_discoverable_toggle = _build_host_toggle("discovery", access_row)
|
||||
_discoverable_state_label = (
|
||||
_discoverable_toggle.get_node("StateBadge/State") as Label
|
||||
)
|
||||
|
|
@ -244,12 +254,12 @@ func _build_host_settings(root: VBoxContainer) -> void:
|
|||
_host_discovery_status.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||
)
|
||||
row.add_child(_host_discovery_status)
|
||||
access_row.add_child(_host_discovery_status)
|
||||
|
||||
|
||||
func _build_host_toggle(label_text: String, row: HBoxContainer) -> Button:
|
||||
var button := Button.new()
|
||||
button.custom_minimum_size = Vector2(150.0, 46.0)
|
||||
button.custom_minimum_size = Vector2(140.0, 46.0)
|
||||
button.text = label_text
|
||||
button.alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
button.clip_contents = false
|
||||
|
|
@ -448,7 +458,8 @@ func _build_active_rows() -> void:
|
|||
for entry: PlayerListEntry in entries:
|
||||
var row := _make_row()
|
||||
var identity := Label.new()
|
||||
identity.custom_minimum_size.x = 430
|
||||
identity.custom_minimum_size.x = 250
|
||||
identity.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var markers: Array[String] = []
|
||||
if entry.is_host:
|
||||
markers.append("host")
|
||||
|
|
@ -470,7 +481,7 @@ func _build_active_rows() -> void:
|
|||
)
|
||||
row.add_child(identity)
|
||||
var ping := Label.new()
|
||||
ping.custom_minimum_size.x = 80
|
||||
ping.custom_minimum_size.x = 60
|
||||
ping.text = (
|
||||
"Local" if entry.is_host
|
||||
else "%d ms" % entry.ping_to_host_ms
|
||||
|
|
@ -484,31 +495,31 @@ func _build_active_rows() -> void:
|
|||
mute.text = "unmute" if entry.muted else "mute"
|
||||
mute.disabled = entry.is_local_player
|
||||
mute.pressed.connect(_toggle_mute.bind(entry))
|
||||
UtilityPageStyle.apply_ocean_button(mute)
|
||||
UtilityPageStyle.apply_compact_ocean_button(mute)
|
||||
row.add_child(mute)
|
||||
var block := Button.new()
|
||||
block.text = "block"
|
||||
block.disabled = entry.is_local_player
|
||||
block.pressed.connect(_confirm_block.bind(entry))
|
||||
UtilityPageStyle.apply_ocean_button(block)
|
||||
UtilityPageStyle.apply_compact_ocean_button(block)
|
||||
row.add_child(block)
|
||||
if entry.can_manage_operator:
|
||||
var operator := Button.new()
|
||||
operator.text = "deop" if entry.is_operator else "op"
|
||||
operator.pressed.connect(_confirm_operator.bind(entry))
|
||||
UtilityPageStyle.apply_ocean_button(operator)
|
||||
UtilityPageStyle.apply_compact_ocean_button(operator)
|
||||
row.add_child(operator)
|
||||
var kick := Button.new()
|
||||
kick.text = "kick"
|
||||
kick.disabled = not entry.can_kick
|
||||
kick.pressed.connect(_confirm_kick.bind(entry))
|
||||
UtilityPageStyle.apply_ocean_button(kick)
|
||||
UtilityPageStyle.apply_compact_ocean_button(kick)
|
||||
row.add_child(kick)
|
||||
var ban := Button.new()
|
||||
ban.text = "ban"
|
||||
ban.disabled = not entry.can_ban
|
||||
ban.pressed.connect(_confirm_ban.bind(entry))
|
||||
UtilityPageStyle.apply_ocean_button(ban)
|
||||
UtilityPageStyle.apply_compact_ocean_button(ban)
|
||||
row.add_child(ban)
|
||||
|
||||
|
||||
|
|
@ -516,7 +527,8 @@ func _build_session_artwork_controls() -> void:
|
|||
var counts: Vector2i = _service.get_session_artwork_counts()
|
||||
var row := _make_row()
|
||||
var label := Label.new()
|
||||
label.custom_minimum_size.x = 830
|
||||
label.custom_minimum_size.x = 600
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
label.text = "session artwork · %d layers · %d painted pixels" % [
|
||||
counts.x, counts.y,
|
||||
]
|
||||
|
|
@ -534,7 +546,7 @@ func _build_session_artwork_controls() -> void:
|
|||
_service.reset_session_artwork,
|
||||
)
|
||||
)
|
||||
UtilityPageStyle.apply_ocean_button(reset)
|
||||
UtilityPageStyle.apply_compact_ocean_button(reset)
|
||||
row.add_child(reset)
|
||||
|
||||
|
||||
|
|
@ -546,7 +558,8 @@ func _build_relationship_rows() -> void:
|
|||
for record: Dictionary in records:
|
||||
var row := _make_row()
|
||||
var label := Label.new()
|
||||
label.custom_minimum_size.x = 690
|
||||
label.custom_minimum_size.x = 560
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var fingerprint := str(record["fingerprint"])
|
||||
label.text = "%s · %s %s" % [
|
||||
str(record.get("last_known_display_name", "Player")),
|
||||
|
|
@ -564,7 +577,7 @@ func _build_relationship_rows() -> void:
|
|||
unblock.pressed.connect(func() -> void:
|
||||
_service.set_blocked(fingerprint, str(record["last_known_display_name"]), false)
|
||||
)
|
||||
UtilityPageStyle.apply_ocean_button(unblock)
|
||||
UtilityPageStyle.apply_compact_ocean_button(unblock)
|
||||
row.add_child(unblock)
|
||||
var unmute := Button.new()
|
||||
unmute.text = "unmute"
|
||||
|
|
@ -572,7 +585,7 @@ func _build_relationship_rows() -> void:
|
|||
unmute.pressed.connect(func() -> void:
|
||||
_service.set_muted(fingerprint, str(record["last_known_display_name"]), false)
|
||||
)
|
||||
UtilityPageStyle.apply_ocean_button(unmute)
|
||||
UtilityPageStyle.apply_compact_ocean_button(unmute)
|
||||
row.add_child(unmute)
|
||||
|
||||
|
||||
|
|
@ -585,7 +598,8 @@ func _build_ban_rows() -> void:
|
|||
var row := _make_row()
|
||||
var fingerprint := str(record["target_fingerprint"])
|
||||
var label := Label.new()
|
||||
label.custom_minimum_size.x = 830
|
||||
label.custom_minimum_size.x = 600
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
label.text = "%s · %s banned %s" % [
|
||||
str(record.get("last_known_display_name", "Player")),
|
||||
NetworkIdentityCrypto.compact_suffix(fingerprint),
|
||||
|
|
@ -599,7 +613,7 @@ func _build_ban_rows() -> void:
|
|||
var unban := Button.new()
|
||||
unban.text = "unban"
|
||||
unban.pressed.connect(_confirm_unban.bind(fingerprint))
|
||||
UtilityPageStyle.apply_ocean_button(unban)
|
||||
UtilityPageStyle.apply_compact_ocean_button(unban)
|
||||
row.add_child(unban)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,16 @@ extends Control
|
|||
|
||||
const CHECK_DEBOUNCE_SECONDS: float = 0.4
|
||||
const APPEARANCE_PREVIEW_INTERVAL_SECONDS: float = 0.08
|
||||
const OPTION_GRID_COLUMNS: int = 6
|
||||
const FUR_PALETTE_GRID_COLUMNS: int = 10
|
||||
const FUR_PATTERN_GRID_COLUMNS: int = 4
|
||||
const OPTION_GRID_COLUMNS: int = 3
|
||||
const FUR_PALETTE_GRID_COLUMNS: int = 6
|
||||
const FUR_PATTERN_GRID_COLUMNS: int = 2
|
||||
const FUR_CHANNEL_GRID_COLUMNS: int = 2
|
||||
const FUR_PALETTE_SWATCH_SIZE: float = 38.0
|
||||
const FUR_CHANNEL_SWATCH_SIZE: float = 22.0
|
||||
const FUR_PART_TAB_WIDTH: float = 58.0
|
||||
const FUR_COLOR_PICKER_POPUP_SIZE: Vector2i = Vector2i(720, 560)
|
||||
const FUR_COLOR_PICKER_SV_SIZE: Vector2i = Vector2i(520, 300)
|
||||
const VOICE_OPTION_BUTTON_SIZE: Vector2 = Vector2(68.0, 32.0)
|
||||
const FUR_SECTION_PATTERNS: String = "patterns"
|
||||
const FUR_SECTION_COLORS: String = "colors"
|
||||
const FEATURE_DRAWER_ANIMATION_SECONDS: float = 0.16
|
||||
|
|
@ -722,7 +725,7 @@ func _build_ui() -> void:
|
|||
_category_list.add_theme_constant_override("separation", 5)
|
||||
category_scroll.add_child(_category_list)
|
||||
_option_list = VBoxContainer.new()
|
||||
_option_list.custom_minimum_size = Vector2(360, 0)
|
||||
_option_list.custom_minimum_size = Vector2(326, 0)
|
||||
_option_list.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_option_list.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
_option_list.add_theme_constant_override("separation", 5)
|
||||
|
|
@ -732,13 +735,13 @@ func _build_ui() -> void:
|
|||
body_spacer.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
body.add_child(body_spacer)
|
||||
var preview_stack := VBoxContainer.new()
|
||||
preview_stack.custom_minimum_size.x = 260.0
|
||||
preview_stack.custom_minimum_size.x = 240.0
|
||||
preview_stack.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
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(260.0, 0.0)
|
||||
preview_frame.custom_minimum_size = Vector2(240.0, 0.0)
|
||||
preview_frame.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
preview_frame.add_theme_stylebox_override(
|
||||
"panel", UtilityPageStyle.rounded_style(
|
||||
|
|
@ -752,7 +755,7 @@ func _build_ui() -> void:
|
|||
preview_layer.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
preview_frame.add_child(preview_layer)
|
||||
_preview = preload("res://ui/profile_preview.tscn").instantiate()
|
||||
_preview.custom_minimum_size = Vector2(260.0, 0.0)
|
||||
_preview.custom_minimum_size = Vector2(240.0, 0.0)
|
||||
_preview.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_preview.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
preview_layer.add_child(_preview)
|
||||
|
|
@ -911,7 +914,7 @@ func _build_voice_options() -> void:
|
|||
var settings_grid := GridContainer.new()
|
||||
settings_grid.name = "VoiceSettingsGrid"
|
||||
settings_grid.columns = 2
|
||||
settings_grid.add_theme_constant_override("h_separation", 10)
|
||||
settings_grid.add_theme_constant_override("h_separation", 8)
|
||||
settings_grid.add_theme_constant_override("v_separation", 8)
|
||||
settings_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_option_list.add_child(settings_grid)
|
||||
|
|
@ -943,7 +946,7 @@ func _build_voice_options() -> void:
|
|||
sample_set_button.text = str(option.get("label", sample_set_id))
|
||||
sample_set_button.toggle_mode = true
|
||||
sample_set_button.button_group = sample_set_group
|
||||
sample_set_button.custom_minimum_size = Vector2(108.0, 32.0)
|
||||
sample_set_button.custom_minimum_size = VOICE_OPTION_BUTTON_SIZE
|
||||
sample_set_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
sample_set_button.button_pressed = (
|
||||
_draft_sample_set_id == sample_set_id
|
||||
|
|
@ -951,7 +954,7 @@ func _build_voice_options() -> void:
|
|||
sample_set_button.pressed.connect(
|
||||
_select_sample_set_option.bind(sample_set_id)
|
||||
)
|
||||
UtilityPageStyle.apply_compact_ocean_button(sample_set_button)
|
||||
_apply_voice_option_button(sample_set_button)
|
||||
sample_set_grid.add_child(sample_set_button)
|
||||
var pitch_title := Label.new()
|
||||
pitch_title.text = "pitch"
|
||||
|
|
@ -976,11 +979,11 @@ func _build_voice_options() -> void:
|
|||
button.text = str(option.get("label", option_id))
|
||||
button.toggle_mode = true
|
||||
button.button_group = pitch_group
|
||||
button.custom_minimum_size = Vector2(108.0, 32.0)
|
||||
button.custom_minimum_size = VOICE_OPTION_BUTTON_SIZE
|
||||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
button.button_pressed = _draft_voice_id == option_id
|
||||
button.pressed.connect(_select_voice_option.bind(option_id))
|
||||
UtilityPageStyle.apply_compact_ocean_button(button)
|
||||
_apply_voice_option_button(button)
|
||||
grid.add_child(button)
|
||||
var speed_title := Label.new()
|
||||
speed_title.text = "playback speed"
|
||||
|
|
@ -1008,13 +1011,13 @@ func _build_voice_options() -> void:
|
|||
)
|
||||
speed_button.toggle_mode = true
|
||||
speed_button.button_group = speed_group
|
||||
speed_button.custom_minimum_size = Vector2(108.0, 32.0)
|
||||
speed_button.custom_minimum_size = VOICE_OPTION_BUTTON_SIZE
|
||||
speed_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
speed_button.button_pressed = _draft_speech_speed_id == speed_id
|
||||
speed_button.pressed.connect(
|
||||
_select_speech_speed_option.bind(speed_id)
|
||||
)
|
||||
UtilityPageStyle.apply_compact_ocean_button(speed_button)
|
||||
_apply_voice_option_button(speed_button)
|
||||
speed_grid.add_child(speed_button)
|
||||
var call_title := Label.new()
|
||||
call_title.text = "call (G)"
|
||||
|
|
@ -1039,14 +1042,29 @@ func _build_voice_options() -> void:
|
|||
call_button.text = str(option.get("label", call_id))
|
||||
call_button.toggle_mode = true
|
||||
call_button.button_group = call_group
|
||||
call_button.custom_minimum_size = Vector2(108.0, 32.0)
|
||||
call_button.custom_minimum_size = VOICE_OPTION_BUTTON_SIZE
|
||||
call_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
call_button.button_pressed = _draft_call_id == call_id
|
||||
call_button.pressed.connect(_select_call_option.bind(call_id))
|
||||
UtilityPageStyle.apply_compact_ocean_button(call_button)
|
||||
_apply_voice_option_button(call_button)
|
||||
call_grid.add_child(call_button)
|
||||
|
||||
|
||||
func _apply_voice_option_button(button: Button) -> void:
|
||||
UtilityPageStyle.apply_compact_ocean_button(button)
|
||||
button.custom_minimum_size = VOICE_OPTION_BUTTON_SIZE
|
||||
button.add_theme_font_size_override("font_size", 12)
|
||||
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 = 6.0
|
||||
style.content_margin_right = 6.0
|
||||
button.add_theme_stylebox_override(state, style)
|
||||
|
||||
|
||||
func _select_sample_set_option(sample_set_id: String) -> void:
|
||||
if not VoiceProfilesType.is_valid_sample_set(sample_set_id):
|
||||
return
|
||||
|
|
@ -1473,7 +1491,7 @@ func _build_fur_pattern_options() -> void:
|
|||
var part_tabs := HBoxContainer.new()
|
||||
part_tabs.name = "FurPatternPartTabs"
|
||||
part_tabs.custom_minimum_size.y = 38.0
|
||||
part_tabs.add_theme_constant_override("separation", 4)
|
||||
part_tabs.add_theme_constant_override("separation", 2)
|
||||
part_tab_margin.add_child(part_tabs)
|
||||
for style_index: int in range(
|
||||
CharacterCustomizationCatalog.FUR_STYLE_IDS.size()
|
||||
|
|
@ -1486,7 +1504,7 @@ func _build_fur_pattern_options() -> void:
|
|||
style_field
|
||||
)
|
||||
part_tab.palette_index = mini(style_index, 2)
|
||||
part_tab.custom_minimum_size = Vector2(90.0, 38.0)
|
||||
part_tab.custom_minimum_size = Vector2(FUR_PART_TAB_WIDTH, 38.0)
|
||||
part_tab.focus_mode = Control.FOCUS_ALL
|
||||
part_tab.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
part_tab.pressed.connect(_select_fur_pattern_part.bind(style_field))
|
||||
|
|
@ -1586,7 +1604,7 @@ func _build_fur_color_channels(options: Array) -> void:
|
|||
|
||||
var channel_grid := GridContainer.new()
|
||||
channel_grid.name = "FurColorChannelGrid"
|
||||
channel_grid.columns = CharacterCustomizationCatalog.FUR_COLOR_IDS.size()
|
||||
channel_grid.columns = FUR_CHANNEL_GRID_COLUMNS
|
||||
channel_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
channel_grid.add_theme_constant_override("h_separation", 10)
|
||||
channel_grid.add_theme_constant_override("v_separation", 8)
|
||||
|
|
|
|||
366
ui/shop_sell_inventory.gd
Normal file
366
ui/shop_sell_inventory.gd
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
class_name ShopSellInventory
|
||||
extends Control
|
||||
|
||||
const MAIN_SHOP_BUYER_ID: StringName = &"main_fishing_shop"
|
||||
|
||||
var _layout: PlayerInventoryLayout
|
||||
var _bag: PlayerBag
|
||||
var _fish_inventory: FishInventory
|
||||
var _item_catalog: ItemCatalog
|
||||
var _buyer: FishBuyerProfile
|
||||
var _reservations: PlayerAssetReservationService
|
||||
var _network_sale: NetworkSaleService
|
||||
var _inventory_grid: GeneralInventoryGrid
|
||||
var _tray_grid: GridContainer
|
||||
var _total_label: Label
|
||||
var _feedback: Label
|
||||
var _sell_button: Button
|
||||
var _staged: Dictionary[String, Dictionary] = {}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
UtilityPageStyle.apply_page(self)
|
||||
_build_ui()
|
||||
|
||||
|
||||
func setup(
|
||||
layout: PlayerInventoryLayout,
|
||||
bag: PlayerBag,
|
||||
fish_inventory: FishInventory,
|
||||
hotbar: PlayerHotbar,
|
||||
item_catalog: ItemCatalog,
|
||||
buyer: FishBuyerProfile,
|
||||
reservations: PlayerAssetReservationService,
|
||||
network_sale: NetworkSaleService,
|
||||
) -> void:
|
||||
_layout = layout
|
||||
_bag = bag
|
||||
_fish_inventory = fish_inventory
|
||||
_item_catalog = item_catalog
|
||||
_buyer = buyer
|
||||
_reservations = reservations
|
||||
_network_sale = network_sale
|
||||
_inventory_grid.setup(
|
||||
layout,
|
||||
bag,
|
||||
fish_inventory,
|
||||
hotbar,
|
||||
item_catalog,
|
||||
PlayerInventoryLayout.InventoryContainer.INVENTORY,
|
||||
)
|
||||
_inventory_grid.entry_selected.connect(_on_inventory_entry_selected)
|
||||
if _layout != null:
|
||||
_layout.layout_changed.connect(_refresh)
|
||||
if _network_sale != null:
|
||||
_network_sale.local_sale_pending.connect(_on_sale_pending)
|
||||
_network_sale.local_sale_finished.connect(_on_sale_finished)
|
||||
_refresh()
|
||||
|
||||
|
||||
func activate() -> void:
|
||||
_feedback.text = "select or drag items into the sell tray"
|
||||
_refresh()
|
||||
var slot := _inventory_grid.get_first_occupied_slot()
|
||||
if slot != null:
|
||||
slot.grab_focus()
|
||||
|
||||
|
||||
func clear_staged() -> void:
|
||||
_staged.clear()
|
||||
_refresh_tray()
|
||||
|
||||
|
||||
func _on_inventory_entry_selected(kind: int, identity: StringName) -> void:
|
||||
_stage(kind, identity)
|
||||
|
||||
|
||||
func _stage(kind: int, identity: StringName) -> void:
|
||||
if identity.is_empty():
|
||||
return
|
||||
var key := (
|
||||
PlayerInventoryLayout.catch_key(identity)
|
||||
if kind == PlayerInventoryLayout.EntryKind.CATCH
|
||||
else PlayerInventoryLayout.item_key(identity)
|
||||
)
|
||||
if _staged.has(key):
|
||||
_staged.erase(key)
|
||||
_refresh_tray()
|
||||
return
|
||||
if kind == PlayerInventoryLayout.EntryKind.CATCH:
|
||||
var fish_catch := _fish_inventory.get_catch_by_id(identity)
|
||||
if fish_catch == null:
|
||||
_feedback.text = "that catch is no longer available"
|
||||
return
|
||||
if fish_catch.is_favorited:
|
||||
_feedback.text = "favorite catches cannot be sold"
|
||||
return
|
||||
if _reservations != null and _reservations.is_fish_reserved(identity):
|
||||
_feedback.text = "reserved in a letter"
|
||||
return
|
||||
_staged[key] = {"kind": kind, "identity": identity, "quantity": 1}
|
||||
else:
|
||||
var item := _item_catalog.get_item_by_id(identity)
|
||||
if not ItemResalePolicy.is_sellable(item):
|
||||
_feedback.text = "equipment cannot be sold"
|
||||
return
|
||||
var available := _bag.get_quantity(identity)
|
||||
if _reservations != null:
|
||||
available = _reservations.get_available_item_quantity(identity)
|
||||
if available < 1:
|
||||
_feedback.text = "that item is reserved"
|
||||
return
|
||||
_staged[key] = {
|
||||
"kind": kind,
|
||||
"identity": identity,
|
||||
"quantity": available,
|
||||
}
|
||||
_feedback.text = ""
|
||||
_refresh_tray()
|
||||
|
||||
|
||||
func _on_drop_payload(payload: Dictionary) -> void:
|
||||
var payload_kind := str(payload.get("kind", ""))
|
||||
_stage(
|
||||
PlayerInventoryLayout.EntryKind.CATCH
|
||||
if payload_kind == "cooler_fish"
|
||||
else PlayerInventoryLayout.EntryKind.ITEM,
|
||||
StringName(str(payload.get(
|
||||
"catch_id" if payload_kind == "cooler_fish" else "item_id", ""
|
||||
))),
|
||||
)
|
||||
|
||||
|
||||
func _on_remove_requested(key: String) -> void:
|
||||
_staged.erase(key)
|
||||
_refresh_tray()
|
||||
|
||||
|
||||
func _submit_sale() -> void:
|
||||
if _staged.is_empty() or _network_sale == null:
|
||||
return
|
||||
var catch_ids: Array[StringName] = []
|
||||
var items: Dictionary[StringName, int] = {}
|
||||
for record: Dictionary in _staged.values():
|
||||
var identity := StringName(str(record.get("identity", "")))
|
||||
if int(record.get("kind", -1)) == PlayerInventoryLayout.EntryKind.CATCH:
|
||||
catch_ids.append(identity)
|
||||
else:
|
||||
items[identity] = int(record.get("quantity", 0))
|
||||
if _network_sale.request_local_mixed_sale(
|
||||
catch_ids, items, MAIN_SHOP_BUYER_ID
|
||||
).is_empty():
|
||||
_feedback.text = "sale could not be started"
|
||||
|
||||
|
||||
func _on_sale_pending(_request_id: String) -> void:
|
||||
_sell_button.disabled = true
|
||||
_feedback.text = "selling…"
|
||||
|
||||
|
||||
func _on_sale_finished(
|
||||
_request_id: String,
|
||||
accepted: bool,
|
||||
message: String,
|
||||
_catch_ids: Array[StringName],
|
||||
_payout: int,
|
||||
) -> void:
|
||||
if accepted:
|
||||
_staged.clear()
|
||||
_feedback.text = message.to_lower()
|
||||
_refresh()
|
||||
|
||||
|
||||
func _refresh() -> void:
|
||||
_validate_staged()
|
||||
_inventory_grid.refresh()
|
||||
_refresh_tray()
|
||||
|
||||
|
||||
func _validate_staged() -> void:
|
||||
for key: String in _staged.keys():
|
||||
var record: Dictionary = _staged[key]
|
||||
var identity := StringName(str(record.get("identity", "")))
|
||||
if int(record.get("kind", -1)) == PlayerInventoryLayout.EntryKind.CATCH:
|
||||
if (
|
||||
_fish_inventory.get_catch_by_id(identity) == null
|
||||
or not _layout.is_catch_in_inventory(identity)
|
||||
):
|
||||
_staged.erase(key)
|
||||
elif (
|
||||
_bag.get_quantity(identity) < int(record.get("quantity", 0))
|
||||
or not _layout.is_item_in_inventory(identity)
|
||||
):
|
||||
_staged.erase(key)
|
||||
|
||||
|
||||
func _refresh_tray() -> void:
|
||||
_refresh_inventory_staged_highlights()
|
||||
for child: Node in _tray_grid.get_children():
|
||||
child.queue_free()
|
||||
var keys: Array[String] = []
|
||||
keys.assign(_staged.keys())
|
||||
keys.sort()
|
||||
for key: String in keys:
|
||||
var record: Dictionary = _staged[key]
|
||||
var identity := StringName(str(record["identity"]))
|
||||
var icon: Texture2D
|
||||
var label: String
|
||||
if int(record["kind"]) == PlayerInventoryLayout.EntryKind.CATCH:
|
||||
var fish_catch := _fish_inventory.get_catch_by_id(identity)
|
||||
if fish_catch == null:
|
||||
continue
|
||||
icon = fish_catch.fish.display_texture
|
||||
label = fish_catch.fish.display_name
|
||||
else:
|
||||
var item := _item_catalog.get_item_by_id(identity)
|
||||
if item == null:
|
||||
continue
|
||||
icon = item.icon
|
||||
label = item.display_name
|
||||
var slot := ShopSaleTraySlot.new()
|
||||
_tray_grid.add_child(slot)
|
||||
slot.configure(key, icon, label, int(record.get("quantity", 1)))
|
||||
slot.remove_requested.connect(_on_remove_requested)
|
||||
slot.drop_requested.connect(_on_drop_payload)
|
||||
var drop_slot := ShopSaleTraySlot.new()
|
||||
_tray_grid.add_child(drop_slot)
|
||||
drop_slot.configure("", null, "drop an item here")
|
||||
drop_slot.disabled = false
|
||||
drop_slot.drop_requested.connect(_on_drop_payload)
|
||||
var total := _calculate_total()
|
||||
_total_label.text = str(total)
|
||||
_sell_button.disabled = (
|
||||
_staged.is_empty()
|
||||
or total < 0
|
||||
or _network_sale == null
|
||||
or _network_sale.is_local_sale_pending()
|
||||
)
|
||||
call_deferred("_configure_focus")
|
||||
|
||||
|
||||
func _refresh_inventory_staged_highlights() -> void:
|
||||
if _inventory_grid == null:
|
||||
return
|
||||
for slot: GeneralInventorySlot in _inventory_grid.get_slots():
|
||||
var key: String = ""
|
||||
if not slot.entry_identity.is_empty():
|
||||
key = (
|
||||
PlayerInventoryLayout.catch_key(slot.entry_identity)
|
||||
if slot.entry_kind == PlayerInventoryLayout.EntryKind.CATCH
|
||||
else PlayerInventoryLayout.item_key(slot.entry_identity)
|
||||
)
|
||||
slot.set_staged(not key.is_empty() and _staged.has(key))
|
||||
|
||||
|
||||
func _configure_focus() -> void:
|
||||
var controls: Array[Control] = []
|
||||
controls.assign(_inventory_grid.get_slots())
|
||||
for child: Node in _tray_grid.get_children():
|
||||
var control := child as Control
|
||||
if control != null and control.focus_mode != Control.FOCUS_NONE:
|
||||
controls.append(control)
|
||||
controls.append(_sell_button)
|
||||
ControllerFocusNavigation.configure_spatial_neighbors(controls)
|
||||
|
||||
|
||||
func _calculate_total() -> int:
|
||||
var total: int = 0
|
||||
for record: Dictionary in _staged.values():
|
||||
var identity := StringName(str(record["identity"]))
|
||||
if int(record["kind"]) == PlayerInventoryLayout.EntryKind.CATCH:
|
||||
var fish_catch := _fish_inventory.get_catch_by_id(identity)
|
||||
if fish_catch == null:
|
||||
return -1
|
||||
total += _buyer.get_quality_offer(
|
||||
fish_catch.fish.get_sale_value_for_weight(fish_catch.weight_lb),
|
||||
fish_catch.quality,
|
||||
)
|
||||
else:
|
||||
var item := _item_catalog.get_item_by_id(identity)
|
||||
var unit_value := ItemResalePolicy.get_unit_value(item)
|
||||
if unit_value < 0:
|
||||
return -1
|
||||
total += unit_value * int(record.get("quantity", 0))
|
||||
return total
|
||||
|
||||
|
||||
func _build_ui() -> void:
|
||||
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
var outer := MarginContainer.new()
|
||||
outer.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
outer.add_theme_constant_override("margin_left", 68)
|
||||
outer.add_theme_constant_override("margin_top", 92)
|
||||
outer.add_theme_constant_override("margin_right", 68)
|
||||
outer.add_theme_constant_override("margin_bottom", 44)
|
||||
add_child(outer)
|
||||
var columns := HBoxContainer.new()
|
||||
columns.add_theme_constant_override("separation", 20)
|
||||
outer.add_child(columns)
|
||||
|
||||
var source := _build_panel(columns, "inventory")
|
||||
_inventory_grid = GeneralInventoryGrid.new()
|
||||
(source as VBoxContainer).add_child(_inventory_grid)
|
||||
var tray := _build_panel(columns, "sell tray")
|
||||
var tray_scroll := ScrollContainer.new()
|
||||
tray_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
tray_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
(tray as VBoxContainer).add_child(tray_scroll)
|
||||
_tray_grid = GridContainer.new()
|
||||
_tray_grid.columns = PlayerInventoryLayout.INVENTORY_COLUMNS
|
||||
_tray_grid.add_theme_constant_override(
|
||||
"h_separation", GeneralInventoryGrid.DEFAULT_SLOT_SEPARATION
|
||||
)
|
||||
_tray_grid.add_theme_constant_override(
|
||||
"v_separation", GeneralInventoryGrid.DEFAULT_SLOT_SEPARATION
|
||||
)
|
||||
tray_scroll.add_child(_tray_grid)
|
||||
_feedback = Label.new()
|
||||
_feedback.text = "select or drag items into the sell tray"
|
||||
_feedback.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_feedback.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||
)
|
||||
(tray as VBoxContainer).add_child(_feedback)
|
||||
var total_row := HBoxContainer.new()
|
||||
(tray as VBoxContainer).add_child(total_row)
|
||||
var total_title := Label.new()
|
||||
total_title.text = "total"
|
||||
total_title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
total_row.add_child(total_title)
|
||||
var coin := TextureRect.new()
|
||||
coin.custom_minimum_size = Vector2(24.0, 24.0)
|
||||
coin.texture = preload("res://items/icons/shop/32_currency.png")
|
||||
coin.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
coin.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
coin.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
total_row.add_child(coin)
|
||||
_total_label = Label.new()
|
||||
_total_label.text = "0"
|
||||
total_row.add_child(_total_label)
|
||||
_sell_button = Button.new()
|
||||
_sell_button.text = "sell"
|
||||
UtilityPageStyle.apply_ocean_button(_sell_button)
|
||||
_sell_button.pressed.connect(_submit_sale)
|
||||
(tray as VBoxContainer).add_child(_sell_button)
|
||||
|
||||
|
||||
func _build_panel(parent: HBoxContainer, title_text: String) -> VBoxContainer:
|
||||
var panel := PanelContainer.new()
|
||||
panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
panel.add_theme_stylebox_override(
|
||||
"panel",
|
||||
UtilityPageStyle.rounded_style(UtilityPageStyle.OCEAN_PANEL_MID, 16),
|
||||
)
|
||||
parent.add_child(panel)
|
||||
var margin := MarginContainer.new()
|
||||
for side: StringName in [&"margin_left", &"margin_top", &"margin_right", &"margin_bottom"]:
|
||||
margin.add_theme_constant_override(side, 12)
|
||||
panel.add_child(margin)
|
||||
var column := VBoxContainer.new()
|
||||
column.add_theme_constant_override("separation", 8)
|
||||
margin.add_child(column)
|
||||
var title := Label.new()
|
||||
title.text = title_text
|
||||
title.add_theme_font_size_override("font_size", 22)
|
||||
column.add_child(title)
|
||||
return column
|
||||
1
ui/shop_sell_inventory.gd.uid
Normal file
1
ui/shop_sell_inventory.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cct5tvsc5igda
|
||||
|
|
@ -26,7 +26,8 @@ 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)
|
||||
const LAPTOP_RECT: Rect2 = Rect2(199.0, 132.0, 882.0, 520.0)
|
||||
const LAPTOP_CONTENT_SIZE: Vector2 = Vector2(794.0, 442.0)
|
||||
const SUPPLY_BADGE_MIN_WIDTH: float = 46.0
|
||||
const SUPPLY_BADGE_HEIGHT: float = 24.0
|
||||
const SUPPLY_BADGE_EDGE_MARGIN: float = 3.0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue