完善客户端功能并同步差距文档
This commit is contained in:
+229
-45
@@ -9,22 +9,38 @@
|
||||
# 卖:inventory_ui 在商店开着时右键道具 → 调 shop_ui.sell(cell)。
|
||||
extends Node
|
||||
|
||||
const CursorManager = preload("res://ui/cursor_manager.gd")
|
||||
const ItemTooltip = preload("res://ui/item_tooltip.gd")
|
||||
const SHOP_SLOT_COUNT := 40 # shop.SHOP_SLOT_COUNT (== SHOP_HOST_ITEM_MAX_NUM)
|
||||
const ITEM_ANTIFLAG_SELL := 1 << 8
|
||||
const ITEM_FLAG_COUNT_PER_1GOLD := 1 << 3
|
||||
|
||||
var client: Node
|
||||
var proto: Node
|
||||
var item_list # ItemListDB (RefCounted)
|
||||
var _tooltip_builder: RefCounted
|
||||
var _root: Control
|
||||
var _tabbar: HBoxContainer
|
||||
var _list: VBoxContainer
|
||||
var _grid: GridContainer
|
||||
var _err: Label
|
||||
var _sell_quantity: SpinBox
|
||||
var _active_tab := 0
|
||||
var item_mouse: Node
|
||||
var cursor_manager: Node
|
||||
var audio: Node
|
||||
var _mode := 1 # 1=BUY, 2=SELL
|
||||
var _buy_mode_button: Button
|
||||
var _sell_mode_button: Button
|
||||
var _sell_drop_target: Button
|
||||
var _sell_confirm: ConfirmationDialog
|
||||
var _buy_confirm: ConfirmationDialog
|
||||
|
||||
func setup(m2client: Node, parent: Node, proto_node: Node = null, ilist: RefCounted = null) -> void:
|
||||
client = m2client
|
||||
proto = proto_node
|
||||
item_list = ilist
|
||||
_tooltip_builder = ItemTooltip.new()
|
||||
_tooltip_builder.setup(AssetRoot.path(), "en", proto)
|
||||
_build(parent)
|
||||
if client.has_signal("shop_opened"):
|
||||
client.shop_opened.connect(func(_v): open())
|
||||
@@ -44,13 +60,26 @@ func open() -> void:
|
||||
_err.text = ""
|
||||
_sell_quantity.value = 1
|
||||
_active_tab = 0
|
||||
_set_mode(1)
|
||||
refresh()
|
||||
|
||||
func _close() -> void:
|
||||
if item_mouse and item_mouse.has_method("unregister_owner"):
|
||||
item_mouse.unregister_owner(self)
|
||||
if item_mouse.has_method("is_attached") and item_mouse.is_attached():
|
||||
item_mouse.cancel()
|
||||
_root.visible = false
|
||||
_err.text = ""
|
||||
if _sell_quantity:
|
||||
_sell_quantity.value = 1
|
||||
if is_instance_valid(_sell_confirm):
|
||||
_sell_confirm.queue_free()
|
||||
_sell_confirm = null
|
||||
if is_instance_valid(_buy_confirm):
|
||||
_buy_confirm.hide()
|
||||
_buy_confirm.queue_free()
|
||||
_buy_confirm = null
|
||||
_set_mode(1)
|
||||
|
||||
func close_and_leave() -> void:
|
||||
client.shop_close()
|
||||
@@ -59,10 +88,119 @@ func close_and_leave() -> void:
|
||||
func sell(inv_cell: int, count: int = -1) -> void:
|
||||
if not is_open() or inv_cell < 0:
|
||||
return
|
||||
var item := _inventory_item(inv_cell)
|
||||
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):
|
||||
if not item.is_empty():
|
||||
amount = clampi(amount, 1, int(item.get("count", amount)))
|
||||
else:
|
||||
amount = clampi(amount, 1, 200)
|
||||
var payload := item.duplicate(true)
|
||||
payload["window"] = 1
|
||||
payload["cell"] = inv_cell
|
||||
payload["vnum"] = int(item.get("vnum", 0))
|
||||
payload["count"] = int(item.get("count", amount))
|
||||
_request_sell(payload, amount)
|
||||
|
||||
func _drop_to_sell(payload: Dictionary) -> bool:
|
||||
if _mode != 2 or payload.is_empty() or int(payload.get("window", -1)) != 1:
|
||||
return false
|
||||
var cell := int(payload.get("cell", -1))
|
||||
if cell < 0 or not client.has_method("shop_sell"):
|
||||
return false
|
||||
var source_count := maxi(1, int(payload.get("count", 1)))
|
||||
var amount := clampi(int(_sell_quantity.value), 1, mini(200, source_count))
|
||||
return _request_sell(payload, amount)
|
||||
|
||||
func _inventory_item(cell: int) -> Dictionary:
|
||||
if client == null or not client.has_method("get_inventory"):
|
||||
return {}
|
||||
for item in client.get_inventory():
|
||||
if int(item.get("cell", -1)) == cell:
|
||||
return item
|
||||
return {}
|
||||
|
||||
func _proto_item(vnum: int) -> Dictionary:
|
||||
if vnum > 0 and proto and proto.has_method("item"):
|
||||
return proto.item(vnum)
|
||||
return {}
|
||||
|
||||
func _sell_price(vnum: int, count: int, item_data: Dictionary = {}) -> int:
|
||||
var d := item_data if not item_data.is_empty() else _proto_item(vnum)
|
||||
var unit := maxi(0, int(d.get("sell_price", 0)))
|
||||
var flags := int(d.get("flags", 0))
|
||||
var raw := (count / maxi(1, unit)) if (flags & ITEM_FLAG_COUNT_PER_1GOLD) != 0 else unit * count
|
||||
return maxi(0, int(raw) / 5)
|
||||
|
||||
func _is_valuable(payload: Dictionary, item_data: Dictionary) -> bool:
|
||||
if int(item_data.get("sell_price", 0)) > 5000:
|
||||
return true
|
||||
var sockets: Array = payload.get("sockets", [])
|
||||
for value in sockets:
|
||||
if int(value) != 0:
|
||||
return true
|
||||
return false
|
||||
|
||||
func _request_sell(payload: Dictionary, amount: int) -> bool:
|
||||
var vnum := int(payload.get("vnum", 0))
|
||||
var d := _proto_item(vnum)
|
||||
var anti_flags := int(payload.get("anti_flags", 0)) | int(d.get("anti_flags", 0))
|
||||
if (anti_flags & ITEM_ANTIFLAG_SELL) != 0:
|
||||
_on_error("CANNOT_SELL")
|
||||
_play_ui("loginfail.wav")
|
||||
return false
|
||||
if _is_valuable(payload, d):
|
||||
_ask_sell_confirmation(payload, amount, _sell_price(vnum, amount, d))
|
||||
return true
|
||||
return _send_sell(int(payload.get("cell", -1)), amount)
|
||||
|
||||
func _send_sell(cell: int, amount: int) -> bool:
|
||||
if cell < 0 or not client.has_method("shop_sell"):
|
||||
return false
|
||||
if not client.shop_sell(cell, amount):
|
||||
_on_error("SEND_FAILED")
|
||||
return false
|
||||
_play_ui("money.wav")
|
||||
return true
|
||||
|
||||
func _ask_sell_confirmation(payload: Dictionary, amount: int, price: int) -> void:
|
||||
if is_instance_valid(_sell_confirm):
|
||||
_sell_confirm.hide()
|
||||
_sell_confirm.queue_free()
|
||||
_sell_confirm = ConfirmationDialog.new()
|
||||
_sell_confirm.title = "确认出售"
|
||||
_sell_confirm.dialog_text = "出售 %d 个物品,获得 %d 金币?" % [amount, price]
|
||||
_sell_confirm.ok_button_text = "出售"
|
||||
_sell_confirm.cancel_button_text = "取消"
|
||||
var dialog := _sell_confirm
|
||||
dialog.confirmed.connect(func() -> void:
|
||||
_sell_confirm = null
|
||||
dialog.hide()
|
||||
dialog.queue_free()
|
||||
_send_sell(int(payload.get("cell", -1)), amount))
|
||||
dialog.canceled.connect(func() -> void:
|
||||
_sell_confirm = null
|
||||
dialog.hide()
|
||||
dialog.queue_free())
|
||||
_root.add_child(dialog)
|
||||
if dialog.is_inside_tree():
|
||||
dialog.popup_centered()
|
||||
else:
|
||||
dialog.call_deferred("popup_centered")
|
||||
|
||||
func _play_ui(name: String) -> void:
|
||||
if audio and audio.has_method("play_ui"):
|
||||
audio.play_ui(name)
|
||||
|
||||
func _set_mode(mode: int) -> void:
|
||||
_mode = 2 if mode == 2 else 1
|
||||
if _buy_mode_button:
|
||||
_buy_mode_button.button_pressed = _mode == 1
|
||||
if _sell_mode_button:
|
||||
_sell_mode_button.button_pressed = _mode == 2
|
||||
if _sell_drop_target:
|
||||
_sell_drop_target.visible = _mode == 2
|
||||
if cursor_manager and cursor_manager.has_method("set_cursor"):
|
||||
cursor_manager.set_cursor(CursorManager.SELL if _mode == 2 else CursorManager.BUY)
|
||||
|
||||
func _name_of(vnum: int) -> String:
|
||||
if proto and proto.has_method("item"):
|
||||
@@ -78,8 +216,8 @@ func refresh() -> void:
|
||||
for c in _tabbar.get_children():
|
||||
_tabbar.remove_child(c)
|
||||
c.queue_free()
|
||||
for c in _list.get_children():
|
||||
_list.remove_child(c)
|
||||
for c in _grid.get_children():
|
||||
_grid.remove_child(c)
|
||||
c.queue_free()
|
||||
|
||||
# START_EX 商店有多个货架(shop.GetTabCount);普通 START 商店 tabs 只 1 个。
|
||||
@@ -108,47 +246,66 @@ func refresh() -> void:
|
||||
_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
|
||||
var by_pos := {}
|
||||
for it in items:
|
||||
_list.add_child(_row(it, base))
|
||||
var slot := int(it.get("pos", 0))
|
||||
if slot >= 0 and slot < SHOP_SLOT_COUNT:
|
||||
by_pos[slot] = it
|
||||
for slot in SHOP_SLOT_COUNT:
|
||||
var it: Dictionary = by_pos.get(slot, {})
|
||||
_grid.add_child(_slot(it, base + slot))
|
||||
|
||||
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):
|
||||
func _slot(it: Dictionary, pos: int) -> Button:
|
||||
var slot := Button.new()
|
||||
slot.custom_minimum_size = Vector2(66, 30)
|
||||
slot.clip_text = true
|
||||
var vnum := int(it.get("vnum", 0))
|
||||
if vnum > 0:
|
||||
var count := int(it.get("count", 0))
|
||||
slot.text = _name_of(vnum).substr(0, 7)
|
||||
if count > 1:
|
||||
slot.text += " ×%d" % count
|
||||
if _tooltip_builder:
|
||||
var pd: Dictionary = proto.item(vnum) if proto and proto.has_method("item") else {}
|
||||
slot.tooltip_text = _tooltip_builder.format(vnum, maxi(1, count), pd, it)
|
||||
slot.tooltip_text += "\n价格:%d" % int(it.get("price", 0))
|
||||
slot.pressed.connect(func() -> void: _buy_slot(pos, it))
|
||||
else:
|
||||
slot.disabled = true
|
||||
return slot
|
||||
|
||||
func _buy_slot(pos: int, item: Dictionary) -> void:
|
||||
if _mode != 1:
|
||||
_set_mode(1)
|
||||
return
|
||||
if item.is_empty() or pos < 0:
|
||||
return
|
||||
if is_instance_valid(_buy_confirm):
|
||||
_buy_confirm.hide()
|
||||
_buy_confirm.queue_free()
|
||||
_buy_confirm = ConfirmationDialog.new()
|
||||
_buy_confirm.title = "确认购买"
|
||||
_buy_confirm.dialog_text = "购买 %s?\n价格:%d 金币" % [_name_of(int(item.get("vnum", 0))), int(item.get("price", 0))]
|
||||
_buy_confirm.ok_button_text = "购买"
|
||||
_buy_confirm.cancel_button_text = "取消"
|
||||
var dialog := _buy_confirm
|
||||
dialog.confirmed.connect(func() -> void:
|
||||
_buy_confirm = null
|
||||
dialog.hide()
|
||||
dialog.queue_free()
|
||||
if not client.shop_buy(pos, 1):
|
||||
_on_error("SEND_FAILED"))
|
||||
row.add_child(buy)
|
||||
return row
|
||||
dialog.canceled.connect(func() -> void:
|
||||
_buy_confirm = null
|
||||
dialog.hide()
|
||||
dialog.queue_free())
|
||||
_root.add_child(dialog)
|
||||
if dialog.is_inside_tree():
|
||||
dialog.popup_centered()
|
||||
else:
|
||||
dialog.call_deferred("popup_centered")
|
||||
|
||||
func _on_error(kind: String) -> void:
|
||||
var tbl := {
|
||||
@@ -202,15 +359,42 @@ func _build(parent: Node) -> void:
|
||||
_sell_quantity.position = Vector2(160, 356)
|
||||
_sell_quantity.size = Vector2(70, 26)
|
||||
_root.add_child(_sell_quantity)
|
||||
_sell_drop_target = Button.new()
|
||||
_sell_drop_target.name = "SellDropTarget"
|
||||
_sell_drop_target.text = "拖动物品到此出售"
|
||||
_sell_drop_target.position = Vector2(12, 325)
|
||||
_sell_drop_target.size = Vector2(210, 26)
|
||||
_sell_drop_target.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_sell_drop_target.visible = false
|
||||
_root.add_child(_sell_drop_target)
|
||||
if item_mouse and item_mouse.has_method("register_target"):
|
||||
item_mouse.register_target(_sell_drop_target, _drop_to_sell, self)
|
||||
_buy_mode_button = Button.new()
|
||||
_buy_mode_button.text = "购买"
|
||||
_buy_mode_button.toggle_mode = true
|
||||
_buy_mode_button.position = Vector2(235, 325)
|
||||
_buy_mode_button.size = Vector2(62, 26)
|
||||
_buy_mode_button.pressed.connect(func() -> void: _set_mode(1))
|
||||
_root.add_child(_buy_mode_button)
|
||||
_sell_mode_button = Button.new()
|
||||
_sell_mode_button.text = "出售"
|
||||
_sell_mode_button.toggle_mode = true
|
||||
_sell_mode_button.position = Vector2(300, 325)
|
||||
_sell_mode_button.size = Vector2(62, 26)
|
||||
_sell_mode_button.pressed.connect(func() -> void: _set_mode(2))
|
||||
_root.add_child(_sell_mode_button)
|
||||
_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)
|
||||
_grid = GridContainer.new()
|
||||
_grid.columns = 5
|
||||
_grid.position = Vector2(12, 58)
|
||||
_grid.size = Vector2(350, 252)
|
||||
_grid.add_theme_constant_override("h_separation", 3)
|
||||
_grid.add_theme_constant_override("v_separation", 2)
|
||||
_root.add_child(_grid)
|
||||
var close_btn := Button.new()
|
||||
close_btn.text = "离开"
|
||||
close_btn.position = Vector2(300, 360)
|
||||
|
||||
Reference in New Issue
Block a user