Files
mtgodot-poc/project/ui/shop_ui.gd
T

216 lines
6.5 KiB
GDScript

# ShopUI (P8) —— NPC 商店窗。
#
# var su := preload("res://ui/shop_ui.gd").new()
# add_child(su)
# su.setup(m2client, canvas_parent, proto, item_list) # proto / item_list 可空
#
# `shop_opened(vid)` → 打开并列出货物(名字 + 价格 + [买])。
# `shop_closed` → 关闭。`shop_error(kind)` → 顶部红字提示。
# 卖:inventory_ui 在商店开着时右键道具 → 调 shop_ui.sell(cell)。
extends Node
const SHOP_SLOT_COUNT := 40 # shop.SHOP_SLOT_COUNT (== SHOP_HOST_ITEM_MAX_NUM)
var client: Node
var proto: Node
var item_list # ItemListDB (RefCounted)
var _root: Control
var _tabbar: HBoxContainer
var _list: VBoxContainer
var _err: Label
var _sell_quantity: SpinBox
var _active_tab := 0
func setup(m2client: Node, parent: Node, proto_node: Node = null, ilist: RefCounted = null) -> void:
client = m2client
proto = proto_node
item_list = ilist
_build(parent)
if client.has_signal("shop_opened"):
client.shop_opened.connect(func(_v): open())
if client.has_signal("shop_closed"):
client.shop_closed.connect(_close)
if client.has_signal("shop_error"):
client.shop_error.connect(_on_error)
func is_open() -> bool:
return _root != null and _root.visible
func open() -> void:
_root.visible = true
_err.text = ""
_sell_quantity.value = 1
_active_tab = 0
refresh()
func _close() -> void:
_root.visible = false
_err.text = ""
if _sell_quantity:
_sell_quantity.value = 1
func close_and_leave() -> void:
client.shop_close()
_close()
func sell(inv_cell: int, count: int = -1) -> void:
if not is_open() or inv_cell < 0:
return
var amount := count if count > 0 else int(_sell_quantity.value)
amount = clampi(amount, 1, 200)
if client.has_method("shop_sell") and not client.shop_sell(inv_cell, amount):
_on_error("SEND_FAILED")
func _name_of(vnum: int) -> String:
if proto and proto.has_method("item"):
var d: Dictionary = proto.item(vnum)
var n := String(d.get("locale_name", d.get("name", "")))
if n != "":
return n
return "#%d" % vnum
func refresh() -> void:
if not is_open() or client == null:
return
for c in _tabbar.get_children():
_tabbar.remove_child(c)
c.queue_free()
for c in _list.get_children():
_list.remove_child(c)
c.queue_free()
# START_EX 商店有多个货架(shop.GetTabCount);普通 START 商店 tabs 只 1 个。
var tabs: Array = []
if client.has_method("get_shop"):
tabs = client.get_shop().get("tabs", [])
if tabs.is_empty():
# 兼容没有 get_shop 的旧桩
tabs = [{"name": "", "items": client.get_shop_items()}]
_active_tab = clampi(_active_tab, 0, tabs.size() - 1)
if tabs.size() > 1:
_tabbar.visible = true
for i in tabs.size():
var tb := Button.new()
tb.toggle_mode = true
var tn := String(tabs[i].get("name", ""))
tb.text = tn if tn != "" else "货架 %d" % (i + 1)
tb.button_pressed = (i == _active_tab)
var idx := i
tb.pressed.connect(func() -> void:
_active_tab = idx
refresh())
_tabbar.add_child(tb)
else:
_tabbar.visible = false
var items: Array = tabs[_active_tab].get("items", [])
if items.is_empty():
var e := Label.new()
e.text = "(无货物)"
_list.add_child(e)
return
# uishop.py: 买位置 = tabIdx * SHOP_SLOT_COUNT + slotPos
var base := _active_tab * SHOP_SLOT_COUNT
for it in items:
_list.add_child(_row(it, base))
func _row(it: Dictionary, pos_base := 0) -> Control:
var row := HBoxContainer.new()
row.custom_minimum_size = Vector2(320, 0)
var nm := Label.new()
nm.text = _name_of(int(it.get("vnum", 0)))
nm.custom_minimum_size = Vector2(170, 0)
nm.add_theme_font_size_override("font_size", 12)
row.add_child(nm)
var pr := Label.new()
pr.text = "%d 金" % int(it.get("price", 0))
pr.custom_minimum_size = Vector2(90, 0)
pr.modulate = Color(0.95, 0.85, 0.5)
row.add_child(pr)
var buy := Button.new()
buy.text = "买"
var pos := pos_base + int(it.get("pos", 0))
var stock := int(it.get("count", 0))
var quantity := SpinBox.new()
quantity.name = "Quantity"
quantity.min_value = 1
quantity.max_value = clampi(stock if stock > 0 else 200, 1, 200)
quantity.step = 1
quantity.value = 1
quantity.custom_minimum_size = Vector2(64, 0)
row.add_child(quantity)
buy.pressed.connect(func() -> void:
var amount := clampi(int(quantity.value), 1, int(quantity.max_value))
if not client.shop_buy(pos, amount):
_on_error("SEND_FAILED"))
row.add_child(buy)
return row
func _on_error(kind: String) -> void:
var tbl := {
"NOT_ENOUGH_MONEY": "金币不足",
"SOLDOUT": "已售罄",
"INVENTORY_FULL": "背包已满",
"INVALID_POS": "位置无效",
"NOT_ENOUGH_ITEM": "物品数量不足",
"CANNOT_BUY": "当前物品不可购买",
"CANNOT_SELL": "当前物品不可出售",
"SHOP_BUSY": "商店正在处理上一笔交易",
"SEND_FAILED": "交易请求发送失败",
}
_err.text = str(tbl.get(kind, kind))
func _build(parent: Node) -> void:
_root = Control.new()
_root.set_anchors_preset(Control.PRESET_CENTER)
_root.position = Vector2(-190, -200)
_root.size = Vector2(380, 400)
_root.visible = false
_root.set_meta("is_titlebar", true)
parent.add_child(_root)
var panel := Panel.new()
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
var sb := StyleBoxFlat.new()
sb.bg_color = Color(0.08, 0.07, 0.06, 0.97)
sb.set_corner_radius_all(4)
panel.add_theme_stylebox_override("panel", sb)
_root.add_child(panel)
var title := Label.new()
title.text = "商店"
title.position = Vector2(12, 8)
_root.add_child(title)
_err = Label.new()
_err.position = Vector2(60, 10)
_err.modulate = Color(1, 0.4, 0.4)
_err.add_theme_font_size_override("font_size", 12)
_root.add_child(_err)
var sell_hint := Label.new()
sell_hint.text = "出售数量(背包右键):"
sell_hint.position = Vector2(12, 360)
sell_hint.add_theme_font_size_override("font_size", 11)
_root.add_child(sell_hint)
_sell_quantity = SpinBox.new()
_sell_quantity.name = "SellQuantity"
_sell_quantity.min_value = 1
_sell_quantity.max_value = 200
_sell_quantity.value = 1
_sell_quantity.step = 1
_sell_quantity.position = Vector2(160, 356)
_sell_quantity.size = Vector2(70, 26)
_root.add_child(_sell_quantity)
_tabbar = HBoxContainer.new()
_tabbar.position = Vector2(12, 30)
_tabbar.add_theme_constant_override("separation", 4)
_tabbar.visible = false
_root.add_child(_tabbar)
_list = VBoxContainer.new()
_list.position = Vector2(12, 58)
_list.add_theme_constant_override("separation", 4)
_root.add_child(_list)
var close_btn := Button.new()
close_btn.text = "离开"
close_btn.position = Vector2(300, 360)
close_btn.pressed.connect(close_and_leave)
_root.add_child(close_btn)