Files

187 lines
8.5 KiB
GDScript
Raw Permalink 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.
# p2b_test —— P2 续:地面掉落物 + PlayerView(真模型)headless 自检。
# godot --headless --path project --script p2b_test.gd
extends SceneTree
const GroundItems = preload("res://ui/ground_items.gd")
const PlayerView = preload("res://ui/player_view.gd")
const TextTailArrange = preload("res://text_tail_arrange.gd")
class FakeClient extends Node:
signal ground_item_added(item: Dictionary)
signal ground_item_removed(vid: int)
var ground := {}
var picked := []
func get_ground_items() -> Array: return ground.values()
func pickup_item(vid) -> bool: picked.append(vid); return true
func add_ground(vid, vnum, pos, owner := "", item_color: Variant = null):
ground[vid] = {"vid": vid, "vnum": vnum, "pos": pos, "owner": owner}
if item_color != null:
ground[vid]["item_color"] = item_color
ground_item_added.emit(ground[vid])
func del_ground(vid):
ground.erase(vid); ground_item_removed.emit(vid)
class FakeProto extends Node:
func item(vnum: int) -> Dictionary:
return {"vnum": vnum, "name": "N%d" % vnum, "locale_name": "掉落%d" % vnum}
var _fail := 0
func _ck(c: bool, m: String) -> void:
if not c:
_fail += 1
printerr("FAIL: " + m)
func _init() -> void:
_run()
if _fail == 0:
print("PASS: p2b_test (ground items + player view)")
quit(0)
else:
printerr("%d check(s) failed" % _fail)
quit(1)
func _run() -> void:
# --- ground items ---
var mount := Node3D.new()
var fc := FakeClient.new()
var fp := FakeProto.new()
var pl := Node3D.new()
get_root().add_child(mount)
get_root().add_child(fc)
get_root().add_child(fp)
get_root().add_child(pl)
pl.position = Vector3.ZERO
var gi: Node = GroundItems.new()
get_root().add_child(gi)
gi.setup(fc, mount, func() -> Node: return pl, fp, null)
fc.add_ground(101, 27, Vector3(1, 0, 1)) # 距玩家 ~1.4m(范围内)
fc.add_ground(102, 28, Vector3(20, 0, 0)) # 远
await process_frame
_ck(mount.get_child_count() == 2, "2 drop nodes spawned")
_ck(mount.get_node_or_null("drop_101") != null, "drop_101 present")
var tag := mount.get_node("drop_101").get_node("tag") as Label3D
_ck(tag.text == "掉落27", "drop name from proto")
# 拾取最近的(101 在范围,102 不在)
var got: int = gi.try_pickup()
_ck(got == 101, "try_pickup -> nearest 101, got %d" % got)
_ck(fc.picked == [101], "client.pickup_item(101) called")
fc.del_ground(101)
await process_frame
_ck(mount.get_node_or_null("drop_101") == null, "drop_101 freed on removed")
_ck(mount.get_child_count() == 1, "1 drop left")
# 远处的捡不到
_ck(gi.try_pickup() == 0, "far item not picked")
# --- §8.8 seam ⑩:多条名条投影后去重叠(ArrangeTextTail item 块)---------------
# 独立的 mount / FakeClient,避免与上面的 gi 抢同名 drop 节点。
var mount2 := Node3D.new()
var fc2 := FakeClient.new()
get_root().add_child(mount2)
get_root().add_child(fc2)
var cam := Camera3D.new()
get_root().add_child(cam)
cam.position = Vector3(0, 3, 6)
cam.look_at(Vector3.ZERO, Vector3.UP)
cam.current = true
var gi2: Node = GroundItems.new()
get_root().add_child(gi2)
gi2.setup(fc2, mount2, func() -> Node: return pl, fp, null, func() -> Camera3D: return cam)
# 单条:名条留默认局部 y。
fc2.add_ground(201, 27, Vector3(0, 0, 0))
await process_frame
gi2._process(0.0)
var t201 := mount2.get_node("drop_201").get_node("tag") as Label3D
_ck(absf(t201.position.y - gi2.TAG_BASE_Y) < 0.0001, "单条名条 -> 留默认局部 y 0.75")
# 同位两条:靠前的(vid 小,m_ItemTextTailList 先)被顶下去,靠后的留原位。
fc2.add_ground(202, 28, Vector3(0, 0, 0))
await process_frame
gi2._process(0.0)
var t202 := mount2.get_node("drop_202").get_node("tag") as Label3D
_ck(t201.position.y < gi2.TAG_BASE_Y - 0.0001, "重叠 -> 靠前名条被下推(世界 y 变小)")
_ck(absf(t202.position.y - gi2.TAG_BASE_Y) < 0.0001, "重叠 -> 靠后名条留默认")
# 被下推量 = (h + gap) px 折世界:(15 + 5) * pixel_size(0.005) = 0.1。
_ck(absf(t201.position.y - (gi2.TAG_BASE_Y - (gi2.TAG_LINE_H_PX + TextTailArrange.PUSH_GAP) * t201.pixel_size)) < 0.0001,
"下推量 = (TAG_LINE_H_PX + PUSH_GAP) * pixel_size = 0.1")
_ck(absf(t201.position.y - t202.position.y) > 0.0001, "两名条错开后局部 y 不同")
# 远离:把 202 挪远,重跑 -> 两条都回默认。
mount2.get_node("drop_202").position = Vector3(-40, 0, -2)
gi2._process(0.0)
_ck(absf(t201.position.y - gi2.TAG_BASE_Y) < 0.0001, "拉开距离 -> 201 名条回默认")
_ck(absf(t202.position.y - gi2.TAG_BASE_Y) < 0.0001, "拉开距离 -> 202 名条回默认")
# --- §8.8 seam ⑩ 末尾:owner 拆成独立 CENTER 黄字 Label3DSetItemTextTailOwner
# + 名字实例下移 15pxArrangeTextTail :200);框仍 yEnd += ownerH + 4(增量 125---
var mount3 := Node3D.new()
var fc3 := FakeClient.new()
get_root().add_child(mount3)
get_root().add_child(fc3)
var gi3: Node = GroundItems.new()
get_root().add_child(gi3)
gi3.setup(fc3, mount3, func() -> Node: return pl, fp, null, func() -> Camera3D: return cam)
fc3.add_ground(301, 27, Vector3(0, 0, 0)) # 靠前、无 owner
var owner_color := Color(0.25, 0.75, 1.0)
fc3.add_ground(302, 28, Vector3(0, 0, 0), "Alice", owner_color) # 靠后、有 owner
await process_frame
gi3._process(0.0)
var d301 := mount3.get_node("drop_301")
var d302 := mount3.get_node("drop_302")
var t301 := d301.get_node("tag") as Label3D
var t302 := d302.get_node("tag") as Label3D
# owner 拆成独立 owner_tag:名字实例只放名字,owner_tag 放 "Alice" + "'s"(黄 / CENTER)。
_ck(t302.text == "掉落28", "名字实例只放名字(owner 后缀不再拼串,:723")
_ck(d301.get_node_or_null("owner_tag") == null, "无 owner 的掉落不建 owner_tag:741-745")
var o302 := d302.get_node_or_null("owner_tag") as Label3D
_ck(o302 != null, "有 owner 的掉落建独立 owner_tag:715")
_ck(o302.text == "Alice's", "owner_tag 文本 = owner + strOwnership:722 字面 \"'s\"")
_ck(o302.horizontal_alignment == HORIZONTAL_ALIGNMENT_CENTER, "owner_tag CENTER 对齐(:727")
_ck(o302.modulate.is_equal_approx(owner_color), "owner_tag 使用道具品阶色(:729 pInsertTextTail->Color")
# owner 名条框:无字体度量时 name_h = owner_h = 15 -> yEnd = 15 + 15 + 4 = 34(增量 125 不变)。
# 靠前 301(无 owner)被顶 (34 + PUSH_GAP) px 折世界:(34 + 5) * 0.005 = 0.195。
var push_owner: float = (gi3.TAG_LINE_H_PX * 2.0 + 4.0 + TextTailArrange.PUSH_GAP) * t301.pixel_size
_ck(absf(t301.position.y - (gi3.TAG_BASE_Y - push_owner)) < 0.0001,
"靠前名条被带 owner 的框顶 (34+5)*px = 0.195")
# 靠后 302 未被推:owner 行落基点、名字行下移 15px:195 owner 在 y、:200 名字在 y+15)。
_ck(absf(o302.position.y - gi3.TAG_BASE_Y) < 0.0001, "owner_tag 落尾标基点(:195")
_ck(absf(t302.position.y - (gi3.TAG_BASE_Y - gi3.OWNER_NAME_GAP_PX * t302.pixel_size)) < 0.0001,
"名字实例在 owner 行下移 15px:200 y + 15.0f")
# --- strOwnership 经 setup(possessive) 注入(SetItemTextTailOwner :722 本地化语素)---
var mount4 := Node3D.new()
var fc4 := FakeClient.new()
get_root().add_child(mount4)
get_root().add_child(fc4)
var gi4: Node = GroundItems.new()
get_root().add_child(gi4)
gi4.setup(fc4, mount4, func() -> Node: return pl, fp, null, func() -> Camera3D: return cam, "@")
fc4.add_ground(401, 27, Vector3(0, 0, 0), "Bob")
await process_frame
var o401 := mount4.get_node("drop_401").get_node_or_null("owner_tag") as Label3D
_ck(o401 != null and o401.text == "Bob@", "setup(possessive=\"@\") -> owner_tag 文本 = \"Bob@\"")
# --- player view(有真资产才建)---
var assets := AssetRoot.path()
if not DirAccess.dir_exists_absolute(assets.path_join("PC/ymir work/pc/warrior")):
print(" (skip PlayerView: no PC assets)")
return
if not ClassDB.class_exists("Metin2Model"):
print(" (skip PlayerView: extension not built)")
return
var pv: Node3D = PlayerView.new()
get_root().add_child(pv)
var ok: bool = pv.build(assets, 0) # race 0 = warrior male
_ck(ok, "PlayerView.build(race 0) ok")
_ck(pv.model != null, "PlayerView has Metin2Model")
_ck(pv.motion_dir.ends_with("warrior/general"), "motion_dir -> warrior/general (%s)" % pv.motion_dir)
# 转发 set
pv.set("weapon_gr2", "/tmp/fake_weapon.gr2")
_ck(String(pv.model.get("weapon_gr2")) == "/tmp/fake_weapon.gr2", "PlayerView forwards weapon_gr2 to model")
pv.set_anim_state("run")
await process_frame
if pv.anim:
_ck(String(pv.anim.get("anim_path")).ends_with("run.msa"), "set_anim_state('run') -> run.msa")