# 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 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) _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") # 钓鱼服务端事件:成功/鱼获进入系统和战斗日志。 fc.fishing_event.emit(3, 1000, 0) _ck(_last(tSys).contains("鱼钩命中"), "fishing success -> System") fc.fishing_event.emit(5, 30001, 0) _ck(_last(tBattle).contains("捕获物品 #30001"), "fishing item -> Battle") # --- 前缀解析 --- 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")