straywild/ui/shop_sell_inventory.gd

374 lines
11 KiB
GDScript

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
var quality: int = -1
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
quality = fish_catch.quality
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)),
quality,
)
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