Files
mtgodot-poc/project/guild_war_skill_test.gd
T
shenandClaude Sonnet 5 47baf6c0c6 Metin2 game client (P0–P11) + mobile asset pipeline
Networked client on the existing Godot 4.7 + libgr2 renderer:
- net: m2dev wire protocol (libsodium KX + XChaCha20), auth/select/game
  phases, EntityStore world model, ~all GC/CG headers. char create/delete,
  private shop / mall / cube, SHOP_GC_START_EX, guild, party (+ CG_PARTY_SET_STATE),
  quests, dragon soul, refine, safebox, exchange.
- UI: in-game windows migrated 1:1 from the reference uiscript/root .py —
  char status (/stat), inventory+equipment, select-item ([SELECT_ITEM] quest
  token), system-option + game-option + ESC system menu, private-shop 39-grid,
  party info board, shop tabs, atlas, minimap, quickbar, chat, …
- EterGrnLib polish: GR2 material blend/two-sided, LOD crossfade, motion-event
  dispatch, contact shadow, ray-AABB picking, weapon grip pre-transform.

Portable asset IO (A1) — all extension/libgr2/formats/mtproto reads routed
through godot::FileAccess (res:// PCK works on iOS/Android); standalone-lib
*_path() kept for the non-Godot CTests. AssetResolver + PropertyRegistry
switched to a baked index (bake_asset_index.gd) instead of std::filesystem.

Mobile builds: build-{android,ios}.sh, export-android.sh, pack-assets.sh,
gen-debug-keystore.sh. Assets ship as a zip mounted at runtime by
project/asset_pack.gd (adb push now; HTTP download is a drop-in later).

ctest 10/10, 34 GDScript suites, macOS/iOS/Android all build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EJxkHiNKS4kybHS3XKyAJ
2026-08-31 20:02:12 +09:00

111 lines
4.0 KiB
GDScript

# guild_war_skill_test —— 公会窗「技能 / 公会战」两页 headless 自检。
# godot --headless --path project --script guild_war_skill_test.gd
extends SceneTree
const GuildUI = preload("res://ui/guild_ui.gd")
class FakeTable extends Node:
func for_category(cat: String) -> Array:
return [4051, 4052, 4053] if cat == "GUILD" else []
func name_of(id: int) -> String:
return {4051: "축복", 4052: "미리내", 4053: "무장"}.get(id, "skill %d" % id)
class FakeClient extends Node:
signal guild_changed()
signal guild_skill_changed()
signal guild_war_changed()
var guild := {"in_guild": true, "id": 77, "name": "Dragons", "level": 12,
"member_count": 2, "max_member_count": 32, "gold": 5}
var skill := {"valid": false}
var wars := []
var war := {"opp_guild_id": 0, "opp_name": "", "type": 0, "state": 0}
var calls := []
func is_in_game() -> bool: return true
func get_guild() -> Dictionary: return guild
func get_guild_members() -> Array: return []
func get_guild_grades() -> Array: return []
func get_guild_skill() -> Dictionary: return skill
func get_guild_wars() -> Array: return wars
func get_guild_war() -> Dictionary: return war
func use_guild_skill(vnum: int, target: int) -> bool:
calls.append(["use", vnum, target]); return true
func declare_guild_war(name: String) -> bool:
calls.append(["war", name]); 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: guild_war_skill_test")
quit(0)
else:
printerr("%d check(s) failed" % _fail)
quit(1)
func _btn(root: Node, label: String) -> Button:
for b in root.find_children("*", "Button", true, false):
if String(b.text) == label:
return b
return null
func _labels_text(page: Node) -> String:
var s := ""
for c in page.find_children("*", "Label", true, false):
s += String(c.text) + "\n"
return s
func _run() -> void:
var canvas := CanvasLayer.new()
get_root().add_child(canvas)
var fc := FakeClient.new()
get_root().add_child(fc)
var tbl := FakeTable.new()
get_root().add_child(tbl)
var gu: Node = GuildUI.new()
get_root().add_child(gu)
gu.setup(fc, canvas, tbl)
gu.toggle()
_ck(gu.is_open(), "公会窗打开")
_ck(gu._pages.size() == 3, "3 页")
# --- 技能页 ---
gu._set_tab(1)
_ck(_labels_text(gu._pages[1]).contains("还没收到"), "技能页:无数据提示")
fc.skill = {"valid": true, "skill_point": 5, "guild_point": 1200, "max_guild_point": 5000,
"levels": [3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 7]}
fc.guild_skill_changed.emit()
var st := _labels_text(gu._pages[1])
_ck(st.contains("技能点 5") and st.contains("1200 / 5000"), "技能页:点数行")
_ck(st.contains("축복") and st.contains("Lv 3"), "技能页:GUILD 技能名 + 等级")
_ck(st.contains("무장") and st.contains("Lv 7"), "技能页:第 12 个技能配到 levels[11]")
# 축복 lv3 -> 有「施放」按钮;미리내 lv0 -> 无
var cast := _btn(gu._pages[1], "施放")
_ck(cast != null, "技能页:可施放技能有按钮")
cast.pressed.emit()
_ck(fc.calls == [["use", 4051, 0]], "施放 -> use_guild_skill(4051, 0)")
# --- 公会战页 ---
gu._set_tab(2)
_ck(_labels_text(gu._pages[2]).contains("当前无公会战"), "公会战页:无战提示")
_ck(_labels_text(gu._pages[2]).contains("无"), "公会战页:进行中列表为空")
# 宣战
gu._war_name_edit.text = "Tigers"
_btn(gu._pages[2], "宣战").pressed.emit()
_ck(fc.calls[-1] == ["war", "Tigers"], "宣战 -> declare_guild_war(\"Tigers\")")
# 收到 WAR ON_WAR + WAR_LIST
fc.war = {"opp_guild_id": 88, "opp_name": "Tigers", "type": 1, "state": 6}
fc.wars = [{"src": 77, "dst": 88, "src_name": "Dragons", "dst_name": "Tigers"}]
fc.guild_war_changed.emit()
var wt := _labels_text(gu._pages[2])
_ck(wt.contains("对 Tigers") and wt.contains("交战中"), "公会战页:当前战状态")
_ck(wt.contains("Dragons vs Tigers") and wt.contains("◀我方"), "公会战页:GvG 行 + 我方标记")