Fix some bug

This commit is contained in:
Tran Hai Nam
2026-05-19 15:25:59 +07:00
parent 9baa8207b0
commit 6fb8e22355
16 changed files with 434 additions and 95 deletions
@@ -15,7 +15,7 @@ MonoBehaviour:
m_DefaultGroup: 712e3991f28e549e7a56ee582a977810
m_currentHash:
serializedVersion: 2
Hash: 584471775533227dbfd66c0814b46e32
Hash: 00000000000000000000000000000000
m_OptimizeCatalogSize: 0
m_BuildRemoteCatalog: 0
m_CatalogRequestsTimeout: 0
@@ -4869,7 +4869,7 @@ Transform:
m_GameObject: {fileID: 4197944621747409826}
serializedVersion: 2
m_LocalRotation: {x: -0.6878776, y: -0.16378161, z: 0.16378164, w: 0.6878776}
m_LocalPosition: {x: 0.024, y: 0.554, z: -0.33}
m_LocalPosition: {x: 0.26, y: 1.51, z: -0.52}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
@@ -5595,7 +5595,7 @@ ParticleSystem:
sphericalDirectionAmount: 0
randomPositionAmount: 0
radius:
value: 0.71
value: 2.13
mode: 0
spread: 0
speed:
@@ -19445,8 +19445,8 @@ Transform:
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_LocalScale: {x: 3, y: 3, z: 3}
m_ConstrainProportionsScale: 1
m_Children: []
m_Father: {fileID: 1604809390761698253}
m_LocalEulerAnglesHint: {x: -90, y: 0, z: 0}
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4274349585449340130}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalRotation: {x: 0, y: 0.7071068, z: 0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 0, y: 90, z: 0}
--- !u!33 &7795735887883509743
MeshFilter:
m_ObjectHideFlags: 0
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4135001540345507112}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068}
m_LocalPosition: {x: 0, y: -0.1, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &5782369747155273987
MeshFilter:
m_ObjectHideFlags: 0
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2368932797394218666}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalRotation: {x: 0.35355344, y: 0.6123724, z: 0.6123724, w: 0.35355344}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: -30, y: 90, z: 90}
--- !u!33 &6354786712980373978
MeshFilter:
m_ObjectHideFlags: 0
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 486393464438265562}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalRotation: {x: 0, y: 1, z: 0, w: 0}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 0, y: 180, z: 0}
--- !u!33 &2795371212256588815
MeshFilter:
m_ObjectHideFlags: 0
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5042710232424246754}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalRotation: {x: 0, y: 1, z: 0, w: 0}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 0, y: 180, z: 0}
--- !u!33 &7463675474240568897
MeshFilter:
m_ObjectHideFlags: 0
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 192856137541653222}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalRotation: {x: 0, y: 0.7071068, z: 0.7071068, w: 0}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: -90, y: 180, z: 0}
--- !u!33 &7385562017347778284
MeshFilter:
m_ObjectHideFlags: 0
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5834653072341985716}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalRotation: {x: 0.3535534, y: 0.61237246, z: -0.3535534, w: 0.61237246}
m_LocalPosition: {x: 0, y: -0.2, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 60, y: 90, z: 0}
--- !u!33 &5428329696048806453
MeshFilter:
m_ObjectHideFlags: 0
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6619641526290080649}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalRotation: {x: 0, y: 0.7071068, z: -0.7071068, w: 0}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 90, y: 180, z: 0}
--- !u!33 &7228491550620403586
MeshFilter:
m_ObjectHideFlags: 0
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1020594218048739884}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalRotation: {x: 0, y: 0.7071068, z: 0, w: 0.7071068}
m_LocalPosition: {x: 0.73, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 0, y: 90, z: 0}
--- !u!33 &3048989088492932353
MeshFilter:
m_ObjectHideFlags: 0
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using BrewMonster;
using BrewMonster.Network;
using BrewMonster.Scripts;
using BrewMonster.Scripts.Skills;
using UnityEngine;
namespace PerfectWorld.Scripts
@@ -18,7 +20,7 @@ namespace PerfectWorld.Scripts
/// <summary>
/// Test scene only: bootstraps <see cref="CECHostPlayer"/> model load. After each successful <see cref="CECPlayer.SetPlayerModel"/>,
/// optionally applies serialized weapon prefabs via <see cref="CECPlayer.AnimSceneAttachWeaponPrefabs"/> (sets <c>m_uAttackType</c> like equip data).
/// applies serialized weapon prefabs via <see cref="CECPlayer.AnimSceneAttachWeaponPrefabs"/> (sets <c>m_uAttackType</c> like equip data).
/// After each load, destroys the previous major model instance so it does not linger in the hierarchy.
/// Optional: discard the whole host instance and instantiate a fresh prefab when switching role (see <see cref="replaceHostOnRoleSwitch"/>).
/// </summary>
@@ -28,6 +30,14 @@ namespace PerfectWorld.Scripts
[Tooltip("Scene host player. Do not put this bootstrap component on the same GameObject as the host if you enable Replace host on role switch.")]
[SerializeField]
private CECHostPlayer player;
[SerializeField]
private Camera cameraObject;
[SerializeField]
private List<Transform> cameraPoses;
[SerializeField]
[Range(0, 10)]
private int activeCameraPos;
[Header("Initial Role")]
[SerializeField]
@@ -43,12 +53,9 @@ namespace PerfectWorld.Scripts
[Tooltip("Which weaponByActionType row to apply after load / when using Re-apply in the custom inspector.")]
[SerializeField]
[Range(0, 14)]
[Range(0, 10)]
private int activeWeaponActionTypeIndex;
[SerializeField]
private bool applyWeaponAfterModelLoad = true;
[Header("Animation scene — element data compat")]
[Tooltip(
"If true, sets m_iFashionWeaponType = -1 on the host so PlayAction / GetShowingWeaponType do not call IsFashionWeaponTypeFit with missing DT_FASHION_WEAPON_CONFIG (avoids null action_mask NRE). Disable only if you load that config row.")]
@@ -62,6 +69,15 @@ namespace PerfectWorld.Scripts
[SerializeField]
private bool bindPlayerVisualAfterModelLoad = true;
[Header("Animation scene — skill catalog")]
[Tooltip("Match CDlgSkillSubList god/evil path when building the debug skill grid.")]
[SerializeField]
private bool isEvilSkillPath;
[Tooltip("Default level for skills injected from CECHostSkillModel in anim scenes.")]
[SerializeField]
private int skillCatalogLevel = 1;
[Header("Test-only — cleanup & host swap")]
[Tooltip("After SetPlayerModel, Destroy the previous major .ecm instance (frees scene objects left by re-load).")]
[SerializeField]
@@ -88,6 +104,26 @@ namespace PerfectWorld.Scripts
await BootstrapAsync((byte)profession, (byte)gender, fromInitialStart: true);
}
public void ChangeCameraPos(int i)
{
if(i<0)
activeCameraPos = cameraPoses.Count-1;
if (i>= cameraPoses.Count )
activeCameraPos = 0;
else
activeCameraPos = i;
cameraObject.transform.parent = cameraPoses[activeCameraPos];
cameraObject.transform.localRotation = default;
cameraObject.transform.localPosition = default;
}
public void ChangeNextCamera()
{
ChangeCameraPos (++activeCameraPos );
}
public void ChangePreviosCamera()
{
ChangeCameraPos (--activeCameraPos );
}
/// <summary>Swap profession/gender at runtime (async load inside).</summary>
public void SwitchRole(Profession newProfession, Gender newGender)
@@ -190,6 +226,28 @@ namespace PerfectWorld.Scripts
}
}
/// <summary>
/// Re-apply weapon, rebuild skill catalog for current role, and refresh SkillTriggerPanel.
/// 重新应用武器、重建技能目录并刷新技能面板。
/// </summary>
public void ResetSkillPanel(bool filterByRestrictions)
{
if (player == null)
{
Debug.LogWarning("[AnimSceneBootstrap] ResetSkillPanel: player is null.");
return;
}
ApplyWeaponForActiveSlot();
RefreshSkillTriggerPanel(filterByRestrictions);
}
/// <summary>Reload skill grid showing only skills usable with current weapon/form/env.</summary>
public void ResetSkillsWithRestrictions() => ResetSkillPanel(filterByRestrictions: true);
/// <summary>Reload skill grid with all catalog skills (no weapon/form/env filter).</summary>
public void ResetSkillsWithoutRestrictions() => ResetSkillPanel(filterByRestrictions: false);
private void ReplaceHostPlayerInstance()
{
if (hostPlayerPrefab == null)
@@ -229,6 +287,7 @@ namespace PerfectWorld.Scripts
_busy = true;
try
{
ChangeCameraPos(activeCameraPos);
Debug.Log("[AnimSceneBootstrap] AnimScenePlayerBootstrap — waiting for ElementDataManProvider...");
int waitedFrames = 0;
while (!ElementDataManProvider.IsDataLoaded)
@@ -277,15 +336,9 @@ namespace PerfectWorld.Scripts
await player.SetPlayerModel(prof, gen);
Debug.Log("[AnimSceneBootstrap] SetPlayerModel pipeline finished.");
if (player != null)
{
EC_Game.GetGameRun()?.RegisterAnimSceneHostPlayer(player);
}
ApplyWeaponForActiveSlot();
if (applyWeaponAfterModelLoad && player != null)
{
ApplyWeaponForActiveSlot();
}
AnimSceneInitSkillModelAndRefreshPanel(prof, gen);
AnimSceneBindPlayerVisualIfEnabled();
@@ -309,5 +362,44 @@ namespace PerfectWorld.Scripts
_busy = false;
}
}
/// <summary>
/// Sync profession/gender, rebuild CECHostSkillModel for the current role, and refresh SkillTriggerPanel.
/// SetPlayerModel loads visuals only — m_iProfession is not updated otherwise, and skill catalog init
/// is skipped in anim scenes (no LoadPlayerSkeleton → OnAllResourceReady).
///
/// 同步职业/性别,重建 CECHostSkillModel,并刷新 SkillTriggerPanel。
/// </summary>
private void AnimSceneInitSkillModelAndRefreshPanel(byte prof, byte gen)
{
if (player == null)
{
return;
}
player.m_iProfession = prof;
player.m_iGender = gen;
EC_Game.GetGameRun()?.RegisterAnimSceneHostPlayer(player);
SkillStubs.Init();
CECHostSkillModel.Instance.Initialize(prof);
Debug.Log($"[AnimSceneBootstrap] CECHostSkillModel initialized for profession={prof}, " +
$"catalogSkills={CECHostSkillModel.Instance.CollectSkillSubListSkillIds(isEvilSkillPath).Count}.");
RefreshSkillTriggerPanel(filterByRestrictions: false);
}
private void RefreshSkillTriggerPanel(bool filterByRestrictions)
{
if (SkillTriggerPanel.Instance == null)
{
return;
}
SkillTriggerPanel.Instance.SetHostPlayer(player);
SkillTriggerPanel.Instance.SetEvilSkillPath(isEvilSkillPath);
SkillTriggerPanel.Instance.ResetSkillsFromSkillModel(filterByRestrictions, skillCatalogLevel);
}
}
}
@@ -59,6 +59,11 @@ namespace BrewMonster
[Tooltip("When enabled, hides skills that fail weapon/form/move-env restrictions. Disable to show ALL positive skills regardless of restrictions.")]
[SerializeField] private bool filterByRestrictions = false;
[Tooltip("Match CDlgSkillSubList god/evil path: false = base+god ranks, true = base+evil ranks.")]
[SerializeField] private bool isEvilSkillPath;
[SerializeField] private int skillCatalogLevel = 1;
/// <summary>
/// World position of the draggable target marker, read by CECSkillGfxMan.get_pos_by_id.
/// 可拖动目标标记的世界坐标,由 CECSkillGfxMan.get_pos_by_id 读取。
@@ -101,6 +106,19 @@ namespace BrewMonster
gameRun.RegisterAnimSceneHostPlayer(player);
}
/// <summary>
/// Anim scene host swap: keep panel player reference in sync with bootstrap.
/// 动画场景切换角色时同步面板上的主角引用。
/// </summary>
public void SetHostPlayer(CECHostPlayer host)
{
if (host == null)
return;
player = host;
RegisterSceneHostWithGameRun();
}
/// <summary>Target id for <see cref="LocalCastSkill"/> / GFX composer (self = host cid, else marker NPC id).</summary>
private int ResolveLocalCastTargetId(CECSkill skill)
{
@@ -114,14 +132,11 @@ namespace BrewMonster
}
/// <summary>
/// Polls every 0.5 s until the host player has skills and CECGameUIMan is initialized,
/// then calls Refresh() once. If no server skill data arrives within <see cref="k_InjectFallbackSecs"/>
/// seconds, injects all config skills directly from SkillStub.map so the panel works offline.
/// Polls until CECGameUIMan is ready and the host has skills (server, skill model, or config fallback).
///
/// 每 0.5 秒轮询直到主角已有技能且 CECGameUIMan 初始化完毕,再调用一次 Refresh()
/// 若 k_InjectFallbackSecs 秒内未收到服务端技能数据,则直接从 SkillStub.map 注入所有配置技能,以便离线使用。
/// 轮询直到 UI 图集与技能数据就绪(服务端、CECHostSkillModel 或配置回退)
/// </summary>
private const float k_InjectFallbackSecs = 2f;
private const float k_RefreshWaitTimeoutSecs = 30f;
private IEnumerator RefreshWhenReady()
{
var wait = new WaitForSeconds(0.5f);
@@ -135,17 +150,23 @@ namespace BrewMonster
yield return wait;
}
// Wait for skills, but fall back to config injection after timeout.
// 等待技能数据,超时后回退到配置注入。
while (player != null && player.GetPositiveSkillNum() == 0)
ResolveHostPlayerReference();
// Wait for skills: server list, CECHostSkillModel catalog (anim scene), or config fallback.
// 等待技能:服务端列表、CECHostSkillModel 目录(动画场景)或配置回退。
while (player != null && !HasSkillCatalogOrPositiveSkills())
{
if (elapsed >= k_InjectFallbackSecs)
if (TryInjectFromSkillModel())
break;
if (elapsed >= k_RefreshWaitTimeoutSecs)
{
Debug.Log("[SkillTriggerPanel] No server skills after " +
$"{k_InjectFallbackSecs}s — injecting all config skills for offline debug.");
Debug.LogWarning("[SkillTriggerPanel] No skills after " +
$"{k_RefreshWaitTimeoutSecs}s — falling back to SkillStub.map injection.");
player.InjectDebugSkillsFromConfig();
break;
}
elapsed += 0.5f;
yield return wait;
}
@@ -153,6 +174,36 @@ namespace BrewMonster
Refresh();
}
private void ResolveHostPlayerReference()
{
if (player != null)
return;
player = EC_Game.GetGameRun()?.GetHostPlayer();
}
private bool HasSkillCatalogOrPositiveSkills()
{
if (player != null && player.GetPositiveSkillNum() > 0)
return true;
List<int> catalog = CECHostSkillModel.Instance?.CollectSkillSubListSkillIds(isEvilSkillPath);
return catalog != null && catalog.Count > 0;
}
private bool TryInjectFromSkillModel()
{
if (player == null)
return false;
List<int> catalog = CECHostSkillModel.Instance?.CollectSkillSubListSkillIds(isEvilSkillPath);
if (catalog == null || catalog.Count == 0)
return false;
player.InjectSkillsFromSkillModel(skillCatalogLevel, isEvilSkillPath);
return true;
}
/// <summary>
/// Loads every skill defined in SkillStub.map (config) directly into the player's skill list,
/// bypassing the server skill message. Use this when testing locally without a server.
@@ -171,6 +222,46 @@ namespace BrewMonster
Refresh();
}
/// <summary>
/// Rebuild host positive skills from <see cref="CECHostSkillModel"/> (same catalog as CDlgSkillSubList) and refresh the grid.
/// AnimScenePlayerBootstrap calls this after each model / role switch.
///
/// 从 CECHostSkillModel 重建主角正向技能并刷新网格(与 CDlgSkillSubList 相同目录)。
/// </summary>
[ContextMenu("Debug: Refresh From Skill Model")]
public void RefreshFromSkillModel(int level = -1)
{
ResolveHostPlayerReference();
if (player == null)
{
Debug.LogWarning("[SkillTriggerPanel] RefreshFromSkillModel: no host player.");
return;
}
if (level > 0)
skillCatalogLevel = level;
player.InjectSkillsFromSkillModel(skillCatalogLevel, isEvilSkillPath);
List<CECSkill> catalogSkills = player.BuildSkillSubListSkills(skillCatalogLevel, isEvilSkillPath);
PopulateSkillGrid(catalogSkills, catalogSkills.Count);
}
public void SetEvilSkillPath(bool isEvil)
{
isEvilSkillPath = isEvil;
}
/// <summary>
/// Reload from CECHostSkillModel and optionally hide skills that fail weapon/form/env checks.
/// 从技能目录重新加载;applyRestrictions 为 true 时按武器/形态/环境过滤。
/// </summary>
public void ResetSkillsFromSkillModel(bool applyRestrictions, int level = -1)
{
filterByRestrictions = applyRestrictions;
RefreshFromSkillModel(level);
}
/// <summary>
/// Clears and repopulates the skill grid from the host player's current positive skill list.
/// Call again after equipping a different weapon or changing shape/form.
@@ -197,12 +288,14 @@ namespace BrewMonster
return;
}
// Clear existing buttons / 清空已有按钮
foreach (Transform child in skillGridContainer)
Destroy(child.gameObject);
var catalogSkills = player.BuildSkillSubListSkills(skillCatalogLevel, isEvilSkillPath);
if (catalogSkills.Count > 0)
{
PopulateSkillGrid(catalogSkills, catalogSkills.Count);
return;
}
int total = player.GetPositiveSkillNum();
if (total == 0)
{
Debug.LogWarning("[SkillTriggerPanel] GetPositiveSkillNum() = 0. " +
@@ -211,6 +304,22 @@ namespace BrewMonster
return;
}
var positiveSkills = new List<CECSkill>(total);
for (int i = 0; i < total; i++)
{
CECSkill skill = player.GetPositiveSkillByIndex(i);
if (skill != null)
positiveSkills.Add(skill);
}
PopulateSkillGrid(positiveSkills, total);
}
private void PopulateSkillGrid(IReadOnlyList<CECSkill> skills, int totalListed)
{
foreach (Transform child in skillGridContainer)
Destroy(child.gameObject);
CECGameUIMan uiMan = CECUIManager.Instance?.GetInGameUIMan();
if (uiMan == null)
{
@@ -221,9 +330,9 @@ namespace BrewMonster
int added = 0;
int filtered = 0;
for (int i = 0; i < total; i++)
for (int i = 0; i < skills.Count; i++)
{
CECSkill skill = player.GetPositiveSkillByIndex(i);
CECSkill skill = skills[i];
if (skill == null)
continue;
@@ -235,21 +344,19 @@ namespace BrewMonster
GameObject cell = Instantiate(skillButtonPrefab, skillGridContainer);
// --- Icon ---
string iconName = skill.GetIconFile();
if (!string.IsNullOrEmpty(iconName))
{
Sprite icon = uiMan.GetIcon(iconName, EC_GAMEUI_ICONS.ICONS_SKILL);
Image cellImage = GetIconImage(cell);
Debug.Log("[SkillTriggerPanel] icon = " + (icon == null) + "cellImage = " + (cellImage == null));
if (cellImage != null && icon != null)
cellImage.sprite = icon;
}
else{
else
{
Debug.LogWarning("[SkillTriggerPanel] skill.GetIconFile() is empty for skill " + skill.GetSkillID());
}
// --- Label ---
Text cellText = cell.GetComponentInChildren<Text>();
if (cellText != null)
{
@@ -259,7 +366,6 @@ namespace BrewMonster
: $"{skillName}\nLv{skill.GetSkillLevel()}";
}
// --- Button ---
CECSkill captured = skill;
Button btn = cell.GetComponentInChildren<Button>();
if (btn != null)
@@ -269,7 +375,7 @@ namespace BrewMonster
}
Debug.Log($"[SkillTriggerPanel] Refreshed: {added} buttons added " +
$"({filtered} filtered by restrictions, {total} total positive skills).");
$"({filtered} filtered by restrictions, {totalListed} catalog/server skills).");
}
/// <summary>
@@ -294,7 +400,7 @@ namespace BrewMonster
if (weapon != null && weapon.CurEndurance > 0)
weaponId = (int)weapon.GetDBMajorType().id;
}
// Fake unlimited resources but keep real weapon / form / env values
// 伪造无限资源,但保留真实的武器/形态/环境值
UseRequirement fakeInfo = new UseRequirement
@@ -364,21 +470,21 @@ namespace BrewMonster
if (!animOk)
Debug.LogWarning($"[SkillTriggerPanel] PlaySkillCastAction returned false for skill {skillId} — animation may not play.");
// 2. Trigger VFX via the GFX composer — bypasses network entirely
// 通过特效组合器触发特效,完全绕过网络
var composerMan = CECAttacksMan.Instance?.GetSkillGfxComposerMan();
if (composerMan != null)
{
var targets = new List<TARGET_DATA>
{
new TARGET_DATA { idTarget = targetId, dwModifier = 0, nDamage = 0 }
};
composerMan.Play(skillId, player.GetCharacterID(), targetId, targets);
}
else
{
Debug.LogWarning($"[SkillTriggerPanel] composerMan is null — VFX skipped for skill {skillId}.");
}
// // 2. Trigger VFX via the GFX composer — bypasses network entirely
// // 通过特效组合器触发特效,完全绕过网络
// var composerMan = CECAttacksMan.Instance?.GetSkillGfxComposerMan();
// if (composerMan != null)
// {
// var targets = new List<TARGET_DATA>
// {
// new TARGET_DATA { idTarget = targetId, dwModifier = 0, nDamage = 0 }
// };
// composerMan.Play(skillId, player.GetCharacterID(), targetId, targets);
// }
// else
// {
// Debug.LogWarning($"[SkillTriggerPanel] composerMan is null — VFX skipped for skill {skillId}.");
// }
// 3. After 2s, play the release / 施放起+落 attack animation chain (local-only).
// 2 秒后播放施放攻击动画链(仅本地)。
@@ -397,20 +503,20 @@ namespace BrewMonster
// first try to find if there is already a skill attack event in attackman
CECAttackerEvents attackerEvents = CECAttacksMan.Instance.FindAttackByAttacker(hostPlayer.GetPlayerInfo().cid);
// if (attackerEvents)
// {
// pAttack = attackerEvents.Find(skillId, 0);
// if (pAttack != null)
// {
// // Ãæ¹¥»÷µÄ·ÇµÚÒ»´ÎÉ˺¦ÏûÏ¢
// pAttack.AddTarget(DEBUG_TARGET_ID, 1, 1);
// goto EXIT;
// }
// else
// {
// attackerEvents.Signal();
// }
// }
if (attackerEvents)
{
pAttack = attackerEvents.Find(skillId, 0);
if (pAttack != null)
{
// Ãæ¹¥»÷µÄ·ÇµÚÒ»´ÎÉ˺¦ÏûÏ¢
pAttack.AddTarget(DEBUG_TARGET_ID, 1, 1);
goto EXIT;
}
else
{
attackerEvents.Signal();
}
}
if (ElementSkill.IsGoblinSkill((uint)skillId) &&
ElementSkill.GetType((uint)skillId) == 2)
{
@@ -110,6 +110,27 @@ namespace PerfectWorld.Scripts
boot.ApplyWeaponForActiveSlot();
}
}
// EditorGUILayout.Space(8f);
// EditorGUILayout.LabelField("Play Mode — skill grid", EditorStyles.boldLabel);
// EditorGUILayout.HelpBox(
// "Reset Skills (With Restrictions) re-applies the active weapon slot, rebuilds the skill catalog, " +
// "and shows only skills that pass weapon / form / move-env checks.",
// MessageType.Info);
// using (new EditorGUI.DisabledScope(!Application.isPlaying))
// {
// EditorGUILayout.BeginHorizontal();
// if (GUILayout.Button("Reset Skills", GUILayout.Height(28f)))
// {
// ((AnimScenePlayerBootstrap)target).ResetSkillsWithoutRestrictions();
// }
// if (GUILayout.Button("Reset Skills (With Restrictions)", GUILayout.Height(28f)))
// {
// ((AnimScenePlayerBootstrap)target).ResetSkillsWithRestrictions();
// }
// EditorGUILayout.EndHorizontal();
// }
}
private static void TryPlayActionOnHost(
@@ -1,4 +1,5 @@
using Animancer;
using BrewMonster;
using CSNetwork;
using CSNetwork.GPDataType;
using ModelRenderer.Scripts.GameData;
@@ -50,6 +51,51 @@ namespace BrewMonster.Scripts.Skills
{
return m_allRankProfSkills;
}
/// <summary>
/// Same skill ID set as <see cref="BrewMonster.UI.CDlgSkillSubList"/> — walks base/god/evil ranks,
/// applies god/evil path filter and skips overridden skills.
/// 与 CDlgSkillSubList 相同的技能 ID 集合。
/// </summary>
public List<int> CollectSkillSubListSkillIds(bool isEvil)
{
var result = new List<int>();
var seen = new HashSet<int>();
void CollectRankRange(CECTaoistRank begin, CECTaoistRank end)
{
for (CECTaoistRank taoistRank = begin; taoistRank != end; taoistRank = taoistRank.GetNext())
{
if (isEvil && taoistRank.IsGodRank())
continue;
if (!isEvil && taoistRank.IsEvilRank())
continue;
int rankId = taoistRank.GetID();
if (!m_allRankProfSkills.TryGetValue(rankId, out List<int> rankSkills) ||
rankSkills == null || rankSkills.Count == 0)
{
continue;
}
foreach (int skillId in rankSkills)
{
if (ElementSkill.IsOverridden((uint)skillId))
continue;
if (seen.Add(skillId))
result.Add(skillId);
}
}
}
CollectRankRange(CECTaoistRank.GetBaseRankBegin(), CECTaoistRank.GetBaseRankEnd());
CollectRankRange(CECTaoistRank.GetGodRankBegin(), CECTaoistRank.GetGodRankEnd());
CollectRankRange(CECTaoistRank.GetEvilRankBegin(), CECTaoistRank.GetEvilRankEnd());
result.Sort();
return result;
}
public CECHostSkillModel()
{
m_skillLearnNPCNID = 0;
@@ -127,12 +173,18 @@ namespace BrewMonster.Scripts.Skills
}
}
public void Initialize()
{
Initialize(null);
}
/// <param name="professionOverride">When set (anim test scene), filters catalog by this profession instead of querying host.</param>
public void Initialize(int? professionOverride)
{
//BMLogger.LogError("HoangDev CECHostSkillModel Initialize called");
// Çå¿ÕËùÓм¼ÄÜ£¬·ÀÖ¹ÒòΪ¶à¸ö½ÇÉ«µÇ¼µ¼ÖÂÖØ¸´¼ÓÔØ¼¼ÄÜ
Release();
InitAllSkillsOfCurProf();
InitAllSkillsOfCurProf(professionOverride);
FindAllNPCsOfCurProf();
HashSet<int> rootSkills = GetRootSkillSet();
InitSkillTreeHeightMap(rootSkills);
@@ -489,6 +541,11 @@ namespace BrewMonster.Scripts.Skills
}
}
public void InitAllSkillsOfCurProf()
{
InitAllSkillsOfCurProf(null);
}
public void InitAllSkillsOfCurProf(int? professionOverride)
{
// --- B1: Thu thập toàn bộ skill từ các NPC có cung cấp dịch vụ học skill ---
HashSet<uint> npcSkills = new HashSet<uint>();
@@ -526,7 +583,8 @@ namespace BrewMonster.Scripts.Skills
{
ElementSkill pSkill = ElementSkill.Create(curID, 1);
int cls = pSkill.GetCls();
int playerCls = CECGameRun.Instance.GetHostPlayer().GetProfession();
int playerCls = professionOverride ??
CECGameRun.Instance.GetHostPlayer().GetProfession();
bool isSameClass = (cls == playerCls || cls == 255);
bool isProvidedByNPC = npcSkills.Contains(curID);
+62
View File
@@ -286,6 +286,68 @@ namespace BrewMonster
$"({m_aPtSkills.Count} active, {m_aPsSkills.Count} passive) at level {level}.");
}
/// <summary>
/// Animation test / offline: populate skill lists from <see cref="CECHostSkillModel"/> catalog
/// (same source and filters as CDlgSkillSubList).
/// Call <see cref="CECHostSkillModel.Initialize"/> before this so the catalog matches the current profession.
///
/// 动画测试 / 离线:从 CECHostSkillModel 目录填充技能列表(与 CDlgSkillSubList 相同来源与过滤)。
/// </summary>
public void InjectSkillsFromSkillModel(int level = 1, bool isEvil = false)
{
m_aPtSkills.Clear();
m_aPsSkills.Clear();
List<int> skillIds = CECHostSkillModel.Instance?.CollectSkillSubListSkillIds(isEvil);
if (skillIds == null || skillIds.Count == 0)
{
BMLogger.LogWarning(
"InjectSkillsFromSkillModel: CECHostSkillModel catalog is empty — call CECHostSkillModel.Initialize() first.");
return;
}
int injected = 0;
foreach (int skillId in skillIds)
{
CECSkill skill = new CECSkill(skillId, level);
int type = (int)ElementSkill.GetType((uint)skillId);
if (skill.SkillCore != null)
type = skill.GetType();
if (type != (int)CECSkill.SkillType.TYPE_PASSIVE &&
type != (int)CECSkill.SkillType.TYPE_PRODUCE &&
type != (int)CECSkill.SkillType.TYPE_LIVE)
m_aPtSkills.Add(skill);
else
m_aPsSkills.Add(skill);
injected++;
}
BMLogger.Log($"InjectSkillsFromSkillModel: profession={m_iProfession}, isEvil={isEvil}, " +
$"catalog={skillIds.Count}, injected={injected} " +
$"({m_aPtSkills.Count} active, {m_aPsSkills.Count} passive) at level {level}.");
}
/// <summary>
/// Build <see cref="CECSkill"/> instances for every ID returned by
/// <see cref="CECHostSkillModel.CollectSkillSubListSkillIds"/> — includes passive skills shown in the skill tree UI.
/// 构建与 CDlgSkillSubList 完全一致的技能列表(含被动)。
/// </summary>
public List<CECSkill> BuildSkillSubListSkills(int level = 1, bool isEvil = false)
{
var skills = new List<CECSkill>();
List<int> skillIds = CECHostSkillModel.Instance?.CollectSkillSubListSkillIds(isEvil);
if (skillIds == null)
return skills;
foreach (int skillId in skillIds)
skills.Add(new CECSkill(skillId, level));
return skills;
}
private void OnMsgHstLearnSkill(ECMSG Msg)
{
cmd_learn_skill pCmd = GPDataTypeHelper.FromBytes<cmd_learn_skill>((byte[])Msg.dwParam1);