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

420 lines
14 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SafeboxUI (P8) —— 仓库窗。
#
# var bu := preload("res://ui/safebox_ui.gd").new()
# add_child(bu)
# bu.setup(m2client, canvas_parent, proto) # proto 可空
#
# `safebox_changed` 刷新(size>0 → 打开)。列出仓库道具 + [取出]。
# inventory_ui 在仓库开着时右键道具 → 调 bu.deposit(win, cell)。
# 取出/存入用第一个空位(简版:deposit 用 safe_pos = 下一个空格;checkout 用道具 cell)。
extends Node
const ItemTooltip = preload("res://ui/item_tooltip.gd")
var client: Node
var proto: Node
var _tooltip_builder: RefCounted
var _root: Control
var _list: VBoxContainer
var _gold: Label
var _title: Label
var _status: Label
var _password_dialog: ConfirmationDialog
var _grid: GridContainer
var _cells: Dictionary = {}
var _page_label: Label
var _page := 0
var item_mouse: Node
var _mobile_mode := false
var _mobile_inventory_title: Label
var _mobile_inventory_scroll: ScrollContainer
var _mobile_inventory_list: VBoxContainer
var _mobile_deposit_pending := {}
const SAFE_PAGE_SLOTS := 45
const SAFE_WINDOW := 3
const INVENTORY_WINDOW := 1
func setup(m2client: Node, parent: Node, proto_node: Node = null) -> void:
client = m2client
proto = proto_node
_tooltip_builder = ItemTooltip.new()
_tooltip_builder.setup(AssetRoot.path(), "en", proto)
_build(parent)
if client.has_signal("safebox_changed"):
client.safebox_changed.connect(refresh)
if client.has_signal("safebox_password_required"):
client.safebox_password_required.connect(func(): _ask_password("safebox"))
if client.has_signal("safebox_wrong_password"):
client.safebox_wrong_password.connect(func():
if is_instance_valid(_status):
_status.text = "仓库密码错误")
func is_open() -> bool:
return _root != null and _root.visible
func set_mobile_mode(enabled: bool) -> void:
_mobile_mode = enabled
_apply_mobile_layout()
_refresh_mobile_inventory()
func get_mobile_window() -> Control:
return _root
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()
if _root:
_root.visible = false
_page = 0
_mobile_deposit_pending.clear()
if is_instance_valid(_password_dialog):
_password_dialog.queue_free()
_password_dialog = null
func deposit(inv_window: int, inv_cell: int) -> void:
if not is_open() or inv_window < 0 or inv_cell < 0:
return
if inv_window == INVENTORY_WINDOW and _mobile_deposit_pending.has(inv_cell):
_status.text = "该物品的存入请求已发送"
return
var safe_pos := _next_free_slot()
if safe_pos < 0:
_status.text = "仓库已满"
return
if not client.has_method("safebox_checkin"):
_status.text = "存入请求发送失败"
return
if not client.safebox_checkin(safe_pos, inv_window, inv_cell):
_status.text = "存入请求发送失败"
return
if _mobile_mode and inv_window == INVENTORY_WINDOW:
_mobile_deposit_pending[inv_cell] = true
_status.text = "已发送存入请求"
func _next_free_slot() -> int:
var used := {}
for it in client.get_safebox_items():
used[int(it.get("cell", -1))] = true
var cap: int = maxi(1, client.get_safebox_size()) * 45
for i in range(cap):
if not used.has(i):
return i
return -1
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 _tooltip_for(item: Dictionary) -> String:
var vnum := int(item.get("vnum", 0))
if _tooltip_builder:
var pd: Dictionary = proto.item(vnum) if proto and proto.has_method("item") else {}
return _tooltip_builder.format(vnum, int(item.get("count", 1)), pd, item)
return "%s\n#%d ×%d" % [_name_of(vnum), vnum, int(item.get("count", 1))]
func refresh() -> void:
if client == null:
return
_root.visible = client.is_safebox_open()
if not _root.visible:
return
_status.text = ""
var pages := maxi(1, int(client.get_safebox_size()))
_page = clampi(_page, 0, pages - 1)
_title.text = "仓库(第 %d/%d 页)" % [_page + 1, pages]
_page_label.text = "%d / %d" % [_page + 1, pages]
_gold.text = "仓库金币: %d" % client.get_safebox_gold()
for c in _list.get_children():
c.queue_free()
_render_grid()
var items: Array = client.get_safebox_items()
if items.is_empty():
var e := Label.new()
e.text = "(空)"
_list.add_child(e)
else:
for it in items:
_list.add_child(_row(it))
_refresh_mobile_inventory()
func _render_grid() -> void:
if _grid == null:
return
for pos in _cells:
var cell: Button = _cells[pos]
cell.text = ""
cell.tooltip_text = ""
cell.set_meta("vnum", 0)
cell.set_meta("count", 0)
var items: Array = client.get_safebox_items() if client and client.has_method("get_safebox_items") else []
for it in items:
var absolute_pos := int(it.get("cell", -1))
var pos := absolute_pos - _page * SAFE_PAGE_SLOTS
if not _cells.has(pos):
continue
var cell: Button = _cells[pos]
var vnum := int(it.get("vnum", 0))
var count := int(it.get("count", 1))
cell.text = "%s%s" % [_name_of(vnum).substr(0, 7), (" ×%d" % count) if count > 1 else ""]
cell.tooltip_text = _tooltip_for(it)
cell.set_meta("vnum", vnum)
cell.set_meta("count", count)
func _drop_to_slot(payload: Dictionary, local_pos: int) -> bool:
if payload.is_empty() or not _cells.has(local_pos):
return false
var safe_pos := _page * SAFE_PAGE_SLOTS + local_pos
var cell: Button = _cells[local_pos]
if int(cell.get_meta("vnum", 0)) != 0:
return false
var source_window := int(payload.get("window", -1))
var source_cell := int(payload.get("cell", -1))
if source_cell < 0:
return false
if source_window in [1, 2] and client.has_method("safebox_checkin"):
return client.safebox_checkin(safe_pos, source_window, source_cell)
if source_window == SAFE_WINDOW and client.has_method("safebox_move"):
return client.safebox_move(source_cell, safe_pos, 1)
return false
func _on_grid_input(local_pos: int, event: InputEvent) -> void:
if not item_mouse or not item_mouse.has_method("attach_item"):
return
if not (event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT
and event.pressed):
return
var cell: Button = _cells[local_pos]
var vnum := int(cell.get_meta("vnum", 0))
if vnum == 0:
return
item_mouse.attach_item(SAFE_WINDOW, _page * SAFE_PAGE_SLOTS + local_pos, vnum,
int(cell.get_meta("count", 1)), null, "safebox")
func _on_mobile_grid_tap(local_pos: int) -> void:
if not _mobile_mode or client == null or not _cells.has(local_pos):
return
var cell: Button = _cells[local_pos]
if int(cell.get_meta("vnum", 0)) == 0 or not client.has_method("safebox_checkout"):
return
var target := _first_free_inv()
if target < 0:
_status.text = "背包已满"
return
if not client.safebox_checkout(_page * SAFE_PAGE_SLOTS + local_pos, 1, target):
_status.text = "取出请求发送失败"
func _apply_mobile_layout() -> void:
if _root == null:
return
if _mobile_mode:
# The source-bag candidates stay inside the same hosted surface as the
# warehouse grid, so both obey one safe-area scale and touch boundary.
_root.size = Vector2(688, 400)
_root.position = Vector2(-344, -200)
_grid.position = Vector2(12, 54)
if _mobile_inventory_title:
_mobile_inventory_title.visible = true
_mobile_inventory_title.position = Vector2(350, 48)
if _mobile_inventory_scroll:
_mobile_inventory_scroll.visible = true
_mobile_inventory_scroll.position = Vector2(350, 76)
_mobile_inventory_scroll.size = Vector2(326, 292)
else:
_root.size = Vector2(340, 400)
_root.position = Vector2(-170, -200)
_grid.position = Vector2(12, 54)
if _mobile_inventory_title:
_mobile_inventory_title.visible = false
if _mobile_inventory_scroll:
_mobile_inventory_scroll.visible = false
func _refresh_mobile_inventory() -> void:
if not _mobile_mode or _mobile_inventory_list == null or not is_open():
return
for child in _mobile_inventory_list.get_children():
child.queue_free()
var inventory: Array = client.get_inventory() if client and client.has_method("get_inventory") else []
var shown := 0
for item in inventory:
var cell := int(item.get("cell", -1))
var vnum := int(item.get("vnum", 0))
if cell < 0 or vnum <= 0 or _mobile_deposit_pending.has(cell):
continue
var button := Button.new()
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
button.text = "%s ×%d · 点按存入" % [_name_of(vnum), int(item.get("count", 1))]
button.custom_minimum_size = Vector2(316, 34)
button.tooltip_text = _tooltip_for(item)
var captured_cell := cell
button.pressed.connect(func() -> void: deposit(INVENTORY_WINDOW, captured_cell))
_mobile_inventory_list.add_child(button)
shown += 1
if shown == 0:
var empty := Label.new()
empty.text = "(没有可存入物品)"
empty.add_theme_font_size_override("font_size", 12)
_mobile_inventory_list.add_child(empty)
func _change_page(delta: int) -> void:
var pages := maxi(1, int(client.get_safebox_size()))
_page = clampi(_page + delta, 0, pages - 1)
refresh()
func _ask_password(kind: String) -> void:
if is_instance_valid(_password_dialog):
_password_dialog.queue_free()
_password_dialog = ConfirmationDialog.new()
_password_dialog.title = "商城密码" if kind == "mall" else "仓库密码"
_password_dialog.dialog_text = "请输入 16 位密码"
_password_dialog.ok_button_text = "确认"
_password_dialog.cancel_button_text = "取消"
var edit := LineEdit.new()
edit.secret = true
edit.max_length = 6
edit.placeholder_text = "密码"
edit.virtual_keyboard_type = LineEdit.KEYBOARD_TYPE_NUMBER
edit.custom_minimum_size = Vector2(360, 44) if _mobile_mode else Vector2(220, 28)
_password_dialog.add_child(edit)
_password_dialog.confirmed.connect(func() -> void:
var sent: bool = client.safebox_password(edit.text) if kind == "safebox" else client.mall_password(edit.text)
if not sent and is_instance_valid(_status):
_status.text = "密码格式无效"
_password_dialog.queue_free()
_password_dialog = null)
_password_dialog.canceled.connect(func() -> void:
_password_dialog.queue_free()
_password_dialog = null)
if _mobile_mode:
_password_dialog.set_meta("mobile_modal", true)
_password_dialog.set_meta("mobile_title", "商城密码" if kind == "mall" else "仓库密码")
_password_dialog.min_size = Vector2i(460, 230)
_password_dialog.size = Vector2i(460, 230)
_root.add_child(_password_dialog)
if _mobile_mode:
_password_dialog.popup_centered(Vector2i(460, 230))
else:
_password_dialog.popup_centered()
func _row(it: Dictionary) -> Control:
var row := HBoxContainer.new()
row.custom_minimum_size = Vector2(300, 0)
var nm := Label.new()
nm.text = "%s ×%d" % [_name_of(int(it.get("vnum", 0))), int(it.get("count", 1))]
nm.custom_minimum_size = Vector2(220, 0)
nm.add_theme_font_size_override("font_size", 12)
row.add_child(nm)
var out := Button.new()
out.text = "取出"
var cell := int(it.get("cell", 0))
out.pressed.connect(func() -> void:
var target := _first_free_inv()
if target < 0:
_status.text = "背包已满"
return
if not client.safebox_checkout(cell, 1, target):
_status.text = "取出请求发送失败")
row.add_child(out)
return row
func _first_free_inv() -> int:
var used := {}
for it in client.get_inventory():
used[int(it.get("cell", -1))] = true
for i in range(90):
if not used.has(i):
return i
return -1
func _build(parent: Node) -> void:
_root = Control.new()
_root.set_anchors_preset(Control.PRESET_CENTER)
_root.position = Vector2(-170, -200)
_root.size = Vector2(340, 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.06, 0.08, 0.09, 0.97)
sb.set_corner_radius_all(4)
panel.add_theme_stylebox_override("panel", sb)
_root.add_child(panel)
_title = Label.new()
_title.text = "仓库"
_title.position = Vector2(12, 8)
_root.add_child(_title)
var prev := Button.new()
prev.text = ""
prev.position = Vector2(235, 6)
prev.size = Vector2(28, 24)
prev.pressed.connect(func(): _change_page(-1))
_root.add_child(prev)
_page_label = Label.new()
_page_label.position = Vector2(266, 10)
_page_label.size = Vector2(60, 18)
_page_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_root.add_child(_page_label)
var next := Button.new()
next.text = ""
next.position = Vector2(302, 6)
next.size = Vector2(28, 24)
next.pressed.connect(func(): _change_page(1))
_root.add_child(next)
_gold = Label.new()
_gold.text = "仓库金币: 0"
_gold.position = Vector2(12, 30)
_gold.modulate = Color(0.95, 0.85, 0.5)
_gold.add_theme_font_size_override("font_size", 12)
_root.add_child(_gold)
_status = Label.new()
_status.position = Vector2(12, 382)
_status.add_theme_font_size_override("font_size", 11)
_status.add_theme_color_override("font_color", Color(1, 0.65, 0.55))
_root.add_child(_status)
_list = VBoxContainer.new()
_list.position = Vector2(12, 54)
_list.add_theme_constant_override("separation", 4)
_root.add_child(_list)
_list.visible = false
_grid = GridContainer.new()
_grid.columns = 5
_grid.position = Vector2(12, 54)
_grid.add_theme_constant_override("h_separation", 4)
_grid.add_theme_constant_override("v_separation", 4)
_root.add_child(_grid)
for pos in SAFE_PAGE_SLOTS:
var cell := Button.new()
cell.custom_minimum_size = Vector2(58, 30)
cell.add_theme_font_size_override("font_size", 9)
cell.set_meta("safe_pos", pos)
cell.gui_input.connect(func(e: InputEvent): _on_grid_input(pos, e))
cell.pressed.connect(func(): _on_mobile_grid_tap(pos))
if item_mouse and item_mouse.has_method("register_target"):
item_mouse.register_target(cell,
func(payload: Dictionary): return _drop_to_slot(payload, pos), self)
_grid.add_child(cell)
_cells[pos] = cell
_mobile_inventory_title = Label.new()
_mobile_inventory_title.text = "背包 · 点按存入"
_mobile_inventory_title.add_theme_font_size_override("font_size", 12)
_mobile_inventory_title.add_theme_color_override("font_color", Color(0.95, 0.84, 0.58))
_mobile_inventory_title.visible = false
_root.add_child(_mobile_inventory_title)
_mobile_inventory_scroll = ScrollContainer.new()
_mobile_inventory_scroll.name = "MobileInventory"
_mobile_inventory_scroll.visible = false
_mobile_inventory_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
_mobile_inventory_list = VBoxContainer.new()
_mobile_inventory_list.add_theme_constant_override("separation", 4)
_mobile_inventory_scroll.add_child(_mobile_inventory_list)
_root.add_child(_mobile_inventory_scroll)
_apply_mobile_layout()