306 lines
14 KiB
GDScript
306 lines
14 KiB
GDScript
class_name WarehousePanel
|
|
extends RefCounted
|
|
|
|
# 仓库面板:
|
|
# - 负责果实、种子、道具三类库存展示,以及果实出售弹窗。
|
|
# - 数据源由 PlayerInventoryFacade/InventoryState 提供,本面板只负责当前分页和 UI 选择。
|
|
# - 出售成功后通过 callbacks 刷新仓库和玩家资源,避免直接改主场景字段。
|
|
const NumberFormatterScript := preload("res://scripts/core/number_formatter.gd")
|
|
const UiMotionScript := preload("res://scripts/ui/common/ui_motion.gd")
|
|
|
|
var _ui
|
|
var _texture_provider
|
|
var _owner: Node
|
|
var _warehouse_api
|
|
var _get_item_icon_texture: Callable
|
|
var _context
|
|
var _callbacks: Dictionary = {}
|
|
var _panel_screen_size := Vector2(640.0, 960.0)
|
|
var _capacity := 32
|
|
var _item_type_fruit := 2
|
|
var _item_type_seed := 1
|
|
var _item_type_props := 3
|
|
var _tab_index := 0
|
|
var _panel: Control
|
|
var _list: GridContainer
|
|
var _modal_layer: Control
|
|
|
|
|
|
func setup(options: Dictionary) -> void:
|
|
_ui = options.get("ui_builder")
|
|
_texture_provider = options.get("texture_provider")
|
|
_owner = options.get("owner") as Node
|
|
_warehouse_api = options.get("warehouse_api")
|
|
_get_item_icon_texture = options.get("get_item_icon_texture", Callable())
|
|
_context = options.get("context")
|
|
_callbacks = options.get("callbacks", {})
|
|
if _callbacks.is_empty() and _context != null and _context.has_method("legacy_callbacks"):
|
|
_callbacks = _context.legacy_callbacks()
|
|
_panel_screen_size = options.get("panel_screen_size", _panel_screen_size)
|
|
_capacity = int(options.get("capacity", _capacity))
|
|
_item_type_fruit = int(options.get("item_type_fruit", _item_type_fruit))
|
|
_item_type_seed = int(options.get("item_type_seed", _item_type_seed))
|
|
_item_type_props = int(options.get("item_type_props", _item_type_props))
|
|
_modal_layer = options.get("modal_layer") as Control
|
|
|
|
|
|
func create(tab_index: int) -> Control:
|
|
_tab_index = tab_index
|
|
_panel = _ui.create_common_panel_shell("WareHousePanel", Vector2(540.0, 634.0), "panel_icon_warehouse", _texture_provider.atlas_path("common"), "panel_title_warehouse", _texture_provider.atlas_path("common"))
|
|
_ui.add_content_panel_background(_panel, "InnerPage", Vector2(114.0, 310.0), Vector2(411.0, 415.0))
|
|
_ui.create_tab_bar(_panel, "WareHouseTabs", ["果实", "种子", "道具"], Vector2(114.0, 263.0), _tab_index, Callable(self, "_on_tab_pressed"))
|
|
|
|
var scroll := ScrollContainer.new()
|
|
scroll.name = "WareHouseScroll"
|
|
scroll.position = Vector2(114.0, 313.0)
|
|
scroll.size = Vector2(411.0, 408.0)
|
|
scroll.clip_contents = true
|
|
scroll.follow_focus = true
|
|
_ui.configure_vertical_scroll(scroll)
|
|
_panel.add_child(scroll)
|
|
|
|
var holder := MarginContainer.new()
|
|
holder.name = "WareHouseListHolder"
|
|
holder.custom_minimum_size = Vector2(411.0, 0.0)
|
|
holder.add_theme_constant_override("margin_left", 10)
|
|
holder.add_theme_constant_override("margin_top", 10)
|
|
holder.add_theme_constant_override("margin_right", 0)
|
|
holder.add_theme_constant_override("margin_bottom", 10)
|
|
scroll.add_child(holder)
|
|
|
|
_list = GridContainer.new()
|
|
_list.name = "WareHouseList"
|
|
_list.columns = 4
|
|
_list.custom_minimum_size = Vector2(390.0, 0.0)
|
|
_list.add_theme_constant_override("h_separation", 10)
|
|
_list.add_theme_constant_override("v_separation", 10)
|
|
holder.add_child(_list)
|
|
render()
|
|
return _panel
|
|
|
|
|
|
func list() -> GridContainer:
|
|
return _list
|
|
|
|
|
|
func set_modal_layer(modal_layer: Control) -> void:
|
|
_modal_layer = modal_layer
|
|
|
|
|
|
func render() -> void:
|
|
if _list == null:
|
|
return
|
|
_clear_children(_list)
|
|
var items := _items_for_tab(_tab_index)
|
|
items.sort_custom(func(left: Dictionary, right: Dictionary) -> bool:
|
|
return int(left.get("item_id", 0)) < int(right.get("item_id", 0))
|
|
)
|
|
for item in items:
|
|
_list.add_child(_create_item(item))
|
|
for _index in range(maxi(_capacity - items.size(), 0)):
|
|
_list.add_child(_create_item({}))
|
|
|
|
|
|
func _on_tab_pressed(index: int) -> void:
|
|
_tab_index = index
|
|
var tab_bar := _panel.get_node_or_null("WareHouseTabs") if _panel != null else null
|
|
_ui.update_tab_bar(tab_bar as Control, _tab_index)
|
|
render()
|
|
|
|
|
|
func _items_for_tab(index: int) -> Array[Dictionary]:
|
|
var wanted_type := _item_type_fruit
|
|
if index == 1:
|
|
wanted_type = _item_type_seed
|
|
elif index == 2:
|
|
wanted_type = _item_type_props
|
|
var result: Array[Dictionary] = []
|
|
for item in _warehouse_items():
|
|
var config: Dictionary = item.get("config", {})
|
|
if int(config.get("type", item.get("type", 0))) == wanted_type:
|
|
result.append(item.duplicate(true))
|
|
return result
|
|
|
|
|
|
func _create_item(item: Dictionary) -> Control:
|
|
var holder := Control.new()
|
|
holder.name = "WareHouseItem%s" % int(item.get("item_id", 0))
|
|
holder.custom_minimum_size = Vector2(90.0, 90.0)
|
|
holder.size = Vector2(90.0, 90.0)
|
|
_ui.add_texture(holder, "ItemBg", _texture_provider.common("item_bg"), Vector2.ZERO, Vector2(90.0, 90.0))
|
|
if item.is_empty():
|
|
return holder
|
|
|
|
var config: Dictionary = item.get("config", {})
|
|
_ui.add_texture(holder, "Icon", _item_icon_texture(int(config.get("icon", item.get("icon_id", item.get("item_id", 0))))), Vector2(8.0, 9.0), Vector2(74.0, 68.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED)
|
|
_ui.add_nine_patch(holder, "LvBg", _texture_provider.common("blue_rect"), Vector2.ZERO, Vector2(48.0, 21.0), Vector4(0.0, 2.0, 5.0, 17.0))
|
|
_ui.add_panel_label(holder, "Lv", "Lv.%s" % int(config.get("lv", 1)), Vector2(3.0, 2.0), Vector2(46.0, 18.0), 16, Color.WHITE, 0, Color.TRANSPARENT, HORIZONTAL_ALIGNMENT_LEFT)
|
|
_ui.add_panel_label(holder, "Num", "x%s" % int(item.get("num", item.get("number", 0))), Vector2(20.0, 62.0), Vector2(64.0, 24.0), 20, Color.html("#b96108"), 1, Color.html("#ffe59d"), HORIZONTAL_ALIGNMENT_RIGHT)
|
|
|
|
var hit := Button.new()
|
|
hit.name = "Hit"
|
|
hit.position = Vector2.ZERO
|
|
hit.size = holder.size
|
|
hit.focus_mode = Control.FOCUS_NONE
|
|
hit.text = ""
|
|
UiMotionScript.apply_empty_button_style(hit)
|
|
hit.pressed.connect(_open_sell_popup.bind(item.duplicate(true)))
|
|
hit.button_down.connect(func() -> void: UiMotionScript.play_press_scale(_owner, holder))
|
|
hit.button_up.connect(func() -> void: UiMotionScript.play_release_scale(_owner, holder))
|
|
holder.add_child(hit)
|
|
return holder
|
|
|
|
|
|
func _open_sell_popup(item: Dictionary) -> void:
|
|
var config: Dictionary = item.get("config", {})
|
|
if int(config.get("sellPrice", 0)) <= 0:
|
|
_show_toast("该物品不能出售")
|
|
return
|
|
if _modal_layer == null:
|
|
return
|
|
var popup := Control.new()
|
|
popup.name = "SellPopup"
|
|
popup.size = _panel_screen_size
|
|
popup.mouse_filter = Control.MOUSE_FILTER_STOP
|
|
_modal_layer.add_child(popup)
|
|
|
|
var mask := Button.new()
|
|
mask.name = "SellMask"
|
|
mask.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
mask.focus_mode = Control.FOCUS_NONE
|
|
mask.text = ""
|
|
UiMotionScript.apply_empty_button_style(mask)
|
|
popup.add_child(mask)
|
|
|
|
_ui.add_nine_patch(popup, "SellBg", _texture_provider.common("c_panel2"), Vector2(88.0, 302.0), Vector2(464.0, 316.0), Vector4(136.0, 128.0, 4.0, 2.0))
|
|
_ui.add_texture(popup, "Trim1", _texture_provider.common("trimming1"), Vector2(127.0, 279.0), Vector2(70.0, 63.0))
|
|
_ui.add_texture(popup, "Trim2", _texture_provider.common("trimming2"), Vector2(441.0, 302.0), Vector2(35.0, 26.0))
|
|
var title: Texture2D = _texture_provider.common("panel_title_sell")
|
|
_ui.add_texture(popup, "SellTitle", title, Vector2(320.0 - title.get_width() * 0.5, 263.0), title.get_size())
|
|
_ui.add_texture_button(popup, "SellClose", _texture_provider.common("btn_close"), Vector2(480.0, 281.0), Vector2(80.0, 74.0), "", func() -> void: popup.queue_free(), 20)
|
|
|
|
_ui.add_texture(popup, "ItemBg", _texture_provider.common("item_bg"), Vector2(124.0, 348.0), Vector2(90.0, 90.0))
|
|
_ui.add_texture(popup, "Icon", _item_icon_texture(int(config.get("icon", item.get("item_id", 0)))), Vector2(132.0, 356.0), Vector2(74.0, 70.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED)
|
|
_ui.add_panel_label(popup, "Name", str(config.get("name", "物品")), Vector2(226.0, 349.0), Vector2(210.0, 28.0), 24, Color.html("#290202"), 0, Color.TRANSPARENT, HORIZONTAL_ALIGNMENT_LEFT)
|
|
_ui.add_nine_patch(popup, "PriceBg", _texture_provider.common("blue_rect"), Vector2(229.0, 374.0), Vector2(94.0, 21.0), Vector4(23.0, 9.0, 9.0, 2.0))
|
|
_ui.add_panel_label(popup, "PriceTitle", "单价", Vector2(257.0, 376.0), Vector2(44.0, 18.0), 18, Color.WHITE, 0, Color.TRANSPARENT)
|
|
_ui.add_nine_patch(popup, "TotalBg", _texture_provider.common("blue_rect"), Vector2(349.0, 374.0), Vector2(94.0, 21.0), Vector4(23.0, 9.0, 9.0, 2.0))
|
|
_ui.add_panel_label(popup, "TotalTitle", "总计收入", Vector2(360.0, 376.0), Vector2(72.0, 18.0), 18, Color.WHITE, 0, Color.TRANSPARENT)
|
|
_ui.add_texture(popup, "PriceGold", _texture_provider.common("sicon_gold"), Vector2(225.0, 401.0), Vector2(35.0, 35.0))
|
|
_ui.add_texture(popup, "TotalGold", _texture_provider.common("sicon_gold"), Vector2(345.0, 401.0), Vector2(35.0, 35.0))
|
|
var sell_price := int(config.get("sellPrice", 0))
|
|
_ui.add_panel_label(popup, "Price", str(sell_price), Vector2(262.0, 407.0), Vector2(70.0, 24.0), 20, Color.html("#fffbf2"), 2, Color.html("#836344"), HORIZONTAL_ALIGNMENT_LEFT)
|
|
var total_label: Label = _ui.add_panel_label(popup, "TotalPrice", str(sell_price), Vector2(382.0, 407.0), Vector2(88.0, 24.0), 20, Color.html("#fffbf2"), 2, Color.html("#836344"), HORIZONTAL_ALIGNMENT_LEFT)
|
|
_ui.add_nine_patch(popup, "Line", _texture_provider.common("line"), Vector2(129.0, 447.0), Vector2(367.0, 4.0), Vector4.ZERO)
|
|
_ui.add_nine_patch(popup, "Input", _texture_provider.common("inp_mid"), Vector2(244.0, 471.0), Vector2(155.0, 44.0), Vector4(30.0, 20.0, 7.0, 3.0))
|
|
var count_label: Label = _ui.add_panel_label(popup, "Count", "1", Vector2(256.0, 475.0), Vector2(125.0, 34.0), 30, Color.WHITE, 0, Color.TRANSPARENT)
|
|
var state := {"count": 1}
|
|
_ui.add_texture_button(popup, "Sub", _texture_provider.common("btn_sub"), Vector2(168.0, 464.0), Vector2(49.0, 32.0), "", Callable(self, "_change_sell_count").bind(item.duplicate(true), state, count_label, total_label, -1), 20)
|
|
_ui.add_texture_button(popup, "Add", _texture_provider.common("btn_add"), Vector2(424.0, 464.0), Vector2(49.0, 32.0), "", Callable(self, "_change_sell_count").bind(item.duplicate(true), state, count_label, total_label, 1), 20)
|
|
_ui.add_texture_button(popup, "Sell", _texture_provider.common("c_btn3"), Vector2(237.0, 554.0), Vector2(166.0, 58.0), "卖出", Callable(self, "_sell_item").bind(item.duplicate(true), state, popup), 30)
|
|
|
|
popup.pivot_offset = _panel_screen_size * 0.5
|
|
popup.scale = Vector2(0.9, 0.9)
|
|
popup.modulate = Color(1.0, 1.0, 1.0, 0.0)
|
|
if _owner != null:
|
|
var tween := _owner.create_tween().set_parallel(true)
|
|
tween.tween_property(popup, "scale", Vector2.ONE, 0.18).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
|
|
tween.tween_property(popup, "modulate:a", 1.0, 0.12)
|
|
|
|
|
|
func _change_sell_count(item: Dictionary, state: Dictionary, count_label: Label, total_label: Label, delta: int) -> void:
|
|
var current := int(state.get("count", 1))
|
|
var max_count := int(item.get("num", item.get("number", 1)))
|
|
current = clampi(current + delta, 1, maxi(max_count, 1))
|
|
state["count"] = current
|
|
count_label.text = str(current)
|
|
var config: Dictionary = item.get("config", {})
|
|
total_label.text = str(int(config.get("sellPrice", 0)) * current)
|
|
|
|
|
|
func _sell_item(item: Dictionary, state: Dictionary, popup: Control) -> void:
|
|
if _is_busy():
|
|
return
|
|
var token := _token()
|
|
if token.is_empty():
|
|
_show_toast("登录信息失效")
|
|
return
|
|
var count := int(state.get("count", 1))
|
|
var config: Dictionary = item.get("config", {})
|
|
var sell_price := int(config.get("sellPrice", 0))
|
|
if sell_price <= 0:
|
|
_show_toast("该物品不能出售")
|
|
return
|
|
_set_busy(true)
|
|
var result: Dictionary = await _warehouse_api.sell(token, int(item.get("item_id", config.get("id", 0))), count)
|
|
_set_busy(false)
|
|
if not result.get("ok", false):
|
|
_show_toast("出售失败:%s" % result.get("game_status", result.get("status", 0)))
|
|
return
|
|
_call("add_player_gold", [sell_price * count])
|
|
_call("decrease_warehouse_item", [int(item.get("item_id", config.get("id", 0))), count])
|
|
_call("render_player")
|
|
render()
|
|
if popup != null:
|
|
popup.queue_free()
|
|
_show_toast("出售成功,获得%s金豆" % (sell_price * count))
|
|
await _call_async("refresh_store_house")
|
|
|
|
|
|
func _item_icon_texture(icon_id: int) -> Texture2D:
|
|
if _texture_provider != null and _texture_provider.has_method("item_icon"):
|
|
return _texture_provider.item_icon(icon_id)
|
|
if _get_item_icon_texture.is_valid():
|
|
return _get_item_icon_texture.call(icon_id) as Texture2D
|
|
return _texture_provider.common("item_bg")
|
|
|
|
|
|
func _warehouse_items() -> Array[Dictionary]:
|
|
var result = _call("warehouse_items", [], [])
|
|
var items: Array[Dictionary] = []
|
|
if result is Array:
|
|
for item in result:
|
|
if item is Dictionary:
|
|
items.append(item)
|
|
return items
|
|
|
|
|
|
func _token() -> String:
|
|
return str(_call("token", [], ""))
|
|
|
|
|
|
func _is_busy() -> bool:
|
|
return bool(_call("is_operation_in_progress", [], false))
|
|
|
|
|
|
func _set_busy(value: bool) -> void:
|
|
_call("set_operation_in_progress", [value])
|
|
|
|
|
|
func _show_toast(message: String) -> void:
|
|
_call("show_toast", [message])
|
|
|
|
|
|
func _call(name: String, args: Array = [], fallback: Variant = null) -> Variant:
|
|
if _context != null and _context.has_method("call_action"):
|
|
return _context.call_action(name, args, fallback)
|
|
var callable: Callable = _callbacks.get(name, Callable())
|
|
if not callable.is_valid():
|
|
return fallback
|
|
return callable.callv(args)
|
|
|
|
|
|
func _call_async(name: String, args: Array = []) -> Variant:
|
|
if _context != null and _context.has_method("call_action_async"):
|
|
return await _context.call_action_async(name, args)
|
|
var callable: Callable = _callbacks.get(name, Callable())
|
|
if not callable.is_valid():
|
|
return null
|
|
return await callable.callv(args)
|
|
|
|
|
|
func _clear_children(parent: Node) -> void:
|
|
for child in parent.get_children():
|
|
parent.remove_child(child)
|
|
child.queue_free()
|