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
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# skill_fx_test —— 技能特效表(skill id / 内建 id → .mse)headless 自检。
|
||||
# godot --headless --path project --script skill_fx_test.gd
|
||||
extends SceneTree
|
||||
|
||||
const SkillFx = preload("res://fx/skill_fx.gd")
|
||||
const SkillTable = preload("res://ui/skill_table.gd")
|
||||
|
||||
# 假 EffectRegistry:记录 resolve / spawn,指定哪些名字「存在」
|
||||
class FakeFxr extends RefCounted:
|
||||
var exists := {} # name -> true
|
||||
var spawned := []
|
||||
func resolve(name: String) -> String:
|
||||
return "res://fake/%s.mse" % name if exists.get(name, false) else ""
|
||||
func spawn(name: String, _parent, _one := true):
|
||||
spawned.append(name)
|
||||
return null
|
||||
|
||||
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: skill_fx_test")
|
||||
quit(0)
|
||||
else:
|
||||
printerr("%d check(s) failed" % _fail)
|
||||
quit(1)
|
||||
|
||||
func _run() -> void:
|
||||
# 用真 skilldesc.txt(有就用,没有用桩表)
|
||||
var st: RefCounted = SkillTable.new()
|
||||
var assets := AssetRoot.path()
|
||||
var have_real := false
|
||||
for lang in ["en", "common"]:
|
||||
if st.load_file(assets.path_join("locale/locale/%s/skilldesc.txt" % lang)):
|
||||
have_real = true
|
||||
break
|
||||
|
||||
var fxr := FakeFxr.new()
|
||||
var host := Node3D.new()
|
||||
get_root().add_child(host)
|
||||
var sfx: RefCounted = SkillFx.new()
|
||||
sfx.setup(fxr, st)
|
||||
|
||||
if have_real:
|
||||
# skill 1 = Three-Way Cut, motion_name samyeon
|
||||
# 后缀 = clamp(2 + master, 2, 4):m0->_2, m1->_3, m2/m3->_4
|
||||
_ck(sfx.skill_effect_name(1, 0) == "samyeon_2", "skill 1 m0 -> samyeon_2")
|
||||
_ck(sfx.skill_effect_name(1, 1) == "samyeon_3", "skill 1 m1(M) -> samyeon_3")
|
||||
_ck(sfx.skill_effect_name(1, 2) == "samyeon_4", "skill 1 m2(G) -> samyeon_4")
|
||||
_ck(sfx.skill_effect_name(1, 9) == "samyeon_4", "master 夹到 4")
|
||||
# 主特效存在 + 一个子特效存在 -> 播 2 个
|
||||
fxr.exists = {"samyeon_4": true, "samyeon_blow_4": true, "samyeon_hand_4": false}
|
||||
var n: int = sfx.spawn_skill(1, 3, host)
|
||||
_ck(n == 2 and fxr.spawned.has("samyeon_4") and fxr.spawned.has("samyeon_blow_4"),
|
||||
"spawn_skill 播主 + 存在的子特效 (%d: %s)" % [n, fxr.spawned])
|
||||
# motion 查不到 -> 空
|
||||
_ck(sfx.skill_effect_name(99999, 0) == "", "未知 skill -> 空名")
|
||||
else:
|
||||
print(" (skip real skilldesc checks: 无资产)")
|
||||
# 桩:手塞一条
|
||||
st._by_id[7] = {"id": 7, "motion": "testmo", "job": "WARRIOR", "name": "T"}
|
||||
_ck(sfx.skill_effect_name(7, 1) == "testmo_2", "桩 skill -> testmo_2")
|
||||
|
||||
# 内建特效:SE_AUTO_HPUP(19) -> drugup_red
|
||||
fxr.spawned.clear()
|
||||
fxr.exists = {"drugup_red": true, "drugup_blue": true}
|
||||
_ck(sfx.spawn_special(19, host) and fxr.spawned == ["drugup_red"],
|
||||
"special 19 (SE_AUTO_HPUP) -> drugup_red")
|
||||
_ck(sfx.spawn_special(20, host) and fxr.spawned[-1] == "drugup_blue",
|
||||
"special 20 (SE_AUTO_SPUP) -> drugup_blue")
|
||||
# 没映射的 id / 文件不存在 -> false
|
||||
_ck(sfx.spawn_special(255, host) == false, "未映射 special id -> false")
|
||||
fxr.exists = {}
|
||||
_ck(sfx.spawn_special(19, host) == false, "文件不存在 -> false")
|
||||
Reference in New Issue
Block a user