Files
mtgodot-poc/project/ui/dragon_soul_ui.gd
T
shenandClaude Sonnet 5 47baf6c0c6 Metin2 game client (P0–P11) + mobile asset pipeline
Networked client on the existing Godot 4.7 + libgr2 renderer:
- net: m2dev wire protocol (libsodium KX + XChaCha20), auth/select/game
  phases, EntityStore world model, ~all GC/CG headers. char create/delete,
  private shop / mall / cube, SHOP_GC_START_EX, guild, party (+ CG_PARTY_SET_STATE),
  quests, dragon soul, refine, safebox, exchange.
- UI: in-game windows migrated 1:1 from the reference uiscript/root .py —
  char status (/stat), inventory+equipment, select-item ([SELECT_ITEM] quest
  token), system-option + game-option + ESC system menu, private-shop 39-grid,
  party info board, shop tabs, atlas, minimap, quickbar, chat, …
- EterGrnLib polish: GR2 material blend/two-sided, LOD crossfade, motion-event
  dispatch, contact shadow, ray-AABB picking, weapon grip pre-transform.

Portable asset IO (A1) — all extension/libgr2/formats/mtproto reads routed
through godot::FileAccess (res:// PCK works on iOS/Android); standalone-lib
*_path() kept for the non-Godot CTests. AssetResolver + PropertyRegistry
switched to a baked index (bake_asset_index.gd) instead of std::filesystem.

Mobile builds: build-{android,ios}.sh, export-android.sh, pack-assets.sh,
gen-debug-keystore.sh. Assets ship as a zip mounted at runtime by
project/asset_pack.gd (adb push now; HTTP download is a drop-in later).

ctest 10/10, 34 GDScript suites, macOS/iOS/Android all build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EJxkHiNKS4kybHS3XKyAJ
2026-08-31 20:02:12 +09:00

171 lines
4.9 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.
# DragonSoulUI (M4) —— 龙魂精炼窗:`ds_window_open` 来时弹。
#
# var du := preload("res://ui/dragon_soul_ui.gd").new()
# add_child(du)
# du.setup(m2client, canvas_parent, proto) # proto 可空(出名字)
#
# 三种模式:升级(0) / 改良(1) / 精炼(2) —— 对应 CG_DRAGON_SOUL_REFINE 的
# DS_SUB_DO_UPGRADE / IMPROVEMENT / REFINE。
# 打开时右键背包道具 → 填进 15 格中的下一个空位(grid[0] = 龙魂,其余为材料)。
# [执行] → `M2Client.ds_refine(mode, cells)``ds_refine_result(ok, sub, cell)` → 提示。
extends Node
const SLOTS := 15
const MODE_NAMES := ["升级", "改良", "精炼"]
var client: Node
var proto: Node
var _root: Control
var _mode := 2
var _cells: Array[int] = [] # 背包 cell,按加入顺序
var _mode_btns: Array[Button] = []
var _slot_labels: Array[Label] = []
var _status: Label
func setup(m2client: Node, parent: Node, proto_node: Node = null) -> void:
client = m2client
proto = proto_node
_build(parent)
if client.has_signal("ds_window_open"):
client.ds_window_open.connect(_on_open)
if client.has_signal("ds_refine_result"):
client.ds_refine_result.connect(_on_result)
func is_open() -> bool:
return _root != null and _root.visible
func toggle() -> void:
if _root:
_root.visible = not _root.visible
func _on_open() -> void:
_cells.clear()
_refresh()
if _root:
_root.visible = true
# 供 game_scene._on_inv_context 调用:把背包 cell 塞进下一个空格。
func add_cell(window: int, cell: int) -> void:
if window != 1: # 仅背包(WINDOW_INVENTORY
return
if _cells.has(cell):
_cells.erase(cell) # 再次右键 = 取出
elif _cells.size() < SLOTS:
_cells.append(cell)
_refresh()
func _name_of(vnum: int) -> String:
if vnum == 0:
return "-"
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 _cell_text(i: int) -> String:
if i >= _cells.size():
return "[%d] —" % i
var c: int = _cells[i]
var vnum := 0
if client and client.has_method("get_item"):
var it: Dictionary = client.get_item(1, c)
vnum = int(it.get("vnum", 0))
var tag := "龙魂" if i == 0 else "材料"
return "[%d] %s %s (格%d)" % [i, tag, _name_of(vnum), c]
func _refresh() -> void:
for i in SLOTS:
_slot_labels[i].text = _cell_text(i)
for m in 3:
_mode_btns[m].disabled = (m == _mode)
if _status:
_status.text = "模式:%s 已放 %d/%d" % [MODE_NAMES[_mode], _cells.size(), SLOTS]
func _set_mode(m: int) -> void:
_mode = clampi(m, 0, 2)
_refresh()
func _do() -> void:
if client and client.has_method("ds_refine") and not _cells.is_empty():
client.ds_refine(_mode, _cells)
func _on_result(ok: bool, sub_type: int, cell: int) -> void:
if not _status:
return
if ok:
_cells.clear()
_refresh()
_status.text = ("✔ 精炼成功(格%d" % cell) if ok else ("✘ 精炼失败(原因码 %d" % sub_type)
func _build(parent: Node) -> void:
_root = Control.new()
_root.set_anchors_preset(Control.PRESET_CENTER)
_root.position = Vector2(-190, -220)
_root.size = Vector2(380, 440)
_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.05, 0.09, 0.98)
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)
var modes := HBoxContainer.new()
modes.position = Vector2(14, 34)
modes.add_theme_constant_override("separation", 8)
_root.add_child(modes)
for m in 3:
var b := Button.new()
b.text = MODE_NAMES[m]
b.custom_minimum_size = Vector2(112, 28)
b.pressed.connect(_set_mode.bind(m))
modes.add_child(b)
_mode_btns.append(b)
var grid := VBoxContainer.new()
grid.position = Vector2(16, 74)
grid.add_theme_constant_override("separation", 2)
_root.add_child(grid)
for i in SLOTS:
var l := Label.new()
l.add_theme_font_size_override("font_size", 12)
grid.add_child(l)
_slot_labels.append(l)
_status = Label.new()
_status.position = Vector2(16, 366)
_root.add_child(_status)
var row := HBoxContainer.new()
row.position = Vector2(16, 394)
row.add_theme_constant_override("separation", 12)
_root.add_child(row)
var go := Button.new()
go.text = "执行"
go.custom_minimum_size = Vector2(150, 30)
go.pressed.connect(_do)
row.add_child(go)
var clr := Button.new()
clr.text = "清空"
clr.custom_minimum_size = Vector2(90, 30)
clr.pressed.connect(func(): _cells.clear(); _refresh())
row.add_child(clr)
var cancel := Button.new()
cancel.text = "关闭"
cancel.custom_minimum_size = Vector2(90, 30)
cancel.pressed.connect(func(): _root.visible = false)
row.add_child(cancel)
_refresh()