Implement 40250 classic client port
This commit is contained in:
+158
-17
@@ -9,7 +9,9 @@
|
||||
# cube_changed → 刷新配方 / 材料 / 金币
|
||||
# cube_result(vnum,count,ok) → 结果提示
|
||||
# cube_closed → 隐藏
|
||||
# 选中一个配方 → [制作] → M2Client.cube_make(index)。
|
||||
# 选中一个配方、把背包物品放入材料槽 → [制作] → M2Client.cube_make(index)。
|
||||
# 40250 服务端按材料槽匹配配方,`result_index` 只保留给 m2dev 后端;classic
|
||||
# 会安全地忽略它并执行一次 `/cube make`。
|
||||
extends Node
|
||||
|
||||
var client: Node
|
||||
@@ -21,6 +23,9 @@ var _list: VBoxContainer
|
||||
var _mat: VBoxContainer
|
||||
var _make_btn: Button
|
||||
var _sel := -1
|
||||
var _cube_slots: Dictionary = {} # cube index:int -> inventory cell:int
|
||||
var _craft_in_flight := false
|
||||
var _cube_npc_vnum := 0
|
||||
|
||||
func setup(m2client: Node, parent: Node, proto_node: Node = null) -> void:
|
||||
client = m2client
|
||||
@@ -34,12 +39,25 @@ func setup(m2client: Node, parent: Node, proto_node: Node = null) -> void:
|
||||
client.cube_changed.connect(refresh)
|
||||
if client.has_signal("cube_result"):
|
||||
client.cube_result.connect(_on_result)
|
||||
if client.has_signal("inventory_changed"):
|
||||
client.inventory_changed.connect(func(_window, _cell): refresh())
|
||||
|
||||
func is_open() -> bool:
|
||||
return _root != null and _root.visible
|
||||
|
||||
func _on_open(npc_vnum: int) -> void:
|
||||
if npc_vnum <= 0:
|
||||
return
|
||||
var current: Dictionary = client.get_cube() if client.has_method("get_cube") else {}
|
||||
if current.has("open") and not bool(current.get("open", false)):
|
||||
return
|
||||
var current_npc := int(current.get("npc_vnum", 0))
|
||||
if current_npc > 0 and current_npc != npc_vnum:
|
||||
return
|
||||
_sel = -1
|
||||
_cube_slots.clear()
|
||||
_craft_in_flight = false
|
||||
_cube_npc_vnum = npc_vnum
|
||||
_root.visible = true
|
||||
_title.text = "제작 (Cube) · NPC #%d" % npc_vnum
|
||||
_status.text = ""
|
||||
@@ -48,10 +66,15 @@ func _on_open(npc_vnum: int) -> void:
|
||||
refresh()
|
||||
|
||||
func _on_result(vnum: int, count: int, ok: bool) -> void:
|
||||
_craft_in_flight = false
|
||||
if ok:
|
||||
_status.text = "제작 성공: %s ×%d" % [_name_of(vnum), count]
|
||||
# 40250 在成功后消耗材料;等待 inventory/cube_changed 的最终状态前,
|
||||
# 先清掉本地映射,避免旧背包格继续显示为可用材料。
|
||||
_cube_slots.clear()
|
||||
else:
|
||||
_status.text = "제작 실패"
|
||||
refresh()
|
||||
|
||||
func _name_of(vnum: int) -> String:
|
||||
if proto and proto.has_method("item"):
|
||||
@@ -65,6 +88,9 @@ func refresh() -> void:
|
||||
if client == null or _root == null or not _root.visible:
|
||||
return
|
||||
var cube: Dictionary = client.get_cube()
|
||||
if not cube.get("open", true):
|
||||
_root.visible = false
|
||||
return
|
||||
var recipes: Array = cube.get("recipes", [])
|
||||
var results: Array = cube.get("results", [])
|
||||
for c in _list.get_children():
|
||||
@@ -105,21 +131,123 @@ func _refresh_materials() -> void:
|
||||
c.queue_free()
|
||||
var cube: Dictionary = client.get_cube()
|
||||
var recipes: Array = cube.get("recipes", [])
|
||||
_make_btn.disabled = _sel < 0
|
||||
var recipe: Dictionary = _selected_recipe()
|
||||
_make_btn.disabled = _sel < 0 or recipe.is_empty() or _craft_in_flight \
|
||||
or not _recipe_can_craft(recipe)
|
||||
if _sel < 0 or _sel >= recipes.size():
|
||||
_mat.add_child(_lbl("재료 정보를 불러오는 중…", 12))
|
||||
return
|
||||
var r: Dictionary = recipes[_sel]
|
||||
_mat.add_child(_lbl("필요 골드: %d" % int(r.get("gold", 0)), 12))
|
||||
var groups: Array = r.get("material_groups", [])
|
||||
if groups.is_empty():
|
||||
_mat.add_child(_lbl("(재료 없음)", 12))
|
||||
for g in groups:
|
||||
var parts: Array = []
|
||||
for s in (g as Array):
|
||||
parts.append("%s ×%d" % [_name_of(int((s as Dictionary).get("vnum", 0))),
|
||||
int((s as Dictionary).get("count", 1))])
|
||||
_mat.add_child(_lbl("• " + " / ".join(parts), 12))
|
||||
else:
|
||||
var r: Dictionary = recipe
|
||||
_mat.add_child(_lbl("필요 골드: %d" % int(r.get("gold", 0)), 12))
|
||||
var groups: Array = r.get("material_groups", [])
|
||||
if groups.is_empty():
|
||||
_mat.add_child(_lbl("(재료 없음)", 12))
|
||||
for g in groups:
|
||||
var parts: Array = []
|
||||
for s in (g as Array):
|
||||
parts.append("%s ×%d" % [_name_of(int((s as Dictionary).get("vnum", 0))),
|
||||
int((s as Dictionary).get("count", 1))])
|
||||
_mat.add_child(_lbl("• " + " / ".join(parts), 12))
|
||||
if not _recipe_can_craft(r):
|
||||
_mat.add_child(_lbl("(材料不足或未放入材料槽)", 11))
|
||||
elif client.has_method("get_points") and int(r.get("gold", 0)) > int(client.get_points().get("gold", 0)):
|
||||
_make_btn.disabled = true
|
||||
_mat.add_child(_lbl("(金币不足)", 11))
|
||||
|
||||
_mat.add_child(HSeparator.new())
|
||||
_mat.add_child(_lbl("재료 슬롯 (클릭하면 추가/삭제)", 12))
|
||||
for slot in _cube_slots.keys():
|
||||
var cell := int(_cube_slots[slot])
|
||||
var row := HBoxContainer.new()
|
||||
var label := _lbl("cube[%d] ← bag[%d]" % [int(slot), cell], 11)
|
||||
row.add_child(label)
|
||||
var remove := Button.new()
|
||||
remove.text = "삭제"
|
||||
remove.pressed.connect(func() -> void:
|
||||
if client.has_method("cube_delete_item") and client.cube_delete_item(int(slot)):
|
||||
_cube_slots.erase(slot)
|
||||
refresh())
|
||||
row.add_child(remove)
|
||||
_mat.add_child(row)
|
||||
|
||||
_mat.add_child(_lbl("背包物品:", 12))
|
||||
var used_cells := {}
|
||||
for cell in _cube_slots.values():
|
||||
used_cells[int(cell)] = true
|
||||
var inventory: Array = client.get_inventory() if client.has_method("get_inventory") else []
|
||||
var added := 0
|
||||
for item in inventory:
|
||||
var inv_cell := int(item.get("cell", -1))
|
||||
var item_vnum := int(item.get("vnum", 0))
|
||||
if used_cells.has(inv_cell):
|
||||
continue
|
||||
if not _recipe_accepts_vnum(recipe, item_vnum):
|
||||
continue
|
||||
var free_slot := _first_free_cube_slot()
|
||||
if free_slot < 0:
|
||||
break
|
||||
var add := Button.new()
|
||||
add.text = "+ bag[%d] %s ×%d" % [inv_cell, _name_of(item_vnum),
|
||||
int(item.get("count", 1))]
|
||||
add.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
add.pressed.connect(func() -> void:
|
||||
var target := _first_free_cube_slot()
|
||||
if target >= 0 and client.has_method("cube_add_item") and client.cube_add_item(target, inv_cell):
|
||||
_cube_slots[target] = inv_cell
|
||||
refresh())
|
||||
_mat.add_child(add)
|
||||
added += 1
|
||||
if added == 0:
|
||||
_mat.add_child(_lbl("(可用背包物品 없음)", 11))
|
||||
|
||||
func _selected_recipe() -> Dictionary:
|
||||
if client == null or not client.has_method("get_cube"):
|
||||
return {}
|
||||
var recipes: Array = client.get_cube().get("recipes", [])
|
||||
if _sel < 0 or _sel >= recipes.size():
|
||||
return {}
|
||||
return recipes[_sel] as Dictionary
|
||||
|
||||
func _recipe_accepts_vnum(recipe: Dictionary, vnum: int) -> bool:
|
||||
if vnum <= 0:
|
||||
return false
|
||||
for group in recipe.get("material_groups", []):
|
||||
for option in group as Array:
|
||||
if int((option as Dictionary).get("vnum", 0)) == vnum:
|
||||
return true
|
||||
return false
|
||||
|
||||
func _recipe_can_craft(recipe: Dictionary) -> bool:
|
||||
if recipe.is_empty():
|
||||
return false
|
||||
var provided := {}
|
||||
if client.has_method("get_inventory"):
|
||||
var inventory: Array = client.get_inventory()
|
||||
for cell in _cube_slots.values():
|
||||
for item in inventory:
|
||||
if int(item.get("cell", -1)) == int(cell):
|
||||
var vnum := int(item.get("vnum", 0))
|
||||
provided[vnum] = int(provided.get(vnum, 0)) + maxi(1, int(item.get("count", 1)))
|
||||
break
|
||||
for group in recipe.get("material_groups", []):
|
||||
var matched := false
|
||||
for option in group as Array:
|
||||
var material: Dictionary = option as Dictionary
|
||||
var vnum := int(material.get("vnum", 0))
|
||||
var need := maxi(1, int(material.get("count", 1)))
|
||||
if int(provided.get(vnum, 0)) >= need:
|
||||
provided[vnum] = int(provided[vnum]) - need
|
||||
matched = true
|
||||
break
|
||||
if not matched:
|
||||
return false
|
||||
return true
|
||||
|
||||
func _first_free_cube_slot() -> int:
|
||||
for i in 24:
|
||||
if not _cube_slots.has(i):
|
||||
return i
|
||||
return -1
|
||||
|
||||
func _lbl(t: String, sz: int) -> Label:
|
||||
var l := Label.new()
|
||||
@@ -165,10 +293,23 @@ func _build(parent: Node) -> void:
|
||||
_make_btn.text = "제작"
|
||||
_make_btn.disabled = true
|
||||
_make_btn.pressed.connect(func() -> void:
|
||||
if _sel >= 0 and client.has_method("cube_make"):
|
||||
client.cube_make(_sel))
|
||||
var recipe := _selected_recipe()
|
||||
if _sel < 0 or recipe.is_empty():
|
||||
_status.text = "请先选择配方"
|
||||
return
|
||||
if not _recipe_can_craft(recipe):
|
||||
_status.text = "材料不足或未放入材料槽"
|
||||
refresh()
|
||||
return
|
||||
if client.has_method("cube_make") and client.cube_make(_sel):
|
||||
_craft_in_flight = true
|
||||
_status.text = "制作中…"
|
||||
_make_btn.disabled = true)
|
||||
brow.add_child(_make_btn)
|
||||
var close := Button.new()
|
||||
close.text = "닫기"
|
||||
close.pressed.connect(func() -> void: _root.visible = false)
|
||||
close.pressed.connect(func() -> void:
|
||||
if client.has_method("cube_close"):
|
||||
client.cube_close()
|
||||
_root.visible = false)
|
||||
brow.add_child(close)
|
||||
|
||||
Reference in New Issue
Block a user