132 lines
5.1 KiB
GDScript
132 lines
5.1 KiB
GDScript
# chat_test —— P3:聊天窗路由 + 前缀解析 + 气泡 headless 自检。
|
|
# godot --headless --path project --script chat_test.gd
|
|
extends SceneTree
|
|
|
|
const ChatUI = preload("res://ui/chat_ui.gd")
|
|
const NetWorld = preload("res://net_world.gd")
|
|
|
|
class FakeClient extends Node:
|
|
signal chat(type: int, vid: int, text: String)
|
|
# 注:GDScript 不允许 signal 和 func 同名;真 M2Client 是 GDExtension,两者可共存。
|
|
# 这里 whisper 用作方法(chat_ui 调 client.whisper(...));入站 whisper 直接调 chat._on_whisper。
|
|
signal item_picked_up(vnum: int, count: int, from: String)
|
|
signal fishing_event(subheader: int, info: int, dir: int)
|
|
# net_world 也连这些:
|
|
signal entity_spawned(entity: Dictionary)
|
|
signal entity_despawned(vid: int)
|
|
signal entity_moved(vid: int)
|
|
signal entity_main_set(vid: int)
|
|
signal entity_dead(vid: int)
|
|
signal vitals_changed(vid: int)
|
|
signal damage(vid: int, amount: int, flag: int)
|
|
var ents := {1000: {"vid": 1000, "name": "Me"}, 2000: {"vid": 2000, "name": "Bob"}}
|
|
var said := []
|
|
var whispered := []
|
|
func get_entity(vid: int) -> Dictionary: return ents.get(vid, {})
|
|
func get_entities() -> Array: return ents.values()
|
|
func say(t, s) -> bool: said.append([t, s]); return true
|
|
func whisper(to, s) -> bool: whispered.append([to, s]); return true
|
|
|
|
class FakeProto extends Node:
|
|
func item(vnum: int) -> Dictionary:
|
|
if vnum == 30001:
|
|
return {"vnum": vnum, "type": 12, "locale_name": "Trout"}
|
|
return {}
|
|
|
|
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: chat_test (routing + prefixes + bubble)")
|
|
quit(0)
|
|
else:
|
|
printerr("%d check(s) failed" % _fail)
|
|
quit(1)
|
|
|
|
func _last(rt: RichTextLabel) -> String:
|
|
return rt.get_parsed_text().strip_edges().split("\n")[-1]
|
|
|
|
func _run() -> void:
|
|
var canvas := CanvasLayer.new()
|
|
get_root().add_child(canvas)
|
|
var fc := FakeClient.new()
|
|
get_root().add_child(fc)
|
|
var chat: Node = ChatUI.new()
|
|
get_root().add_child(chat)
|
|
chat.setup(fc, canvas, AssetRoot.path())
|
|
var proto := FakeProto.new()
|
|
get_root().add_child(proto)
|
|
chat.set_proto(proto)
|
|
|
|
_ck(chat._log.size() == 4, "4 tabs built")
|
|
var tAll: RichTextLabel = chat._log[0]
|
|
var tWhisper: RichTextLabel = chat._log[1]
|
|
var tSys: RichTextLabel = chat._log[2]
|
|
var tBattle: RichTextLabel = chat._log[3]
|
|
|
|
# 普通聊天 -> 全部标签,带名字
|
|
fc.chat.emit(0, 2000, "hi all")
|
|
_ck(_last(tAll).contains("Bob: hi all"), "talking -> All tab with name")
|
|
_ck(chat._buffers[2].size() == 0, "talking not in System tab")
|
|
|
|
# info -> 系统 + 全部
|
|
fc.chat.emit(1, 0, "you leveled up")
|
|
_ck(_last(tSys).contains("you leveled up"), "info -> System tab")
|
|
_ck(_last(tAll).contains("you leveled up"), "info -> All tab too")
|
|
|
|
# whisper -> 私聊 + 全部(入站直接调 handler,见 FakeClient 注释)
|
|
chat._on_whisper(0, "Alice", "secret")
|
|
_ck(_last(tWhisper).contains("Alice: secret"), "whisper -> Whisper tab")
|
|
_ck(_last(tAll).contains("Alice: secret"), "whisper -> All tab")
|
|
chat._on_whisper(1, "", "server notice")
|
|
_ck(_last(tWhisper).contains("[系统]: server notice"), "system whisper -> [系统]")
|
|
|
|
# 拾取 -> 系统 + 战斗
|
|
fc.item_picked_up.emit(27, 3, "")
|
|
_ck(_last(tSys).contains("拾取 #27 x3"), "pickup -> System")
|
|
_ck(_last(tBattle).contains("拾取 #27 x3"), "pickup -> Battle")
|
|
# 钓鱼服务端事件:SUCCESS 只切动画;FISH 按 IsFishing() + item type
|
|
# 走 ClientVS22 的 notify/success locale 分支。
|
|
fc.fishing_event.emit(0, 1000, 0)
|
|
fc.fishing_event.emit(5, 30001, 0)
|
|
_ck(_last(tBattle).contains("Trout") and _last(tBattle).contains("hooked"),
|
|
"fishing notify fish -> Battle locale")
|
|
fc.fishing_event.emit(3, 1000, 0)
|
|
fc.fishing_event.emit(5, 30001, 0)
|
|
_ck(_last(tSys).contains("You captured Trout!"), "fishing success fish -> System locale")
|
|
chat.on_fishing_feedback("FISHING_WRONG_PLACE")
|
|
_ck(_last(tSys).contains("You cannot go fishing here."), "wrong place -> locale")
|
|
|
|
# --- 前缀解析 ---
|
|
chat._on_submit("hello world")
|
|
_ck(fc.said.size() == 1 and fc.said[0] == [0, "hello world"], "plain -> say(TALKING)")
|
|
chat._on_submit("/g guild msg")
|
|
_ck(fc.said.size() == 2 and fc.said[1] == [4, "guild msg"], "/g -> say(GUILD)")
|
|
chat._on_submit("/s shout!")
|
|
_ck(fc.said[2] == [6, "shout!"], "/s -> say(SHOUT)")
|
|
chat._on_submit("/w Bob psst hey")
|
|
_ck(fc.whispered.size() == 1 and fc.whispered[0] == ["Bob", "psst hey"], "/w Bob ... -> whisper('Bob','psst hey')")
|
|
_ck(_last(tWhisper).contains("→ Bob: psst hey"), "/w -> local echo in Whisper tab")
|
|
|
|
# --- 气泡 ---
|
|
var nw: Node = NetWorld.new()
|
|
get_root().add_child(nw)
|
|
var mount := Node3D.new()
|
|
get_root().add_child(mount)
|
|
nw.setup(fc, mount)
|
|
fc.entity_spawned.emit({"vid": 2000, "name": "Bob", "pos": Vector3.ZERO, "func": 0, "angle_deg": 0.0})
|
|
await process_frame
|
|
var got_main := [""]
|
|
nw.main_bubble.connect(func(t): got_main[0] = t)
|
|
nw._bubble(2000, "hi there")
|
|
var bob: Node3D = nw.node_for(2000)
|
|
_ck(bob != null and bob.get_node_or_null("bubble") != null, "entity 2000 got a bubble node")
|
|
nw.set_local_vid(1000)
|
|
nw._bubble(1000, "my line")
|
|
_ck(got_main[0] == "my line", "local vid bubble -> main_bubble signal")
|