diff --git a/Assets/PerfectWorld/Scene/AnimationTest.unity b/Assets/PerfectWorld/Scene/AnimationTest.unity new file mode 100644 index 0000000000..2d16aa6a66 --- /dev/null +++ b/Assets/PerfectWorld/Scene/AnimationTest.unity @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2daf9dffc5bed73510959aebf1ee1b4b2fe326380d6417e1a262c919b651095e +size 70182 diff --git a/Assets/PerfectWorld/Scene/AnimationTest.unity.meta b/Assets/PerfectWorld/Scene/AnimationTest.unity.meta new file mode 100644 index 0000000000..da95c5ebac --- /dev/null +++ b/Assets/PerfectWorld/Scene/AnimationTest.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a28f72b65352d8241827c49bc1f92408 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PerfectWorld/Scripts/AnimScenePlayerBootstrap.cs.meta b/Assets/PerfectWorld/Scripts/AnimScenePlayerBootstrap.cs.meta new file mode 100644 index 0000000000..6e7cab2aae --- /dev/null +++ b/Assets/PerfectWorld/Scripts/AnimScenePlayerBootstrap.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4f5be834edbd85540b34164ff311afbc \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Editor.meta b/Assets/PerfectWorld/Scripts/Editor.meta new file mode 100644 index 0000000000..ac3e76577b --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 66e02f889cedace409f7b1e3db23273a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PerfectWorld/Scripts/_Doc/AnimationSceneEditor.md b/Assets/PerfectWorld/Scripts/_Doc/AnimationSceneEditor.md new file mode 100644 index 0000000000..cf28618b6d --- /dev/null +++ b/Assets/PerfectWorld/Scripts/_Doc/AnimationSceneEditor.md @@ -0,0 +1,214 @@ +# Animation Scene — Editor Tooling + +This document tracks **Editor-only tooling** used with the runtime animation test workflow described in **`AnimationSceneSetup.md`**. + +**Scene docs (runtime hierarchy, managers, APIs):** see `Assets/PerfectWorld/Scripts/_Doc/AnimationSceneSetup.md`. + +**How to extend this file:** whenever you add or plan a new piece of Editor UI / automation for the animation scene, append a **`### Feature`** block under **[Feature log](#feature-log)** using the **[Feature template](#feature-template)** below. Keep one feature per numbered section so history stays searchable. + +--- + +## Goals + +| Goal | Notes | +|---|---| +| Faster iteration | Load and validate character models **from Play Mode**, driven by Editor menus where appropriate — **no Edit Mode `.ecm` preview** without a dedicated preview pipeline | +| Stable bootstrap | Respect the same prerequisites as runtime (`NPCManager`, `elementdataman`, `CECPlayer.InitStaticRes()`, etc.—see §Prerequisites below) | +| Traceability | Features and acceptance criteria live here **before or while** implementing the Editor scripts | + +Suggested implementation locations (when you create them): + +- Menu: e.g. `Tools / PerfectWorld / Animation Scene / …` +- Scripts: under `Assets/PerfectWorld/Scripts/Editor/` (or project convention for Editor assemblies) + +--- + +## Prerequisites (runtime + Editor) + +Anything the Editor tool drives must satisfy the same constraints as a Play Mode session: + +| Prerequisite | Why | +|---|---| +| `AutoInitializer` in the open scene | Bootstraps `IAutoInitialize` types including **`ElementDataManProvider`**; runs **`EC_Game.Init()`**, **`SkillStubs.Init()`** | +| **`elementdataman` loaded** | **`CECPlayer.InitStaticRes()`** → **`BuildActionList()`** uses **`ElementDataManProvider.GetElementDataMan()`** | +| **`CECPlayer.InitStaticRes()`** called once **after** element data is usable | **`InitializePlayer`** does **not** register **`IAutoInitialize`** (interface commented); **not** invoked by **`CECGameRun.Init()`** alone. Normal login hits it via **`CECGameRun.InitCharacter`**. Animation scene tooling must arrange this explicitly if you rely on skill / action maps | +| `NPCManager` in scene | **`SetPlayerModel`** → **`NPCManager.Instance.GetModelPlayer(profession, gender)`** | +| `CECAttacksMan` in scene | **`PlayAttackEffect`**, **`SkillGfxMan.InstanceSub.Tick`** path (see **`AnimationSceneSetup.md` §12**) | +| `EC_ManMessageMono` in scene | **`SkillGfxMan`** resolves **`EC_ManMessageMono.Instance`** references | +| `CECHostPlayer` reference (selection or serialized) | **`await player.SetPlayerModel(byte profession, byte gender)`** is **`CECPlayer`** API; host implementation is **`CECHostPlayer`** | +| **`InGameGraphicOption`** in scene *(optional)* | **`EC_Game.InitSetting()`** adjusts render scale / MSAA via **`InGameGraphicOption`**. If omitted, **`MonoSingleton`** auto-creates a host component; **`InGameGraphicOption`** resolves URP from **`GraphicsSettings.defaultRenderPipeline`**. Fully custom scenes should still assign URP (+ optional Cinemachine) on **`InGameGraphicOption`** for predictable behavior. + +**Editor caveat:** **`CECHostPlayer.LoadResources`** uses **`UnityGameSession.Instance.GetRoleInfo()`**. For offline Editor-driven loads, prefer calling **`SetPlayerModel`** directly (and/or setting **`m_iProfession`**, **`m_iGender`** on the player instance as your tool requires) rather than invoking full **`InitCharacter`** unless session is mocked. + +--- + +## Feature log + +### Feature 1 — Editor / Play Mode: bootstrap player model + +**Status:** Planned (spec captured here; implementation optional). + +**Problem:** In the animation test scene you need a repeatable way to load the `.ecm` player model **by profession and gender** without going through the server login flow. + +**Proposed behavior:** + +1. User opens the animation test scene (or any scene that contains the required managers + a **`CECHostPlayer`** instance). +2. **Play Mode required** for real **`SetPlayerModel`** / Addressables — Editor UI only **queues** work once Unity is playing (same constraint as Feature 2–3). +3. Tool or scene **`MonoBehaviour`** exposes **`Profession`** / **`Gender`** mapped to **`CECPlayer.SetPlayerModel(byte, byte)`**. +4. Sequence (recommended): wait until **`elementdataman`** load has succeeded → **`CECPlayer.InitStaticRes()`** once → **`await SetPlayerModel(profession, gender)`**. +5. Success: visible `.ecm` under **`Player`** per **`parentModel`** rules (**`CECPlayer.SetPlayerModel`** — first child if present, else root); Console filter **`AnimSceneBootstrap`** shows **`SetPlayerModel` BEGIN/END** when those logs exist. + +**Out of scope:** Full **`InitCharacter`** / **`UnityGameSession`** parity unless explicitly mocked elsewhere. + +**Acceptance criteria:** + +- Model appears after bootstrap when **`NPCManager`**, Addressables, and element data are healthy. +- Failures are visible (**`NPCManager`** null, **`GetModelPlayer`** null, **`InvalidOperationException`** from **`SetPlayerModel`**). +- **`InitStaticRes`** either runs before skill/action-dependent tests or is intentionally skipped with a clear warning. + +**Primary APIs:** **`CECPlayer.SetPlayerModel`**, **`CECPlayer.InitStaticRes`** — see **`AnimationSceneSetup.md`** §1, §10–11. + +--- + +### Feature 2 — Editor: trigger Play Action (animation probe) + +**Status:** Planned. + +**Problem:** Quickly verify **`CECPlayerActionController` / Animancer** wiring without hunting hotkeys or server-driven triggers. + +**Proposed behavior:** + +1. Menu entry e.g. **`Tools / PerfectWorld / Animation Scene / …`** opens a small **EditorWindow** (or equivalent). +2. Window active **only in Play Mode**; references **`CECHostPlayer`** (object field or auto-find). +3. User enters **`PLAYER_ACTION_TYPE`** index (same **`int`** as **`CECPlayer.PlayAction(int iAction, bool bRestart)`** — **`ACT_STAND = 0`**, etc.; see **`CECPlayer`** enum). +4. Button **Play action** invokes **`host.PlayAction(index, true)`** on the live instance. + +**Out of scope:** Driving **`PlaySkillCastAction`** / **`PlayAttackEffect`** from this panel (those belong to combat/skill tooling or separate Feature entries). + +**Acceptance criteria:** + +- With model loaded and action maps built (**`InitStaticRes`**), **`ACT_STAND`** / **`ACT_RUN`**-class actions behave like runtime calls. +- **`ACT_ATTACK_1..4`** may still refuse **`PlayAction`** by design (**`CECPlayer.PlayActionWithConfig`** blocks normal melee slots — use documented skill APIs instead). + +**Primary API:** **`CECPlayer.PlayAction(int iAction, bool bRestart)`** on **`CECHostPlayer`**. + +--- + +### Feature 3 — Offline host init: god stats + synthetic skills (below character level 80, human form) + +**Status:** Planned. + +**Problem:** **`OnMsgHstSkillData`** never runs offline, so **`m_aPtSkills`** / **`SkillWrapper`** stay empty — **`GetNormalSkill`** / cast checks cannot mirror a real character. You still want **most** combat skills usable for VFX/animation testing **without** shape-change prerequisites. + +**Design summary:** After **`InitStaticRes`** and **`SetPlayerModel`**, synthesize a **`cmd_skill_data`-equivalent** payload in memory and apply the **same conceptual pipeline** as **`CECHostPlayer.OnMsgHstSkillData`**: **`ElementSkill.LoadSkillData`** → **`SkillWrapper.Instance.LoadData`** → instantiate **`CECSkill`** rows into **`m_aPtSkills`** / **`m_aPsSkills`** by skill **type** (positive vs passive/production/life). Optionally align **`m_iProfession` / `m_iGender`** with the loaded model before filtering. + +**Simulated character limits:** + +| Constraint | Intent | +|---|---| +| Player level ceiling **below 80** | Treat **maximum simulated level as 79** (or equivalent) when choosing skill **rank**: for each skill id, pick the **highest** rank whose **`SkillStub.GetRequiredLevel(skill)`** is **≤** that simulated level (exact comparison uses live **`Skill`** instance at each candidate rank). | +| Realm | Set **`m_RealmLevel`** (and any extend props you rely on) high enough so **`GetRequiredRealmLevel`** does not fail for included ranks. | +| SP / Mana / AP / Vigour / HP | Raise **`ROLEBASICPROP`** (**`iCurMP`**, **`iCurAP`**, **`iSP`**, **`iVigour`**, **`iCurHP`**, etc.) and matching **`ROLEEXTPROP`** caps (**`max_hp`**, **`max_mp`**, **`max_ap`**) so **`CheckSkillCastCondition`** resource checks succeed for typical casts. | +| **Exclude transform-shape-only skills** | **`ElementSkill.Condition`** tests **`(allow_forms & (1 << form_type))`** where **`form_type`** comes from **`m_iShape`** high bits (**`FORM_MASK_HIGH`**). Base human **`m_iShape = 0`** ⇒ **`form_type = 0`** ⇒ skill must allow **bit 0** in **`SkillStub.allow_forms`**. **Exclude** stubs where **`(allow_forms & 1) == 0`** (human/base form disallowed). Also **exclude** stubs flagged **`restrict_change`** when they denote transformation-only restrictions in data. | +| **Exclude combo-chain prerequisites** | Skip stubs with **`combosk_preskill != 0`** unless you also simulate combo state (**`CECComboSkillState`**). | +| **Exclude item / ammo gated skills** | Skip **`itemcost > 0`** or **`arrowcost > 0`** unless you populate inventory accordingly. | +| **Exclude goblin line** | Skip **`cls == 258`**. | +| **Profession filter** | Include **`stub.cls == playerProfession`** **or** **`cls == 255`** (general skills). | + +**Caveats (document in tooltips):** + +- **`restrict_weapons`**: skills may still return condition **invalid weapon** if no matching weapon sits in **`m_pEquipPack`** — either equip a dummy weapon per profession or accept skipped casts. +- **`cmd_skill_data.SKILL.id_skill`** is a **`short`** in **`GPDataType`**: skill ids **> 32767** cannot be represented; skip or warn. +- Passive / prerequisite graphs are imperfectly simulated — goal is **animation/VFX iteration**, not authoritative balance. + +**Acceptance criteria:** + +- **`GetPositiveSkillByID`** / **`GetNormalSkill`** resolve for a large subset of profession skills after injection. +- **`CheckSkillCastCondition`** returns **0** for a sampled skill **without** shape-change, assuming weapon/item caveats addressed. +- Transform-only skills (**`allow_forms`** missing human bit or **`restrict_change`**) do **not** appear in the injected list. + +**Primary references:** **`CECHostPlayer.OnMsgHstSkillData`** (**`CECHostPlayer.Skill.cs`**), **`ElementSkill.Condition`**, **`SkillStub.allow_forms`**, **`ROLEBASICPROP`**, **`SkillWrapper`**. + +--- + +### Feature 4 — `AnimScenePlayerBootstrap` inspector: play action + weapon slots (`action_type` index) + +**Status:** Done. + +**Problem:** Drive **`PlayAction`** and test **weapon-mesh + `action_weapon_suffix`** alignment without a separate EditorWindow; **`GetShowingWeaponType`** ignores attached meshes when **`m_uAttackType == DEFAULT_ACTION_TYPE`**, so animation tests must set **`m_uAttackType`** the same way as **`CECPlayer_Inventory.ShowEquipments`** (from **`WEAPON_SUB_TYPE.action_type`**). + +**Implemented behavior:** + +1. **`AnimScenePlayerBootstrapEditor`** (Play Mode): **Play action** (`int` + **Restart** → **`CECHostPlayer.PlayAction`**), **Re-apply active weapon slot**. +2. **`AnimScenePlayerBootstrap`**: **`AnimSceneWeaponSlot[15]`** (`rightHandModelPrefab` / `leftHandModelPrefab` per row **`i === action_type`**), **`activeWeaponActionTypeIndex`**, **`applyWeaponAfterModelLoad`**; after **`SetPlayerModel`**, **`ApplyWeaponForActiveSlot()`** runs when enabled. +3. **`CECPlayer.AnimSceneAttachWeaponPrefabs`**: clears **`_currentRightHandWeapon` / `_currentLeftHandWeapon`**, parents instances under **`HH_righthandweapon` / `HH_lefthandweapon`** (same as **`CECPlayer_Inventory.ShowEquipments`**), sets **`m_uAttackType`** when any side attaches; empty slot → **`DEFAULT_ACTION_TYPE`** and **`AttachWeapon()`**. + +**Primary API / hooks:** **`CECPlayer.AnimSceneAttachWeaponPrefabs`**, **`AnimScenePlayerBootstrap.ApplyWeaponForActiveSlot`**, **`Assets/PerfectWorld/Scripts/Editor/AnimScenePlayerBootstrapEditor.cs`** + +--- + +### Feature 1 legacy API snippet (reference only) + +Use when implementing Feature 1 bootstrap: + +```csharp +await player.SetPlayerModel((byte)profession, (byte)gender); +CECPlayer.InitStaticRes(); +``` + +**Related runtime doc sections:** **`AnimationSceneSetup.md`** §1 (model loading), §9–11 (scene objects / init order). + +--- + +### Feature template (copy for Feature 4, 5, …) + +Paste and fill whenever you scope a **new** Editor capability: + +``` +### Feature N — + +**Status:** Planned | In progress | Done + +**Problem:** + +**Proposed behavior:** +1. +2. + +**Out of scope:** + +**Acceptance criteria:** +- +- <…> + +**Primary API / hooks:** `` + +**Related docs / code paths:** `` +``` + +--- + +## Maintaining this doc + +| When | Do | +|---|---| +| You start designing an Editor capability | Add a **`### Feature N`** section with **Status: Planned** and acceptance criteria | +| You implement or change behavior | Update **Status**, **Primary API**, and point to concrete script paths under `Assets/.../Editor/` | +| Behaviour diverges from runtime | Refresh **Prerequisites** and cross-check **`AnimationSceneSetup.md`** | + +--- + +## Play Mode — Bootstrap log tag `[AnimSceneBootstrap]` + +Scripts log a shared prefix **`[AnimSceneBootstrap]`** so you can filter the Unity Console while verifying initialization order: + +| Typical order | Location | +|---|---| +| Scene `Awake` boot | **`AutoInitializer`**: BEGIN → `IAutoInitialize` count → `EC_Game.Init` → `SkillStubs.Init` → END | +| Element data finished (async after above) | **`ElementDataManProvider`**: SUCCESS or FAILED after `load_data` | +| Attacks/GFX composers | **`CECAttacksMan`**: `Awake` → `Start` → `SetupAttacksMan` → optional GFX preload kick | +| Action/skill tables (when you call it) | **`CECPlayer.InitStaticRes`**: BEGIN / END | + +**`SetPlayerModel`** also logs BEGIN / END with the same prefix when loading the player `.ecm`. + +**Filter:** Console search **`AnimSceneBootstrap`**. diff --git a/Assets/PerfectWorld/Scripts/_Doc/AnimationSceneEditor.md.meta b/Assets/PerfectWorld/Scripts/_Doc/AnimationSceneEditor.md.meta new file mode 100644 index 0000000000..8119889983 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/_Doc/AnimationSceneEditor.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 84111cc94e962b54eb90af40352d0a2b +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PerfectWorld/Scripts/_Doc/AnimationSceneSetup.md b/Assets/PerfectWorld/Scripts/_Doc/AnimationSceneSetup.md new file mode 100644 index 0000000000..d3fce9bbf5 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/_Doc/AnimationSceneSetup.md @@ -0,0 +1,462 @@ +# Animation Test Scene Setup + +A standalone scene for testing character animations, skill VFX, weapon logic, and shape-change +without any server connection. All triggers are issued via local commands. + +--- + +## Related Source Files + +| File | Namespace / Class | Role | +|---|---|---| +| `Assets/PerfectWorld/Scripts/Move/CECPlayer.cs` | `BrewMonster.CECPlayer` | Abstract base — model load, action dispatch, weapon, shape, GFX state | +| `Assets/Scripts/CECHostPlayer.cs` | `BrewMonster.CECHostPlayer` | Concrete host player — message routing, skill prep, combat messages | +| `Assets/Scripts/CECHostPlayer.Skill.cs` | partial `CECHostPlayer` | Skill shortcut, `GetNormalSkill`, spam guard | +| `Assets/Scripts/CECHostPlayer.Combat.cs` | partial `CECHostPlayer` | `PlayAttackEffect` callers, melee/skill result handlers | +| `Assets/PerfectWorld/Scripts/NPC/CECModel.cs` | `BrewMonster.CECModel` | Wraps model `GameObject`; manages `SkeletonBuilder`, hooks, `NamedAnimancerComponent` | +| `Assets/PerfectWorld/Scripts/Players/CECPlayerActionController.cs` | `CECPlayerActionController` | Routes Play/Queue calls to the active `CECPlayerActionPlayPolicy` | +| `Assets/PerfectWorld/Scripts/Players/CECPlayerActionPlayPolicy.cs` | `CECPlayerActionPlayPolicy` | Default policy (no split-body); drives Animancer clips | +| `Assets/PerfectWorld/Scripts/Managers/NPCManager.cs` | `NPCManager` | Async model loader (`GetModelPlayer`, `GetDummyModel`) | +| `Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs` | `CECAttacksMan` | Singleton: attack events, skill GFX preload, state-action config | +| `Assets/PerfectWorld/Scripts/Managers/CECSkillGfxMan.cs` | `CECSkillGfxMan` / `CECSkillGfxEvent` | Skill GFX event state-machine (fly → hit → ground-hit) | +| `Assets/PerfectWorld/Scripts/Managers/A3DSkillGfxMan.cs` | `A3DSkillGfxMan` | Low-level GFX composer manager | +| `Assets/PerfectWorld/Scripts/Vfx/A3DSkillGfxComposerMan.cs` | `A3DSkillGfxComposerMan` | Per-skill GFX composer (fly/hit/ground-hit paths) | +| `Assets/PerfectWorld/Scripts/Sound/SFXManager.cs` | `SFXManager` | Sound pool, `sound.txt` table, `PlaySkillSfxAtPointAsync` | + +--- + +## 1. Model Loading + +### Entry Point +`CECPlayer.SetPlayerModel(byte profession, byte gender)` (async) + +### Flow +``` +SetPlayerModel(profession, gender) + └─ InitializePlayerCECModel(profession, gender) + ├─ NPCManager.Instance.GetModelPlayer(profession, gender) + │ └─ Loads .ecm prefab from Addressables / StreamingAssets + │ path: "models/players/形象//躯干/.ecm" + ├─ Finds SkeletonBuilder on model (retries next frame if not ready) + ├─ Finds CombineActHolder → reads CombinedActionSO + ├─ CECModel.SetSkeletonBuilder / SetNamedAnimancerComponent / SetTransform + ├─ CECModel.SetCombinedAction (action map) + └─ CECModel.InitializeSkeletonBuilder() ← hooks now available + └─ AttachWeapon() + └─ RecreateActionController() + └─ BuildActionList() ← maps skill IDs → PLAYER_ACTION_INFO_CONFIG + └─ PlayAction(ACT_STAND / ACT_FIGHTSTAND) +``` + +### Key Model Paths (NPCManager._playerModelPaths) +| Index | Path | +|---|---| +| 0 | `models/players/形象/武侠男/躯干/武侠男.ecm` | +| 1 | `models/players/形象/武侠女/躯干/武侠女.ecm` | +| 2 | `models/players/形象/法师男/躯干/法师男.ecm` | +| 3 | `models/players/形象/法师女/躯干/法师女.ecm` | +| 4–5 | 巫师男/女 | +| 6–9 | 妖族 (妖精, 妖兽男) | +| 10–11 | 刺客男/女 | +| 12–15 | 羽族 (羽芒, 羽灵) | +| 16–19 | 灵族 (剑灵, 魅灵) | +| 20+ | Profession transform models (白虎, 火狐狸, 影族变身…) | +| 24+ | Skill transform dummy models (金钱蛙, 婚礼童, 树鸡, 龙…) | + +--- + +## 2. Animation System + +### Components on Model Prefab +- `SkeletonBuilder` — builds skeleton and registers hook points +- `NamedAnimancerComponent` — Animancer component; clips referenced by name +- `CombineActHolder` → `CombinedActionSO` — maps action prefix+weapon suffix to clip names + +### CECPlayerActionController +Located: `Assets/PerfectWorld/Scripts/Players/CECPlayerActionController.cs` + +Channels: +- `ACT_CHANNEL_UPPERBODY = 0` +- `ACT_CHANNEL_LOWERBODY = 1` +- `ACT_CHANNEL_WOUND = 2` + +Key methods: +```csharp +// Immediate play +PlayNonSkillActionWithName(int iAction, string szActName, bool bRestart, int nTransTime, bool bNoFx, CECAttackEvent, uint dwFlagMode) +// Queue after current +QueueNonSkillActionWithName(int iAction, string szActName, int nTransTime, bool bForceStop, bool bNoFx, bool bResetSpeed, bool bResetActFlag, CECAttackEvent, uint dwNewFlagMode) +// Skill cast (charging / 吟唱) +PlaySkillCastActionWithName(int idSkill, string szActName, bool bNoFX) +// Skill attack (施放) +PlaySkillAttackActionWithName(int idSkill, string szActName, bool bNoFX, CECAttackEvent, uint dwFlagMode) +QueueSkillAttackActionWithName(int idSkill, string szActName, int nTransTime, bool bNoFX, ...) +// Stop +StopSkillCastAction() +StopSkillAttackAction() +StopChannelAction() +``` + +Flag mode constants (in `CECPlayer`): +- `COMACT_FLAG_MODE_ONCE_IGNOREGFX = 2` +- `COMACT_FLAG_MODE_ONCE_MULTIIGNOREGFX = 3` + +### PLAYER_ACTION_TYPE Enum (key values) +| Constant | Description | +|---|---| +| `ACT_STAND` | Idle stand | +| `ACT_FIGHTSTAND` | Combat idle (maps to ACT_STAND when no fight anim) | +| `ACT_RUN` / `ACT_WALK` | Movement | +| `ACT_JUMP_START` / `ACT_JUMP_LOOP` / `ACT_JUMP_LAND` | Jump phases | +| `ACT_ATTACK_1..4` | Normal attack sequence | +| `ACT_ATTACK_TOSS` | Throw / ranged attack | +| `ACT_TAKEOFF` / `ACT_LANDON` | Flight transitions | +| `ACT_WOUNDED` | Hit reaction | +| `ACT_GROUNDDIE` | Death on ground | +| `ACT_REVIVE` | Revive animation | +| `ACT_PICKUP` / `ACT_PICKUP_MATTER` | Pick-up gestures | +| `ACT_EXP_WAVE..ACT_EXP_DANCE` | Expression animations | +| `ACT_EXP_FASHIONWEAPON` | Fashion weapon expression | +| `ACT_USING_TARGET_ITEM` | Use item animation | + +### Animation Name Convention +For skill actions: +``` +{action_prefix}_{weapon_suffix}_施放起_ ← attack launch (ground) +{action_prefix}_{weapon_suffix}_施放落_ ← attack land (ground) +{action_prefix}_{weapon_suffix}_吟唱_ ← cast/charging + +// Air variants: +{action_prefix}_{weapon_suffix}_空中翅膀_施放起_ ← wing-type air attack +{action_prefix}_{weapon_suffix}_空中飞剑_施放起_ ← flysword-type air attack +{action_prefix}_{weapon_suffix}_空中翅膀_吟唱_ +{action_prefix}_{weapon_suffix}_空中飞剑_吟唱_ +``` + +Multi-section suffix appended: `szAct += "_" + suffix` (from `GetSkillSectionActionName`). + +--- + +## 3. Skill Trigger Logic (No Target Required) + +### Action Config Loading +`BuildActionList()` in `CECPlayer`: +- Reads `PLAYER_ACTION_INFO_CONFIG` from `elementdataman` +- Builds `_default_actions[]` array for standard action types +- Builds `_default_skill_actions` dictionary `(uint skillId → PLAYER_ACTION_INFO_CONFIG)` +- `PlayerSkillAction.NUM_WEAPON_TYPE` weapon suffix variants per skill + +### Trigger Chain (local, no server) +``` +[Local command: trigger skill idSkill] + 1. PlaySkillCastAction(idSkill) ← optional: shows charging/casting animation + └─ BuildActionName(data, weapon_type, "_吟唱_") + └─ PlaySkillCastActionWithName(idSkill, szAct, bHideFX) + └─ ShowWeaponByConfig(data) + + 2. PlaySkillAttackAction(idSkill, nAttackSpeed, ref piAttackTime) + ├─ BuildActionName(data, weapon_type, "_施放起_") + ├─ GetSkillSectionActionName (multi-section support) + ├─ GetComActTimeSpanByName → nTime1, nTime2 + ├─ SetPlaySpeed(vScale) if speed adjustment needed + ├─ PlaySkillAttackActionWithName(idSkill, szAct, bHideFX, attackEvent) + └─ QueueSkillAttackActionWithName(idSkill, szAct2, 0, bHideFX) + + 3. PlayAttackEffect(idTarget=0, idSkill, skillLevel, nDamage=0, dwModifier, nAttackSpeed, ref attackTime) + ├─ Creates CECAttackEvent via CECAttacksMan.AddSkillAttack(...) + ├─ Calls PlaySkillAttackAction(...) + └─ CECAttacksMan ticks the event each frame (GFX flight + hit) +``` + +### Key Method: `PlayAttackEffect` +```csharp +// CECPlayer.cs line ~1658 +public void PlayAttackEffect(int idTarget, int idSkill, int skillLevel, int nDamage, + uint dwModifier, int nAttackSpeed, ref int attackTime) +``` +- `idTarget = 0` → target-less skill (still creates attack event for GFX) +- `idSkill = 0` → normal melee attack; uses `AddMeleeAttack` +- When `idSkill > 0`: uses `AddSkillAttack`, triggers `PlaySkillAttackAction` + +### CECSkill Class (used by CECHostPlayer.Skill.cs) +- `GetSkillID()`, `GetSkillLevel()`, `GetCastRange()`, `GetType()` +- Types: `TYPE_PASSIVE`, `TYPE_PRODUCE`, `TYPE_LIVE`, `TYPE_GOBLIN`, active types +- Retrieved via: `GetNormalSkill(id)`, `GetPositiveSkillByID(id)`, `GetPassiveSkillByID(id)` + +--- + +## 4. Weapon Logic + +### Attach / Detach +```csharp +AttachWeapon() // checks left/right hook availability via CECModel.GetHook() +DetachWeapon() // sets m_bWeaponAttached = false +``` + +### Hook Position Strings +| Method | WEAPON_HANGER_HAND | WEAPON_HANGER_SHOULDER | +|---|---|---| +| `GetLeftWeaponHookPos` | `_hh_left_hand_weapon` | `_hh_left_shoulder_weapon` | +| `GetRightWeaponHookPos` | `_hh_right_hand_weapon` | `_hh_right_shoulder_weapon` | + +### Weapon Type Resolution +```csharp +int weapon_type = GetShowingWeaponType(); // considers fashion mode +int GetWeaponType(int iWeaponType) // maps raw type to canonical type (0-14) +int GetWeaponID() // equip pack weapon item ID (0 if shape-changed) +``` + +### Fashion Weapon +- `InFashionMode()` → `m_bFashionMode` +- `CanShowFashionWeapon(weapon_type, fashion_weapon_type)` → checks `FASHION_WEAPON_CONFIG.action_mask` +- `ShowWeaponByConfig(PLAYER_ACTION_INFO_CONFIG)` → shows/hides weapon per skill config + +### Weapon SFX Maps (`m_aWeaponSFX`, `m_aWeaponHitSFX`) +| Weapon Type | Attack SFX | Hit SFX | +|---|---|---| +| 0,1 | `item/weaponattack/1hshort[a/b/c]` | `item/weaponattack/hitsword[big]` | +| 2 | `item/weaponattack/2hlong[a/b/c/d]` | `item/weaponattack/hitmace[big]` | +| 3 | `item/weaponattack/1hshort[a/b/c]` | `item/weaponattack/hithammer[big]` | +| 4 | `item/weaponattack/2hlong[a/b/c/d]` | `item/weaponattack/hitaxe[big]` | +| 5 | `item/weaponattack/1hshort[a/b]` | `item/weaponattack/hithammer` | +| 6,7,9 | `item/weaponattack/bow[/b/drawbow]` | `item/weaponattack/hitthrow` | +| 8,10 | `item/weaponattack/fist[a/b/c/d]` | `item/weaponattack/hithand` | + +--- + +## 5. SFX System + +### SFXManager +Located: `Assets/PerfectWorld/Scripts/Sound/SFXManager.cs` + +- Singleton (`MonoSingleton`) +- Loads `Resources/sound.txt` (tab-separated: `id path`) into `_soundTable` +- Pool of `_sfxPoolSize` (default 8) `AudioSource` components +- Routes all skill SFX through `_sfxMixerGroup` + +Key method: +```csharp +SFXManager.Instance.PlaySkillSfxAtPointAsync(string soundPath, Vector3 position, float delay) +``` + +Usage in `PlayAttackAction`: +```csharp +string soundPath = m_aWeaponSFX[weapon_type][rand % count]; +string hitSoundPath = m_aWeaponHitSFX[weapon_type][rand % count]; +SFXManager.Instance.PlaySkillSfxAtPointAsync(soundPath, Vector3.zero, iTransTime / 1000f); +SFXManager.Instance.PlaySkillSfxAtPointAsync(hitSoundPath, Vector3.zero, iTransTime / 1000f + 0.1f); +``` + +Movement SFX: `_moveSoundSource` (2D looping AudioSource, assigned in Inspector). + +--- + +## 6. GFX / VFX System + +### State-Effect GFX (persistent buffs/debuffs) +Base path: `"gfx/策划联入/状态效果/"` + +```csharp +// Play GFX on player model +PlayGfx(string strGFXFile, string szHook, float fScale, uint iShapeTypeMask, bool persist) +// Remove GFX +RemoveGfx(string szPath, string szHook, uint iShapeTypeMask) +// Play GFX on weapon model +PlayStateGfxOnModel(CECModel pWeapon, string path, string hook, float fScale) +RemoveStateGfxFromModel(CECModel pWeapon, string path, string hook) +``` + +Active state GFX tracked in: `_stateGfxObjects` (Dictionary keyed by `path+hook`). + +Weapon hook resolution: +```csharp +IsWeaponHookPos(string szHH, out bool bLeft, out CECModel pWeapon) +GetWeaponGFXHookPos(CECModel pModel, bool bLeft) +``` + +### Skill GFX (projectiles / area effects) +Managed by `CECAttacksMan` + `A3DSkillGfxComposerMan`: + +``` +CECAttacksMan.LoadAllSkillGfxAsync() + └─ For each skill: ElementSkill.GetAllGFX(skillId) → (flyGFX, hitGrdGFX, hitGFX) + └─ A3DSkillGfxComposerMan.LoadOneComposerAsync(skillId, skillStub, flyPath, hitGrdPath, hitPath) + +// On-demand (when skill first used): +CECAttacksMan.LoadSkillGfxOnDemand(uint skillId) +``` + +GFX event lifecycle (per `CECSkillGfxEvent`): +1. Spawn fly GFX at caster position +2. Move toward target (or self for targetless) +3. On arrival: spawn hit GFX, optionally spawn ground-hit GFX +4. Mark `m_bFinished = true` → removed from `m_targets` linked list + +Ticked every frame: `SkillGfxMan.InstanceSub.Tick(dwDeltaTime)` in `CECAttacksMan.Update()`. + +--- + +## 7. Attack Event (CECAttackEvent) + +Created by `CECAttacksMan`: +```csharp +// Melee (idSkill == 0) +CECAttacksMan.Instance.AddMeleeAttack(idHost, idTarget, idWeapon, nDamage, dwModifier) + +// Skill +CECAttacksMan.Instance.AddSkillAttack(idHost, idSkillTarget, idTarget, idWeapon, + idSkill, skillLevel, dwModifier, nDamage) +``` + +Key fields on `CECAttackEvent`: +- `m_idHost` — attacker entity ID +- `m_bFinished` — set true when GFX resolved +- `m_bSignaled` — damage applied flag (see `SetApplyDamage`) +- `SetSkillSection(nSection)` — for multi-section skills + +For the animation test scene, pass `idTarget = 0` and `nDamage = 0` — the event will drive GFX travel with no actual damage. + +--- + +## 8. Change Shape / TransformShape + +### Entry Point +```csharp +await player.TransformShape(byte iShape, bool bLoadAtOnce = false) +``` + +### Shape ID Encoding (8-bit) +``` +| Bit 7–6 | Bit 5–0 | +| TYPE | Model ID | +``` +| TYPE value | PLAYERMODEL_TYPE | Meaning | +|---|---|---| +| `0x00` | PLAYERMODEL_TYPE_NONE | Invalid / legacy (auto-corrected to 0x40) | +| `0x40` | PLAYERMODEL_PROFESSION | Class-based transform (mapped via `_GetProfessionTransformModelID`) | +| `0x80` | PLAYERMODEL_DUMMY | Skill-transform / dummy model | + +### Flow +``` +TransformShape(iShape) + ├─ SetShape(iShape) ← decode type+ID, fix legacy format + ├─ IsShapeChanged()? + │ ├─ YES: QueueLoadDummyModel(m_iShape, bLoadAtOnce) + │ │ └─ NPCManager.Instance.GetDummyModel(iShapeID) + │ │ └─ ApplyShapeModelChange(pDummyModel) + │ └─ NO: ApplyShapeModelChange(GetMajorModel()) ← revert to base model + └─ ApplyShapeModelChange(pModel) + ├─ OnModelChange(pModel) → RefreshCECModel(pModel) + │ ├─ CECModel.SetSkeletonBuilder(...) + │ ├─ CECModel.SetNamedAnimancerComponent(...) + │ ├─ CECModel.SetTransform(...) + │ ├─ CECModel.SetCombinedAction(...) + │ └─ CECModel.InitializeSkeletonBuilder() + ├─ Sync position/rotation from old model + ├─ RecreateActionController() + └─ PlayAction(ACT_STAND) +``` + +### Profession → Transform Model Mapping +| Profession | Male | Female | +|---|---|---| +| PROF_HAG (妖族) | `RES_MOD_ORC_FOX` | `RES_MOD_ORC_FOX2` | +| PROF_ORC (妖兽) | `RES_MOD_ORC_TIGER` | `RES_MOD_ORC_PANDER` | +| PROF_MONK / PROF_GHOST | `RES_MOD_SHADOW_FISH_M` | `RES_MOD_SHADOW_FISH_F` | +| PROF_YEYING (夜影) | `RES_MOD_YEYING_RESHAPE_M` | `RES_MOD_YEYING_RESHAPE_F` | +| PROF_YUEXIAN (月仙) | `RES_MOD_YUEXIAN_RESHAPE_M` | `RES_MOD_YUEXIAN_RESHAPE_F` | + +Revert to original: `TransformShape(0)` → `IsShapeChanged()` returns false → `ApplyShapeModelChange(GetMajorModel())`. + +--- + +## 9. Required Scene GameObjects + +| GameObject | Component(s) | Notes | +|---|---|---| +| `NPCManager` | `NPCManager` | Async model loader; must be in scene | +| `CECAttacksMan` | `CECAttacksMan` | Manages attack events and skill GFX; assign `SkillStateActionConfig` SO | +| `SFXManager` | `SFXManager` | Assign `_moveSoundSource`, `_sfxMixerGroup` in Inspector | +| `EC_ManMessageMono` | `EC_ManMessageMono` | Provides `EC_ManPlayer`, `CECNPCMan` | +| `ElementDataManProvider` | `elementdataman` provider | Required for skill/action config lookups | +| Player Object | `CECHostPlayer` (or subclass) | `parentModel` Transform, `txtName` TMP text | +| SkillGfxMan | `A3DSkillGfxMan` | Low-level GFX manager (instantiated via `SkillGfxMan.InstanceSub`) | + +--- + +## 10. Local Command API (No Server) + +Replace server message handlers with these direct calls: + +```csharp +// --- Model --- +await player.SetPlayerModel(profession, gender); + +// --- Standard animations --- +player.PlayAction((int)PLAYER_ACTION_TYPE.ACT_STAND, true); +player.PlayAction((int)PLAYER_ACTION_TYPE.ACT_RUN, true); +player.PlayAction((int)PLAYER_ACTION_TYPE.ACT_ATTACK_1, false); + +// --- Skill cast (charging phase) --- +player.PlaySkillCastAction(idSkill); // 吟唱 animation + +// --- Skill attack (fire phase, no target) --- +int attackTime = 0; +player.PlaySkillAttackAction(idSkill, attackSpeed, ref attackTime); + +// --- Full effect chain (animation + GFX + SFX, no target) --- +int attackTime = 0; +player.PlayAttackEffect( + idTarget: 0, // 0 = no target + idSkill: idSkill, + skillLevel: 1, + nDamage: 0, + dwModifier: 0, + nAttackSpeed: 50, // 50 = default 1x speed + ref attackTime); + +// --- Change shape --- +await player.TransformShape(shapeID); // e.g. 0x40 | modelID +await player.TransformShape(0); // revert to base + +// --- Fashion mode toggle --- +player.m_bFashionMode = true; +player.m_bShowWeapon = true; +``` + +--- + +## 11. Initialization Order for Scene + +``` +1. Awake / Start: + - NPCManager initializes + - CECAttacksMan.SetupAttacksMan() → A3DSkillGfxComposerMan created + - SFXManager.Initialize() → loads sound.txt, builds AudioSource pool + +2. Player Init: + - player.Init(playerInfo) ← sets profession, gender, equips, shape + - await player.SetPlayerModel(profession, gender) + → NPCManager.GetModelPlayer() → model loaded + → CECModel setup (SkeletonBuilder, Animancer, hooks) + → AttachWeapon() + → RecreateActionController() + → BuildActionList() ← requires elementdataman populated + → PlayAction(ACT_STAND) + +3. Skill GFX preload (background, non-blocking): + - CECAttacksMan.LoadAllSkillGfxAsync() + +4. Ready — call local commands to trigger animations +``` + +--- + +## 12. Notes & Caveats + +- **`elementdataman`** must be loaded before `BuildActionList()`. Skills without a matching config entry in `_default_skill_actions` will silently skip the cast/attack animation. +- **Animancer** (`NamedAnimancerComponent`) must be present on the model prefab. The `SkeletonBuilder` may build asynchronously; if `InitializePlayerCECModelDelayed` coroutine fires, hooks are not ready until the next frame. +- **`CECAttacksMan` is required even for animation-only scenes** because `PlayAttackEffect` references it, and `SkillGfxMan.InstanceSub` is initialized via its `OnDestroy`. +- **Target-less GFX**: passing `idTarget = 0` to `AddSkillAttack` creates a valid event; the GFX composer will still instantiate fly/hit effects but travel to `Vector3.zero` unless a target position override is added. +- **Multi-section skills**: use `SetSkillSection(nSection)` on the `CECAttackEvent` before passing it to the animation method; `GetSkillSectionActionName` appends the section suffix automatically. +- **Shape model caching**: `m_pModels[iShapeType]` caches loaded dummy models per type slot. Re-calling `TransformShape` with the same shape reuses the cached model instantly. diff --git a/Assets/PerfectWorld/Scripts/_Doc/AnimationSceneSetup.md.meta b/Assets/PerfectWorld/Scripts/_Doc/AnimationSceneSetup.md.meta new file mode 100644 index 0000000000..9eb2714ef0 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/_Doc/AnimationSceneSetup.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9a7557790afc03640a0b778afe896ab0 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: