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
75 lines
3.0 KiB
GDScript
75 lines
3.0 KiB
GDScript
# SkillFx (P6 收尾) —— 技能 id / 内建特效 id → .mse 文件,接 P5 EffectRegistry。
|
||
#
|
||
# var sfx := preload("res://fx/skill_fx.gd").new()
|
||
# sfx.setup(effect_registry, skill_table)
|
||
# sfx.spawn_skill(1, 3, caster_node) # 技能 1(samyeon) grade→_4 挂 caster 播
|
||
# sfx.spawn_special(19, node) # GC_SPECIAL_EFFECT type 19 = SE_AUTO_HPUP -> drugup_red
|
||
#
|
||
# 技能特效表:skilldesc 的 motion_name(col13)就是特效基名;等级后缀 = 2 + master_type(夹 2..4)。
|
||
# effect/pc/<class>/effect/<motion>_<N>.mse(EffectRegistry 的 basename 扫描能找到散包 / PC / season1)
|
||
# 少数技能有 _blow / _head / _foot / _yong 子特效;这里只放主特效,缺就跳过。
|
||
extends RefCounted
|
||
|
||
# GC_SPECIAL_EFFECT 的 type(ItemData.h / Packet.h 的 SE_* enum,值 = 下标)-> etc 特效基名。
|
||
# 不全 —— 只填常见的(真机 GM 号狂刷 19/20 自动喝药)。
|
||
const SPECIAL_FX := {
|
||
1: "drugup_red", # SE_HPUP_RED
|
||
2: "drugup_blue", # SE_SPUP_BLUE
|
||
3: "drugup_green", # SE_SPEEDUP_GREEN
|
||
4: "drugup_purple", # SE_DXUP_PURPLE
|
||
9: "firecracker_1", # SE_CHINA_FIREWORK
|
||
11: "firecracker_2", # SE_SUCCESS(凑合)
|
||
12: "fail", # SE_FAIL
|
||
19: "drugup_red", # SE_AUTO_HPUP
|
||
20: "drugup_blue", # SE_AUTO_SPUP
|
||
}
|
||
|
||
var fxr: RefCounted # EffectRegistry
|
||
var table: RefCounted # SkillTable
|
||
# 一个 motion 常见的子特效后缀(有就一起播)
|
||
const SUB_SUFFIX := ["", "_blow", "_hand", "_head", "_foot", "_yong"]
|
||
|
||
func setup(effect_registry: RefCounted, skill_table: RefCounted) -> void:
|
||
fxr = effect_registry
|
||
table = skill_table
|
||
|
||
# skill_id + master_type(0..3) -> 主特效基名(<motion>_<N>)。查不到 motion 返回 ""。
|
||
func skill_effect_name(skill_id: int, master: int = 0) -> String:
|
||
if table == null:
|
||
return ""
|
||
var mn := String(table.entry(skill_id).get("motion", ""))
|
||
if mn == "":
|
||
return ""
|
||
var n := clampi(2 + master, 2, 4)
|
||
return "%s_%d" % [mn, n]
|
||
|
||
# 在 host 下播技能特效(主 + 已存在的子特效各一份)。返回播了几个。
|
||
func spawn_skill(skill_id: int, master: int, host: Node3D) -> int:
|
||
if fxr == null or host == null:
|
||
return 0
|
||
var base := skill_effect_name(skill_id, master)
|
||
if base == "":
|
||
return 0
|
||
var parts := base.rsplit("_", false, 1) # "samyeon_4" -> ["samyeon", "4"]
|
||
var stem: String = parts[0]
|
||
var num: String = parts[1] if parts.size() > 1 else ""
|
||
var played := 0
|
||
for suf in SUB_SUFFIX:
|
||
var name: String = base
|
||
if suf != "":
|
||
name = "%s%s_%s" % [stem, suf, num] if num != "" else stem + suf
|
||
if fxr.resolve(name) != "":
|
||
fxr.spawn(name, host, true)
|
||
played += 1
|
||
return played
|
||
|
||
# GC_SPECIAL_EFFECT type -> 内建特效。返回是否播了。
|
||
func spawn_special(special_id: int, host: Node3D) -> bool:
|
||
if fxr == null or host == null or not SPECIAL_FX.has(special_id):
|
||
return false
|
||
var name: String = SPECIAL_FX[special_id]
|
||
if fxr.resolve(name) == "":
|
||
return false
|
||
fxr.spawn(name, host, true)
|
||
return true
|