# guild_mark_tail_test —— 已下载的公会会徽接入角色头顶尾标。 # 覆盖会徽晚于角色实体到达、参考端 mark/guild/name 的水平排布,以及清空会徽缓存后 # 不伪留旧 Sprite3D。godot --headless --path project --script guild_mark_tail_test.gd extends SceneTree const NetWorld = preload("res://net_world.gd") const NameTailLayout = preload("res://name_tail_layout.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) signal guild_marks_ready(mark_count: int) signal guild_mark_updated(guild_id: int, img_idx: int) var entities := {} var mark: Image 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 get_guild_name(guild_id: int) -> String: return "Wolves" if guild_id == 88 else "" func get_guild_mark_image(guild_id: int) -> Image: return mark if guild_id == 88 else null func add_entity(vid: int, guild_id: int) -> Dictionary: var d := {"vid": vid, "name": "Alice", "guild": guild_id, "level": 42, "alignment": 0, "ch_type": 0, "race": 0, "hp": 100, "max_hp": 100, "dead": false, "pos": Vector3.ZERO, "pos_cm": Vector3.ZERO, "func": 0, "moving": false, "angle_deg": 0.0, "parts": [0, 0, 0, 0], "affect_flags": 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 _mark_image(color: Color) -> Image: var bytes := PackedByteArray() bytes.resize(16 * 12 * 4) for i in range(0, bytes.size(), 4): bytes[i] = int(color.r * 255.0) bytes[i + 1] = int(color.g * 255.0) bytes[i + 2] = int(color.b * 255.0) bytes[i + 3] = 255 return Image.create_from_data(16, 12, false, Image.FORMAT_RGBA8, bytes) func _init() -> void: await _run() if _fail == 0: print("PASS: guild_mark_tail_test") quit(0) else: printerr("%d check(s) failed" % _fail) quit(1) func _run() -> void: var mount := Node3D.new() get_root().add_child(mount) var client := FakeClient.new() get_root().add_child(client) var world: Node = NetWorld.new() get_root().add_child(world) world.setup(client, mount) # The character is rendered before the side connection finishes downloading marks. var entity := client.add_entity(7, 88) client.entity_spawned.emit(entity) var actor: Node3D = world.node_for(7) _ck(actor != null, "guild actor is spawned") _ck(actor.get_node_or_null("GuildMark") == null, "no mark image -> no stale GuildMark") # Mark download completion refreshes already-visible actors. client.mark = _mark_image(Color(0.2, 0.6, 0.9)) client.guild_marks_ready.emit(1) var mark := actor.get_node_or_null("GuildMark") as Sprite3D var guild := actor.get_node_or_null("GuildTag") as Label3D _ck(mark != null and mark.texture != null, "guild_marks_ready -> world GuildMark Sprite3D") _ck(mark != null and mark.texture.get_width() == 16 and mark.texture.get_height() == 12, "GuildMark keeps the 16x12 mark dimensions") _ck(guild != null and guild.text == "Wolves", "guild tag remains visible beside the mark") _ck(mark != null and guild != null and not is_zero_approx(guild.position.x), "mark present -> guild name gets mark-relative x offset") _ck(mark != null and is_equal_approx(mark.position.y, world.NAME_TAG_DEFAULT_Y + NameTailLayout.MARK_ABOVE_NAME_PX * actor.get_node("Label3D").pixel_size), "mark is placed 26px above the name row") # An incremental update replaces the texture in place rather than duplicating the node. var old_mark := mark client.mark = _mark_image(Color(0.9, 0.2, 0.2)) client.guild_mark_updated.emit(88, 3) var refreshed := actor.get_node_or_null("GuildMark") as Sprite3D _ck(refreshed == old_mark, "guild_mark_updated reuses the existing Sprite3D") _ck(refreshed.texture != null and refreshed.texture.get_image().get_pixel(0, 0).r > 0.8, "guild_mark_updated refreshes the texture") # GC_CHAR_ADD_INFO can remove a guild; the mark is removed with the guild tag. entity["guild"] = 0 client.entity_info.emit(7, entity) await process_frame _ck(actor.get_node_or_null("GuildMark") == null, "guild removal deletes the mark instance") world.free() mount.free() client.free()