Complete remaining client gap features

This commit is contained in:
shenlei
2026-09-04 13:25:05 +09:00
parent 13ccb8d02d
commit 9a4a044da5
47 changed files with 6667 additions and 140 deletions
+98
View File
@@ -0,0 +1,98 @@
# screen_hp_bar_test —— §8.8 屏幕空间 HP 条 headless 自检。
# godot --headless --path project --script screen_hp_bar_test.gd
# 覆盖:不再创建 3D Sprite3D、实体血量同步、世界坐标投影、镜头后/死亡隐藏、地图清理。
extends SceneTree
const NetWorld = preload("res://net_world.gd")
const ScreenHpBar = preload("res://screen_hp_bar.gd")
class FakeClient extends Node:
signal entity_spawned(entity: Dictionary)
signal entity_despawned(vid: int)
signal entity_moved(vid: int)
signal entity_main_set(vid: int)
signal entity_info(vid: int, entity: Dictionary)
signal chat(type: int, vid: int, text: String)
signal vitals_changed(vid: int)
signal entity_dead(vid: int)
signal damage(vid: int, amount: int, flag: int)
var entities := {}
func get_entity(vid: int) -> Dictionary: return entities.get(vid, {})
func get_entities() -> Array: return entities.values()
func get_pvp_relations() -> Array: return []
func get_duel() -> Dictionary: return {}
func add(vid: int, pos: Vector3, main := false) -> Dictionary:
var d := {"vid": vid, "name": "e%d" % vid, "is_main": main,
"pos": pos, "pos_cm": Vector3(pos.x * 100.0, -pos.z * 100.0, 0.0),
"func": 0, "moving": false, "angle_deg": 0.0,
"hp": 100, "max_hp": 100, "dead": false, "race": 0,
"affect_flags": 0, "parts": [0, 0, 0, 0]}
entities[vid] = d
return d
var _fail := 0
func _ck(condition: bool, message: String) -> void:
if not condition:
_fail += 1
printerr("FAIL: " + message)
func _init() -> void:
await _run()
if _fail == 0:
print("PASS: screen_hp_bar_test (§8.8 screen-space HP bar)")
quit(0)
else:
printerr("%d check(s) failed" % _fail)
quit(1)
func _run() -> void:
var mount := Node3D.new()
var client := FakeClient.new()
var camera := Camera3D.new()
var nw: Node = NetWorld.new()
get_root().add_child(mount)
get_root().add_child(client)
get_root().add_child(camera)
get_root().add_child(nw)
camera.position = Vector3(0.0, 2.0, 6.0)
camera.look_at_from_position(Vector3(0.0, 2.0, 6.0), Vector3.ZERO, Vector3.UP)
camera.current = true
nw.setup(client, mount)
nw.set_camera(camera)
client.add(1, Vector3.ZERO, true)
client.entity_main_set.emit(1)
nw.set_local_vid(1)
var entity := client.add(2, Vector3.ZERO)
client.entity_spawned.emit(entity)
await process_frame
await process_frame
var node: Node3D = nw.node_for(2)
var bar := nw._hp_bars.get(2, null) as Control
_ck(node != null, "remote entity exists")
_ck(bar != null and bar is ScreenHpBar, "entity owns a screen Control HP bar")
_ck(node.get_node_or_null("HpBar") == null, "legacy Sprite3D HpBar removed")
_ck(bar != null and bar.visible, "front-facing living entity shows HP bar")
if bar != null:
var expected := camera.unproject_position(node.global_position + Vector3(0, nw.HP_BAR_WORLD_Y, 0))
_ck(is_equal_approx(bar.position.x, expected.x - bar.size.x * 0.5),
"bar x is centered on projected world anchor")
_ck(is_equal_approx(bar.ratio, 1.0), "initial hp ratio = 1")
entity["hp"] = 25
client.vitals_changed.emit(2)
await process_frame
_ck(is_equal_approx(bar.ratio, 0.25), "vitals update changes screen bar ratio")
entity["dead"] = true
client.vitals_changed.emit(2)
await process_frame
_ck(not bar.visible, "dead entity hides screen bar")
entity["dead"] = false
entity["pos"] = Vector3(0, 0, -12)
client.vitals_changed.emit(2)
await process_frame
_ck(not bar.visible, "entity behind camera hides screen bar")
nw.clear_for_map_change()
_ck(nw._hp_bars.is_empty(), "map change clears screen bars")