# 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 close() -> void: _close() 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)