fix cast skill while flashing move
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:57bb52418b44eb7e3e333915973057be091b0826983b641ceb05263b4dffe2e4
|
||||
size 287382
|
||||
oid sha256:20cec4d7a902084d6e87ff1de8301f79123189bb3c8f06210396aab2d1b1d7ac
|
||||
size 291045
|
||||
|
||||
@@ -72,10 +72,11 @@ namespace BrewMonster.Scripts
|
||||
{
|
||||
_panelConsole.SetActive(false);
|
||||
}
|
||||
private void OnBtnNoCooldownClicked()
|
||||
public void OnBtnNoCooldownClicked()
|
||||
{
|
||||
UnityGameSession.c2s_CmdDebug(8903, 73125);
|
||||
}
|
||||
/// 8903 73125
|
||||
private void OnBtnAddWrathClicked()
|
||||
{
|
||||
UnityGameSession.c2s_CmdDebug(1992);
|
||||
@@ -89,10 +90,6 @@ namespace BrewMonster.Scripts
|
||||
ToggleAutoAddWrath();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggle auto-add wrath feature (calls OnBtnAddWrathClicked every 3 seconds)
|
||||
/// 切换自动添加怒气功能(每3秒调用OnBtnAddWrathClicked)
|
||||
/// </summary>
|
||||
public void ToggleAutoAddWrath()
|
||||
{
|
||||
_isAutoAddWrathEnabled = !_isAutoAddWrathEnabled;
|
||||
@@ -116,10 +113,6 @@ namespace BrewMonster.Scripts
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that calls OnBtnAddWrathClicked every 3 seconds
|
||||
/// 每3秒调用OnBtnAddWrathClicked的协程
|
||||
/// </summary>
|
||||
private IEnumerator AutoAddWrathCoroutine()
|
||||
{
|
||||
while (_isAutoAddWrathEnabled)
|
||||
|
||||
@@ -372,10 +372,21 @@ namespace BrewMonster
|
||||
if (m_enumState == GfxSkillEventState.enumFinished) return; // 结束 / Finished
|
||||
else if (m_enumState == GfxSkillEventState.enumHit) // 命中 / Hit
|
||||
{
|
||||
// In Unity, hit GFX is auto-destroyed via Destroy(obj, 3f) in CECSkillGfxEvent.
|
||||
// Transition to Finished immediately — the hit GFX cleanup is handled by Unity's timer.
|
||||
// 在Unity中,命中特效通过Destroy(obj, 3f)自动销毁。立即转为Finished状态。
|
||||
m_enumState = GfxSkillEventState.enumFinished;
|
||||
// If m_bTraceTarget is true, stay in Hit state to allow derived class to update hit GFX position each frame
|
||||
// This matches C++ logic: hit GFX follows target when m_bTraceTarget is true
|
||||
// 如果m_bTraceTarget为true,保持在Hit状态以允许派生类每帧更新命中特效位置
|
||||
// 这与C++逻辑匹配:当m_bTraceTarget为true时,命中特效跟随目标
|
||||
if (!m_bTraceTarget)
|
||||
{
|
||||
// In Unity, hit GFX is auto-destroyed via Destroy(obj, 3f) in CECSkillGfxEvent.
|
||||
// Transition to Finished immediately — the hit GFX cleanup is handled by Unity's timer.
|
||||
// 在Unity中,命中特效通过Destroy(obj, 3f)自动销毁。立即转为Finished状态。
|
||||
m_enumState = GfxSkillEventState.enumFinished;
|
||||
}
|
||||
// If m_bTraceTarget is true, derived class (CECSkillGfxEvent) will handle position updates
|
||||
// and transition to Finished when hit GFX lifetime expires
|
||||
// 如果m_bTraceTarget为true,派生类(CECSkillGfxEvent)将处理位置更新
|
||||
// 并在命中特效生命周期到期时转为Finished状态
|
||||
}
|
||||
else if (m_enumState == GfxSkillEventState.enumWait)
|
||||
{
|
||||
|
||||
@@ -1520,3 +1520,8 @@ public enum GfxSkillValType
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace BrewMonster
|
||||
pComposer.m_HitPos.vOffset,
|
||||
pComposer.m_HitPos.szHanger,
|
||||
pComposer.m_HitPos.bChildHook);
|
||||
|
||||
|
||||
if (!success)
|
||||
{
|
||||
// Return last known position or zero if target is destroyed
|
||||
@@ -123,7 +123,7 @@ namespace BrewMonster
|
||||
Vector3.zero,
|
||||
null,
|
||||
false);
|
||||
|
||||
|
||||
if (!success)
|
||||
{
|
||||
// Return last known position or zero if target is destroyed
|
||||
@@ -151,7 +151,7 @@ namespace BrewMonster
|
||||
{
|
||||
// Track state before base.Tick() to detect transitions / 在base.Tick()前记录状态以检测转换
|
||||
GfxSkillEventState prevState = m_enumState;
|
||||
|
||||
|
||||
// Update host and target positions / 更新施法者和目标位置
|
||||
if (GetComposer() != null)
|
||||
{
|
||||
@@ -234,17 +234,17 @@ namespace BrewMonster
|
||||
// Spawn fly GFX when entering Flying state / 进入飞行状态时生成飞行特效
|
||||
if (prevState == GfxSkillEventState.enumWait && m_enumState == GfxSkillEventState.enumFlying)
|
||||
{
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
Vector3 currentPos = m_pMoveMethod.GetPos();
|
||||
BMLogger.LogError($"[SKILL_GFX_DEBUG] Event.Tick: Registering gizmo - hostPos={m_vHostPos}, targetPos={m_vTargetPos}, currentPos={currentPos}, hostExist={m_bHostExist}, targetExist={m_bTargetExist}");
|
||||
|
||||
|
||||
if (m_vHostPos.sqrMagnitude > 0.01f && m_vTargetPos.sqrMagnitude > 0.01f)
|
||||
{
|
||||
SkillGfxGizmoDrawer.RegisterProjectile(m_nHostID, m_nTargetID, m_vHostPos, m_vTargetPos, m_pMoveMethod.GetMode());
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
SpawnFlyGfx();
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ namespace BrewMonster
|
||||
if (m_enumState == GfxSkillEventState.enumFlying)
|
||||
{
|
||||
UpdateFlyGfxTransform();
|
||||
|
||||
|
||||
// Update gizmo position / 更新辅助线位置
|
||||
#if UNITY_EDITOR
|
||||
Vector3 currentPos = m_pMoveMethod.GetPos();
|
||||
@@ -264,7 +264,22 @@ namespace BrewMonster
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// Update hit GFX position when m_bTraceTarget is true (follow target) / 当m_bTraceTarget为true时更新命中特效位置(跟随目标)
|
||||
// This matches C++ logic: hit GFX follows target position each frame when m_bTraceTarget is true
|
||||
// 这与C++逻辑匹配:当m_bTraceTarget为true时,命中特效每帧跟随目标位置
|
||||
if (m_enumState == GfxSkillEventState.enumHit && m_bTraceTarget)
|
||||
{
|
||||
UpdateHitGfxTransform();
|
||||
|
||||
// Check if hit GFX has been destroyed (3 second lifetime) or target is destroyed
|
||||
// 检查命中特效是否已被销毁(3秒生命周期)或目标是否已销毁
|
||||
if (m_hitGfxInstance == null || (!m_bTargetExist && m_nTargetID != 0))
|
||||
{
|
||||
m_enumState = GfxSkillEventState.enumFinished;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove gizmo when hit or finished / 命中或完成时移除辅助线
|
||||
if (m_enumState == GfxSkillEventState.enumHit || m_enumState == GfxSkillEventState.enumFinished)
|
||||
{
|
||||
@@ -297,13 +312,13 @@ namespace BrewMonster
|
||||
/// </summary>
|
||||
private void SpawnFlyGfx()
|
||||
{
|
||||
|
||||
|
||||
if (m_pComposer == null)
|
||||
{
|
||||
BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnFlyGfx: m_pComposer is NULL - cannot spawn fly GFX!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
GameObject prefab = m_pComposer.GetFlyGFX();
|
||||
BMLogger.LogError("FlyGfx : " + m_pComposer.flyGfxName);
|
||||
if (prefab == null)
|
||||
@@ -313,11 +328,11 @@ namespace BrewMonster
|
||||
}
|
||||
|
||||
Vector3 pos = m_pMoveMethod.GetPos();
|
||||
/* Vector3 dir = m_pMoveMethod.GetMoveDir();
|
||||
Quaternion rot = dir.sqrMagnitude > 1e-4f ? Quaternion.LookRotation(dir) : Quaternion.identity;*/
|
||||
/* Vector3 dir = m_pMoveMethod.GetMoveDir();
|
||||
Quaternion rot = dir.sqrMagnitude > 1e-4f ? Quaternion.LookRotation(dir) : Quaternion.identity;*/
|
||||
|
||||
m_flyGfxInstance = GameObject.Instantiate(prefab, pos, prefab.transform.rotation);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -333,6 +348,27 @@ namespace BrewMonster
|
||||
m_flyGfxInstance.transform.rotation = Quaternion.LookRotation(dir);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update hit GFX transform to follow target when m_bTraceTarget is true
|
||||
/// 当m_bTraceTarget为true时更新命中特效变换以跟随目标
|
||||
/// This matches C++ logic: hit GFX position is updated each frame to follow target
|
||||
/// 这与C++逻辑匹配:命中特效位置每帧更新以跟随目标
|
||||
/// </summary>
|
||||
private void UpdateHitGfxTransform()
|
||||
{
|
||||
if (m_hitGfxInstance == null) return;
|
||||
|
||||
// Update target position first / 首先更新目标位置
|
||||
if (m_bTargetExist && m_nTargetID != 0)
|
||||
{
|
||||
// Get current target position using GetTargetCenter() / 使用GetTargetCenter()获取当前目标位置
|
||||
Vector3 targetPos = GetTargetCenter();
|
||||
m_hitGfxInstance.transform.position = targetPos;
|
||||
}
|
||||
// If target doesn't exist, hit GFX stays at last known position
|
||||
// 如果目标不存在,命中特效保持在最后已知位置
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroy fly GFX instance
|
||||
/// 销毁飞行特效实例
|
||||
@@ -352,13 +388,13 @@ namespace BrewMonster
|
||||
/// </summary>
|
||||
private void SpawnHitGfx(Vector3 vTarget)
|
||||
{
|
||||
|
||||
|
||||
if (m_pComposer == null)
|
||||
{
|
||||
BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnHitGfx: m_pComposer is NULL - cannot spawn hit GFX!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Check if target exists - if not, use ground hit GFX instead of regular hit GFX
|
||||
// 检查目标是否存在 - 如果不存在,使用地面命中特效而不是常规命中特效
|
||||
// This matches C++ logic: m_szHitGrndGfx is used when projectile hits ground (no target)
|
||||
@@ -375,7 +411,7 @@ namespace BrewMonster
|
||||
{
|
||||
prefab = m_pComposer.GetHitGFX();
|
||||
}
|
||||
|
||||
|
||||
if (prefab == null)
|
||||
{
|
||||
BMLogger.LogWarning($"[SKILL_GFX_DEBUG] SpawnHitGfx: {(bTargetExists ? "Hit" : "Ground Hit")} GFX prefab is NULL - cannot spawn!");
|
||||
@@ -385,18 +421,19 @@ namespace BrewMonster
|
||||
|
||||
BMLogger.LogError($"{(bTargetExists ? "HitGfx" : "HitGrndGfx")} : {(bTargetExists ? m_pComposer.hitGfxName : m_pComposer.hitGrdGfxName)}");
|
||||
|
||||
/* Quaternion rot = Quaternion.identity;
|
||||
if (m_bHostExist)
|
||||
{
|
||||
Vector3 dir = vTarget - m_vHostPos;
|
||||
dir.y = 0;
|
||||
if (dir.sqrMagnitude > 1e-6f) rot = Quaternion.LookRotation(dir);
|
||||
}*/
|
||||
/* Quaternion rot = Quaternion.identity;
|
||||
if (m_bHostExist)
|
||||
{
|
||||
Vector3 dir = vTarget - m_vHostPos;
|
||||
dir.y = 0;
|
||||
if (dir.sqrMagnitude > 1e-6f) rot = Quaternion.LookRotation(dir);
|
||||
}*/
|
||||
|
||||
m_hitGfxInstance = GameObject.Instantiate(prefab, vTarget, prefab.transform.rotation);
|
||||
|
||||
|
||||
GameObject.Destroy(m_hitGfxInstance, 3.0f); // auto-cleanup / 自动清理
|
||||
|
||||
// Destroy hit GFX after 3 seconds (unless m_bTraceTarget is true, then it follows target until destroyed)
|
||||
// 3秒后销毁命中特效(除非m_bTraceTarget为true,否则它会跟随目标直到被销毁)
|
||||
GameObject.Destroy(m_hitGfxInstance, 3.0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -472,12 +509,12 @@ namespace BrewMonster
|
||||
// 注意:GetGoblin()方法可能需要在CECPlayer中实现
|
||||
// For now, we'll try to get it via a common pattern
|
||||
// 目前,我们将尝试通过通用模式获取它
|
||||
|
||||
|
||||
// Try to find goblin model - this is a placeholder until GetGoblin() is implemented
|
||||
// 尝试查找小精灵模型 - 这是占位符,直到实现GetGoblin()
|
||||
// TODO: Implement GetGoblin() in CECPlayer when goblin system is available
|
||||
// TODO: 当小精灵系统可用时,在CECPlayer中实现GetGoblin()
|
||||
|
||||
|
||||
// For Phase 3, we'll search for a child model named "goblin" or similar
|
||||
// 对于第3阶段,我们将搜索名为"goblin"或类似的子模型
|
||||
CECModel pModel = pPlayer.GetPlayerModel();
|
||||
@@ -485,10 +522,10 @@ namespace BrewMonster
|
||||
{
|
||||
// Try common goblin hanger names
|
||||
// 尝试常见的小精灵挂载者名称
|
||||
CECModel goblinModel = pModel.GetChildModel("goblin") ??
|
||||
pModel.GetChildModel("pet") ??
|
||||
CECModel goblinModel = pModel.GetChildModel("goblin") ??
|
||||
pModel.GetChildModel("pet") ??
|
||||
pModel.GetChildModel("_goblin");
|
||||
|
||||
|
||||
if (goblinModel != null)
|
||||
{
|
||||
// Use hook if specified, otherwise use model center
|
||||
@@ -500,21 +537,21 @@ namespace BrewMonster
|
||||
{
|
||||
Transform modelTransform = goblinModel.transform;
|
||||
vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform);
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
BMLogger.Log($"[HOOK_DEBUG] Found goblin hook '{szHook}' for player ID {nID}, position={vPos}");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Fallback to goblin position
|
||||
// 回退到小精灵位置
|
||||
if (goblinModel.transform != null)
|
||||
{
|
||||
vPos = goblinModel.transform.position;
|
||||
vPos.y += 0.5f;
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
BMLogger.Log($"[HOOK_DEBUG] Using goblin center position for player ID {nID}, position={vPos}");
|
||||
#endif
|
||||
@@ -522,7 +559,7 @@ namespace BrewMonster
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
BMLogger.LogWarning($"[HOOK_DEBUG] Goblin model not found for player ID {nID}");
|
||||
#endif
|
||||
|
||||
@@ -519,6 +519,14 @@ namespace BrewMonster.Scripts
|
||||
// LOG_DEBUG_INFO(AString().Format("CECHPWork:: start delayed work %s, priority=%d", pWork->GetWorkName(), m_Delayed.iPriority));
|
||||
InternallyStartWork(m_Delayed.iPriority, pWork);
|
||||
}
|
||||
public bool IsFlashMoving()
|
||||
{
|
||||
return IsWorkRunning(CECHPWork.Host_work_ID.WORK_FLASHMOVE);
|
||||
}
|
||||
public bool IsFlyingOff()
|
||||
{
|
||||
return IsWorkRunning(CECHPWork.Host_work_ID.WORK_FLYOFF);
|
||||
}
|
||||
public bool HasDelayedWork()
|
||||
{
|
||||
return GetDelayedWork() != null;
|
||||
@@ -888,7 +896,7 @@ namespace BrewMonster.Scripts
|
||||
|
||||
public bool IsOperatingPet()
|
||||
{
|
||||
return IsWorkRunning(Host_work_ID.WORK_CONCENTRATE);
|
||||
return IsWorkRunning(Host_work_ID.WORK_CONCENTRATE);
|
||||
}
|
||||
}
|
||||
public abstract class CECHPWorkPostTickCommand
|
||||
|
||||
@@ -390,7 +390,7 @@ namespace BrewMonster
|
||||
cdr.fGravityAccel = 9.8f; // EC_GRAVITY
|
||||
|
||||
EC_CDR.OnGroundMove(ref cdr);
|
||||
BMLogger.LogError($"HoangDev: FlashMove seg={i} stepTime={cdr.t} center=({cdr.vCenter})");
|
||||
//BMLogger.LogError($"HoangDev: FlashMove seg={i} stepTime={cdr.t} center=({cdr.vCenter})");
|
||||
|
||||
if (CECWorld.Instance.GetAssureMove() != null)
|
||||
CECWorld.Instance.GetAssureMove().NoAssureMove();
|
||||
@@ -416,6 +416,8 @@ namespace BrewMonster
|
||||
|
||||
m_vFlashTPNormal = cdr.vTPNormal;
|
||||
|
||||
A3DVECTOR3 vCurrentPlayerPos = m_pHost.GetPos();
|
||||
|
||||
return vCurPos;
|
||||
}
|
||||
// Get host's last position sent to server
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace BrewMonster
|
||||
protected CECSkill m_pCurSkill;
|
||||
protected int m_idFaction; // ID of player's faction
|
||||
protected int m_idForce; // id of the player's force
|
||||
protected BATTLEINFO m_BattleInfo; // Battle information / 战斗信息
|
||||
|
||||
protected int NUM_WEAPON_TYPE = 15;
|
||||
public static readonly int[] m_sciStateIDForStateAction = { 117 };
|
||||
@@ -83,17 +84,17 @@ namespace BrewMonster
|
||||
protected int m_idCountry = 0; // ¹úÕ½ÕóÓª id
|
||||
public static int MAX_REINCARNATION = 2;
|
||||
protected List<int> m_aCurEffects = new List<int>(); // Current effects
|
||||
byte m_ReincarnationCount = 0;
|
||||
string m_strName; // Player name
|
||||
protected byte m_ReincarnationCount = 0;
|
||||
protected string m_strName; // Player name
|
||||
// 需要是可能 || Need is possible
|
||||
protected bool m_bHangerOn = false;
|
||||
protected int m_iCurAction;
|
||||
bool m_bAboutToDie = false;
|
||||
protected bool m_bAboutToDie = false;
|
||||
public bool m_bCandHangerOn = false;
|
||||
public bool m_bPetInSanctuary = false; // true, the pet pet of the player is in sanctuary
|
||||
//The ID of the currently summoned pet
|
||||
int m_idCurPet = 0;
|
||||
byte m_byPariahLvl = 0; // Pariah level
|
||||
public bool m_bPetInSanctuary = false; // true, the pet pet of the player is in sanctuary
|
||||
//The ID of the currently summoned pet
|
||||
protected int m_idCurPet = 0;
|
||||
protected byte m_byPariahLvl = 0; // Pariah level
|
||||
|
||||
public RIDINGPET m_RidingPet; // Riding pet information
|
||||
public GameObject m_pPetModel = null; // Pet model
|
||||
@@ -102,8 +103,8 @@ namespace BrewMonster
|
||||
// ÒÀ¸½ÀàÐÍ
|
||||
AttachMode m_AttachMode = AttachMode.enumAttachNone;
|
||||
// ÒÀ¸½Õß»ò±»ÒÀ¸½Õßid
|
||||
int m_iBuddyId;
|
||||
int m_idCandBuddy; // ID of candidate buddy
|
||||
protected int m_iBuddyId;
|
||||
protected int m_idCandBuddy; // ID of candidate buddy
|
||||
EC_ManPlayer m_pPlayerMan => EC_ManMessageMono.Instance?.GetECManPlayer; // Player manager
|
||||
protected Transform playerTransform => _objectTransform ??= transform;
|
||||
|
||||
@@ -2324,6 +2325,178 @@ namespace BrewMonster
|
||||
public int iDuelTimeCnt; // Duel time counter
|
||||
public int iDuelRlt; // Duel result. 0, no defined; 1-win; 2-lose; 3-draw
|
||||
};
|
||||
|
||||
// Battle type / 战斗类型
|
||||
public enum BattleType
|
||||
{
|
||||
BT_NONE = 0, // No battle / 无战斗
|
||||
BT_GUILD = 1, // Guild war / 帮派战
|
||||
BT_COUNTRY = 2, // Country war / 国战
|
||||
BT_CHARIOT = 3, // Chariot war / 战车战
|
||||
};
|
||||
|
||||
// Score rank entry for country battle live show / 国战直播显示分数排行条目
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct ScoreRankEntry
|
||||
{
|
||||
public int idPlayer; // Player ID / 玩家ID
|
||||
public int iScore; // Score / 分数
|
||||
public int iKillCount; // Kill count / 击杀数
|
||||
public int iDeathCount; // Death count / 死亡数
|
||||
};
|
||||
|
||||
// Death entry for country battle live show / 国战直播显示死亡条目
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct DeathEntry
|
||||
{
|
||||
public int idKiller; // Killer ID / 击杀者ID
|
||||
public int idVictim; // Victim ID / 受害者ID
|
||||
public int iTime; // Death time / 死亡时间
|
||||
};
|
||||
|
||||
// Battle information / 战斗信息
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct BATTLEINFO
|
||||
{
|
||||
public int nType; // Battle type / 战斗类型 (BattleType)
|
||||
public int idBattle; // Battle id / 战斗ID
|
||||
public int iResult; // Battle result / 战斗结果 (0 = no result, 1 = win, 2 = lose, 3 = draw)
|
||||
public int iResultCnt; // Result time counter / 结果时间计数器
|
||||
public int iMaxScore_I; // Maximum score of invader / 攻击方最大分数
|
||||
public int iMaxScore_D; // Maximum score of defender / 防守方最大分数
|
||||
public int iScore_I; // Score of invader / 攻击方分数
|
||||
public int iScore_D; // Score of defender / 防守方分数
|
||||
public int iEndTime; // Battle end time / 战斗结束时间
|
||||
|
||||
// 国战专用数据 / Country war specific data
|
||||
public int iOffenseCountry; // Offense country / 攻击方国家
|
||||
public int iDefenceCountry; // Defence country / 防守方国家
|
||||
public int iReviveTimes; // 剩余复活次数 / Remaining revive times
|
||||
[MarshalAs(UnmanagedType.U1)]
|
||||
public bool bFlagCarrier; // 是否是旗手 / Is flag carrier
|
||||
public int iCarrierID; // 扛旗者ID(bFlagCarrier为false时有效)/ Carrier ID (valid when bFlagCarrier is false)
|
||||
public A3DVECTOR3 posCarrier; // 扛旗者位置(bFlagCarrier为false时有效)/ Carrier position (valid when bFlagCarrier is false)
|
||||
[MarshalAs(UnmanagedType.U1)]
|
||||
public bool bCarrierInvader; // 扛旗者是攻击方(bFlagCarrier为false时有效)/ Carrier is invader (valid when bFlagCarrier is false)
|
||||
|
||||
public int iCombatTime; // 战斗时间(秒)/ Combat time (seconds)
|
||||
public int iAttendTime; // 参加战场时间(秒)/ Attend time (seconds)
|
||||
public int iKillCount; // 击杀次数 / Kill count
|
||||
public int iDeathCount; // 死亡次数 / Death count
|
||||
public int iCountryKillCount; // 同国家击杀次数 / Same country kill count
|
||||
public int iCountryDeathCount;// 同国家死亡次数 / Same country death count
|
||||
public int iAttackerCount; // 攻击方人数 / Attacker count
|
||||
public int iDefenderCount; // 防守方人数 / Defender count
|
||||
|
||||
public int iStrongHoldCount; // 据点个数 / Stronghold count
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
public int[] iStrongHoldState; // 据点占领状态 8 == ARRAY_SIZE(COUNTRY_CONFIG::stronghold) / Stronghold occupation state
|
||||
|
||||
// Score rank containers (using List instead of std::vector) / 分数排行容器(使用List代替std::vector)
|
||||
public List<ScoreRankEntry> OffenseRanks; // Offense ranks / 攻击方排行
|
||||
public List<ScoreRankEntry> DefenceRanks; // Defence ranks / 防守方排行
|
||||
|
||||
// Death containers (using List instead of std::vector) / 死亡容器(使用List代替std::vector)
|
||||
public List<DeathEntry> OffenseDeaths; // Offense deaths / 攻击方死亡
|
||||
public List<DeathEntry> DefenceDeaths; // Defence deaths / 防守方死亡
|
||||
|
||||
// 战车 / Chariot
|
||||
public int iChariot; // 战车id / Chariot id
|
||||
public int iEnergy; // 能量 / Energy
|
||||
public int iScoreSelf; // 自己成绩 / Self score
|
||||
public int iMultiKill; // 连杀 / Multi kill
|
||||
|
||||
// Initialize arrays and lists / 初始化数组和列表
|
||||
public void Initialize()
|
||||
{
|
||||
if (iStrongHoldState == null)
|
||||
iStrongHoldState = new int[8];
|
||||
if (OffenseRanks == null)
|
||||
OffenseRanks = new List<ScoreRankEntry>();
|
||||
if (DefenceRanks == null)
|
||||
DefenceRanks = new List<ScoreRankEntry>();
|
||||
if (OffenseDeaths == null)
|
||||
OffenseDeaths = new List<DeathEntry>();
|
||||
if (DefenceDeaths == null)
|
||||
DefenceDeaths = new List<DeathEntry>();
|
||||
}
|
||||
|
||||
// Set country battle live show info / 设置国战直播显示信息
|
||||
// TODO: Implement when cmd_countrybattle_live_show_result is available
|
||||
// 当cmd_countrybattle_live_show_result可用时实现
|
||||
public void SetCountryBattleLiveShowInfo(/*const S2C::cmd_countrybattle_live_show_result& cmd*/)
|
||||
{
|
||||
// TODO: Implement SetCountryBattleLiveShowInfo
|
||||
// 实现SetCountryBattleLiveShowInfo
|
||||
}
|
||||
|
||||
// Check if in guild war / 检查是否在帮派战中
|
||||
public bool IsGuildWar()
|
||||
{
|
||||
return nType == (int)BattleType.BT_GUILD;
|
||||
}
|
||||
|
||||
// Check if in country war / 检查是否在国战中
|
||||
public bool IsCountryWar()
|
||||
{
|
||||
return nType == (int)BattleType.BT_COUNTRY;
|
||||
}
|
||||
|
||||
// Check if is flag carrier / 检查是否是旗手
|
||||
public bool IsFlagCarrier()
|
||||
{
|
||||
return IsCountryWar() && bFlagCarrier;
|
||||
}
|
||||
|
||||
// Check if in chariot war / 检查是否在战车战中
|
||||
public bool IsChariotWar()
|
||||
{
|
||||
return nType == (int)BattleType.BT_CHARIOT;
|
||||
}
|
||||
|
||||
// Reset battle info / 重置战斗信息
|
||||
public void Reset()
|
||||
{
|
||||
nType = (int)BattleType.BT_NONE;
|
||||
idBattle = 0;
|
||||
iResult = 0;
|
||||
iResultCnt = 0;
|
||||
iMaxScore_I = 0;
|
||||
iMaxScore_D = 0;
|
||||
iScore_I = 0;
|
||||
iScore_D = 0;
|
||||
iEndTime = 0;
|
||||
iOffenseCountry = 0;
|
||||
iDefenceCountry = 0;
|
||||
iReviveTimes = 0;
|
||||
bFlagCarrier = false;
|
||||
iCarrierID = 0;
|
||||
posCarrier = new A3DVECTOR3(0, 0, 0);
|
||||
bCarrierInvader = false;
|
||||
iCombatTime = 0;
|
||||
iAttendTime = 0;
|
||||
iKillCount = 0;
|
||||
iDeathCount = 0;
|
||||
iCountryKillCount = 0;
|
||||
iCountryDeathCount = 0;
|
||||
iAttackerCount = 0;
|
||||
iDefenderCount = 0;
|
||||
iStrongHoldCount = 0;
|
||||
if (iStrongHoldState != null)
|
||||
{
|
||||
for (int i = 0; i < iStrongHoldState.Length; i++)
|
||||
iStrongHoldState[i] = 0;
|
||||
}
|
||||
OffenseRanks?.Clear();
|
||||
DefenceRanks?.Clear();
|
||||
OffenseDeaths?.Clear();
|
||||
DefenceDeaths?.Clear();
|
||||
iChariot = 0;
|
||||
iEnergy = 0;
|
||||
iScoreSelf = 0;
|
||||
iMultiKill = 0;
|
||||
}
|
||||
};
|
||||
public enum PlayerResourcesReadyFlag
|
||||
|
||||
{
|
||||
|
||||
@@ -103,6 +103,8 @@ namespace CSNetwork.C2SCommand
|
||||
if (fieldType == typeof(Vector3))
|
||||
{
|
||||
var vec = (Vector3)fieldValue;
|
||||
// Log Vector3 serialization for flash move debugging
|
||||
// 记录Vector3序列化以用于瞬移调试
|
||||
WriteBasicValue(octets, vec.x);
|
||||
WriteBasicValue(octets, vec.y);
|
||||
WriteBasicValue(octets, vec.z);
|
||||
|
||||
@@ -643,6 +643,10 @@ namespace BrewMonster
|
||||
if (!pSkill.ReadyToCast())
|
||||
return false;
|
||||
|
||||
// Check if skill can be cast immediately (blocks casting during flash move at PRIORITY_2)
|
||||
if (!m_pWorkMan.CanCastSkillImmediately(pSkill.GetSkillID()))
|
||||
return false;
|
||||
|
||||
if (!pSkill.IsInstant() && pSkill.GetType() != (int)Skilltype.TYPE_FLASHMOVE)
|
||||
{
|
||||
if (!NaturallyStopMoving())
|
||||
|
||||
+201
-171
@@ -23,6 +23,7 @@ using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using static BrewMonster.CECPlayer;
|
||||
using cmd_select_target = CSNetwork.GPDataType.cmd_select_target;
|
||||
using Host_work_ID = BrewMonster.Scripts.CECHPWork.Host_work_ID;
|
||||
using ObjectCoords = System.Collections.Generic.List<CSNetwork.GPDataType.OBJECT_COORD>;
|
||||
@@ -104,6 +105,8 @@ namespace BrewMonster
|
||||
private int MAX_JUMP_COUNT = 2;
|
||||
bool m_bUsingTrashBox = false; // Whether being using trash box
|
||||
private float m_fPrayDistancePlus;
|
||||
private bool m_bInRebuildPet = false; // Whether rebuilding pet / 是否正在重建宠物
|
||||
private uint m_dwGMFlags = 0; // GM flags / GM标志
|
||||
private A3DVECTOR3 g_vOrigin = new A3DVECTOR3(0f);
|
||||
|
||||
private float EC_SLOPE_Y = 0.5f;
|
||||
@@ -1983,226 +1986,182 @@ namespace BrewMonster
|
||||
return pNPC && ( /*!IsSkeletonReady() ||*/ CanSafelySelectWith(pNPC.GetDistToHost()));
|
||||
}
|
||||
|
||||
// Check whether host can do a behavior
|
||||
bool CanDo(int iThing)
|
||||
// Check whether host can do a behavior / 检查宿主是否可以执行某个行为
|
||||
public bool CanDo(int iThing)
|
||||
{
|
||||
bool bRet = true;
|
||||
|
||||
switch (iThing)
|
||||
{
|
||||
case ActionCanDo.CANDO_SITDOWN:
|
||||
|
||||
if (IsDead() /*|| IsAboutToDie() */ || IsJumping() /*|| IsTrading() || IsUsingTrashBox()*/ ||
|
||||
IsRooting() || /*IsReviving() || IsTalkingWithNPC() || IsChangingFace() ||*/
|
||||
!m_GndInfo
|
||||
.bOnGround /*|| GetBoothState() != 0 || m_iBuddyId || IsOperatingPet() || IsRebuildingPet() ||
|
||||
IsUsingItem() || IsRidingOnPet() || GetShapeType() == PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove()*/
|
||||
)
|
||||
if (IsDead() || IsAboutToDie() || IsJumping() || IsTrading() || IsUsingTrashBox() ||
|
||||
IsRooting() || IsReviving() || IsTalkingWithNPC() || IsChangingFace() ||
|
||||
!m_GndInfo.bOnGround || GetBoothState() != 0 || m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() ||
|
||||
IsUsingItem() || IsRidingOnPet() || GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove())
|
||||
bRet = false;
|
||||
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_MOVETO:
|
||||
{
|
||||
if (IsDead() /*|| IsSitting() || IsTrading() || IsUsingTrashBox()*/ || IsRooting() /*||
|
||||
IsReviving() || IsTalkingWithNPC() || IsChangingFace() || IsUsingItem() ||
|
||||
GetBoothState() != 0 || m_bHangerOn || IsOperatingPet() || IsRebuildingPet() || IsPassiveMove()*/)
|
||||
bRet = false;
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionCanDo.CANDO_MELEE:
|
||||
|
||||
if (IsDead() /*|| IsSitting() */ || m_idSelTarget == 0 || m_idSelTarget == m_PlayerInfo.cid ||
|
||||
IsJumping() || GPDataTypeHelper.ISMATTERID(m_idSelTarget) /*|| IsTrading() || IsReviving() ||
|
||||
IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace()*/ || CannotAttack() /*||
|
||||
GetBoothState() != 0 || m_iBuddyId || IsRidingOnPet() || IsOperatingPet() || IsRebuildingPet() ||
|
||||
IsUsingItem() || IsPassiveMove()*/)
|
||||
if (IsDead() || IsSitting() || IsTrading() || IsUsingTrashBox() || IsRooting() ||
|
||||
IsReviving() || IsTalkingWithNPC() || IsChangingFace() || IsUsingItem() ||
|
||||
GetBoothState() != 0 || m_bHangerOn || IsOperatingPet() != 0 || IsRebuildingPet() || IsPassiveMove())
|
||||
bRet = false;
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_MELEE:
|
||||
if (IsDead() || IsSitting() || m_idSelTarget == 0 || m_idSelTarget == m_PlayerInfo.cid ||
|
||||
IsJumping() || GPDataTypeHelper.ISMATTERID(m_idSelTarget) || IsTrading() || IsReviving() ||
|
||||
IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || CannotAttack() ||
|
||||
GetBoothState() != 0 || m_iBuddyId != 0 || IsRidingOnPet() || IsOperatingPet() != 0 || IsRebuildingPet() ||
|
||||
IsUsingItem() || IsPassiveMove())
|
||||
bRet = false;
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_ASSISTSEL:
|
||||
|
||||
if (IsDead() || !GPDataTypeHelper.ISPLAYERID(m_idSelTarget) ||
|
||||
m_idSelTarget == m_PlayerInfo.cid /*||
|
||||
!m_pTeam || !m_pTeam.GetMemberByID(m_idSelTarget) || m_iBuddyId || IsPassiveMove()*/ ||
|
||||
if (IsDead() || !GPDataTypeHelper.ISPLAYERID(m_idSelTarget) || m_idSelTarget == m_PlayerInfo.cid ||
|
||||
m_pTeam == null || m_pTeam.GetMemberByID(m_idSelTarget) == null || m_iBuddyId != 0 || IsPassiveMove() ||
|
||||
m_playerLimits[(int)PLAYER_LIMIT.PLAYER_LIMIT_NOCHANGESELECT])
|
||||
bRet = false;
|
||||
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_FLY:
|
||||
|
||||
if (IsDead() || IsRooting() || IsSitting() || IsTrading() || IsReviving() ||
|
||||
IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || GetBoothState() != 0 ||
|
||||
//IsFlashMoving() ||
|
||||
m_pWorkMan.HasWorkRunningOnPriority(CECHPWorkMan.Work_priority.PRIORITY_2) ||
|
||||
m_bHangerOn || /*IsOperatingPet() || IsRebuildingPet() ||*/
|
||||
IsUsingItem() || /*IsRidingOnPet() || GetShapeType() == PLAYERMODEL_DUMMYTYPE2 ||*/ IsPassiveMove() ||
|
||||
m_playerLimits[(int)PLAYER_LIMIT.PLAYER_LIMIT_NOFLY]/* || m_BattleInfo.IsChariotWar()*/)
|
||||
IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || GetBoothState() != 0 ||
|
||||
IsFlashMoving() || (m_pWorkMan != null && m_pWorkMan.HasWorkRunningOnPriority(CECHPWorkMan.Work_priority.PRIORITY_2)) ||
|
||||
m_bHangerOn || IsOperatingPet() != 0 || IsRebuildingPet() ||
|
||||
IsUsingItem() || IsRidingOnPet() || GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() ||
|
||||
m_playerLimits[(int)PLAYER_LIMIT.PLAYER_LIMIT_NOFLY] /*|| m_BattleInfo.IsChariotWar()*/)
|
||||
bRet = false;
|
||||
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_PICKUP:
|
||||
case ActionCanDo.CANDO_GATHER:
|
||||
|
||||
if (IsDead() /*|| IsAboutToDie() || IsSitting() || IsTrading() || IsUsingTrashBox() ||
|
||||
IsReviving() || IsTalkingWithNPC() || IsChangingFace() || GetBoothState() != 0 ||
|
||||
GetBuddyState() == 1 || IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || IsPassiveMove()*/)
|
||||
if (IsDead() || IsAboutToDie() || IsSitting() || IsTrading() || IsUsingTrashBox() ||
|
||||
IsReviving() || IsTalkingWithNPC() || IsChangingFace() || GetBoothState() != 0 ||
|
||||
GetBuddyState() == 1 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove())
|
||||
bRet = false;
|
||||
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_TRADE:
|
||||
|
||||
if (IsDead() || IsMeleeing() /*|| IsAboutToDie() || IsSitting() || IsJumping() ||
|
||||
IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() ||
|
||||
IsSpellingMagic() || GetBoothState() != 0 || m_iBuddyId || IsOperatingPet() || IsRebuildingPet() ||
|
||||
IsUsingItem() || IsInvisible() || IsPassiveMove()*/)
|
||||
if (IsDead() || IsAboutToDie() || IsSitting() || IsJumping() || IsMeleeing() ||
|
||||
IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() ||
|
||||
IsSpellingMagic() || GetBoothState() != 0 || m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() ||
|
||||
IsUsingItem() || IsInvisible() || IsPassiveMove())
|
||||
bRet = false;
|
||||
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_PLAYPOSE:
|
||||
|
||||
if (IsDead() || IsMeleeing() || /*|| IsAboutToDie() || IsSitting() || IsJumping() || /* ||
|
||||
IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() ||
|
||||
IsSpellingMagic() || IsShapeChanged() || IsReviving() ||*/
|
||||
m_iMoveEnv != (int)MoveEnvironment.MOVEENV_GROUND /*||
|
||||
GetBoothState() != 0 || m_iBuddyId || IsOperatingPet() || IsRebuildingPet() || IsUsingItem() ||
|
||||
IsRidingOnPet() || GetShapeType() == PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() || m_BattleInfo.IsChariotWar()*/
|
||||
)
|
||||
if (IsDead() || IsAboutToDie() || IsSitting() || IsJumping() || IsMeleeing() ||
|
||||
IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() ||
|
||||
IsSpellingMagic() || IsShapeChanged() || IsReviving() || m_iMoveEnv != (int)MoveEnvironment.MOVEENV_GROUND ||
|
||||
GetBoothState() != 0 || m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() ||
|
||||
IsRidingOnPet() || GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() /*|| m_BattleInfo.IsChariotWar()*/)
|
||||
bRet = false;
|
||||
|
||||
break;
|
||||
|
||||
//case ActionCanDo.CANDO_SPELLMAGIC:
|
||||
// if (IsDead() || ISMATTERID(m_idSelTarget) || IsAboutToDie() || IsSitting() ||
|
||||
// IsJumping() || IsFlashMoving() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
// IsChangingFace() || CannotAttack() || IsReviving() || GetBoothState() != 0 ||
|
||||
// m_iBuddyId || IsRidingOnPet() || IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || IsPassiveMove())
|
||||
// bRet = false;
|
||||
|
||||
// break;
|
||||
case ActionCanDo.CANDO_SPELLMAGIC:
|
||||
if (IsDead() || GPDataTypeHelper.ISMATTERID(m_idSelTarget) || IsAboutToDie() || IsSitting() ||
|
||||
IsJumping() || IsFlashMoving() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
IsChangingFace() || CannotAttack() || IsReviving() || GetBoothState() != 0 ||
|
||||
m_iBuddyId != 0 || IsRidingOnPet() || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove())
|
||||
bRet = false;
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_SUMMONPET:
|
||||
|
||||
if (IsDead() || GPDataTypeHelper.ISMATTERID(m_idSelTarget) || IsAboutToDie() || IsSitting() ||
|
||||
IsJumping() || /*IsFlashMoving() ||*/ IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
IsChangingFace() || CannotAttack() || IsReviving() || GetBoothState() != 0 ||
|
||||
IsInvisible() /*|| IsGMInvisible()*/ || IsOperatingPet() != 0 || /*IsRebuildingPet() ||*/ IsUsingItem() || IsPassiveMove()
|
||||
/*|| m_BattleInfo.IsChariotWar()*/)
|
||||
IsJumping() || IsFlashMoving() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
IsChangingFace() || CannotAttack() || IsReviving() || GetBoothState() != 0 ||
|
||||
IsInvisible() || IsGMInvisible() || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove()
|
||||
/*|| m_BattleInfo.IsChariotWar()*/)
|
||||
bRet = false;
|
||||
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_REBUILDPET:
|
||||
|
||||
if (IsDead() || GPDataTypeHelper.ISMATTERID(m_idSelTarget) /*|| IsAboutToDie() || IsSitting() */ ||
|
||||
IsJumping() /*|| IsFlashMoving() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
IsChangingFace()*/ || CannotAttack() /*|| IsReviving() || GetBoothState() != 0 ||
|
||||
m_iBuddyId || IsInvisible() || IsGMInvisible() || IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || IsPassiveMove() ||
|
||||
IsPlayerMoving() || m_BattleInfo.IsChariotWar()*/)
|
||||
if (IsDead() || GPDataTypeHelper.ISMATTERID(m_idSelTarget) || IsAboutToDie() || IsSitting() ||
|
||||
IsJumping() || IsFlashMoving() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
IsChangingFace() || CannotAttack() || IsReviving() || GetBoothState() != 0 ||
|
||||
m_iBuddyId != 0 || IsInvisible() || IsGMInvisible() || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove() ||
|
||||
IsPlayerMoving() /*|| m_BattleInfo.IsChariotWar()*/)
|
||||
bRet = false;
|
||||
|
||||
break;
|
||||
|
||||
//case ActionCanDo.CANDO_USEITEM:
|
||||
|
||||
// if (IsAboutToDie() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
// IsChangingFace() || GetBoothState() != 0 || IsPassiveMove() || m_BattleInfo.IsChariotWar())
|
||||
// bRet = false;
|
||||
|
||||
// break;
|
||||
case ActionCanDo.CANDO_USEITEM:
|
||||
if (IsAboutToDie() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
IsChangingFace() || GetBoothState() != 0 || IsPassiveMove() /*|| m_BattleInfo.IsChariotWar()*/)
|
||||
bRet = false;
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_JUMP:
|
||||
{
|
||||
if (IsDead() ||
|
||||
m_iJumpCount >= MAX_JUMP_COUNT ||
|
||||
// cannot jump more than one time if shape mode is type2
|
||||
//(IsJumping() && (GetShapeType() == PLAYERMODEL_DUMMYTYPE2)) ||
|
||||
IsJumpInWater() || m_iMoveEnv == Move_environment.MOVEENV_AIR || IsSitting() ||
|
||||
IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
IsChangingFace() || IsReviving() || IsSpellingMagic() || IsPicking() ||
|
||||
IsGathering() || IsRooting() || GetBoothState() != 0 ||
|
||||
m_bHangerOn || /*(IsJumping() && IsRidingOnPet()) ||*/
|
||||
/*IsOperatingPet() || IsRebuildingPet() ||*/ IsUsingItem() ||
|
||||
IsPassiveMove() /*|| m_BattleInfo.IsChariotWar()*/)
|
||||
bRet = false;
|
||||
|
||||
break;
|
||||
}
|
||||
//case ActionCanDo.CANDO_FOLLOW:
|
||||
// {
|
||||
// if (IsDead() || IsAboutToDie() || IsSitting() || IsMeleeing() || IsReviving() ||
|
||||
// IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() ||
|
||||
// IsSpellingMagic() || GetBoothState() != 0 || m_bHangerOn || IsOperatingPet() || IsRebuildingPet() ||
|
||||
// IsUsingItem() || IsPassiveMove())
|
||||
// bRet = false;
|
||||
|
||||
// break;
|
||||
// }
|
||||
//case ActionCanDo.CANDO_BOOTH:
|
||||
|
||||
// if (IsDead() || IsAboutToDie() || IsPlayerMoving() || IsSitting() || IsReviving() ||
|
||||
// IsMeleeing() || IsJumping() || IsTrading() || IsUsingTrashBox() ||
|
||||
// IsTalkingWithNPC() || IsChangingFace() || IsSpellingMagic() || IsFlying() ||
|
||||
// IsUnderWater() || m_iBuddyId || IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || IsRidingOnPet() || IsInvisible() ||
|
||||
// IsPassiveMove())
|
||||
// bRet = false;
|
||||
|
||||
// break;
|
||||
|
||||
//case ActionCanDo.CANDO_FLASHMOVE:
|
||||
|
||||
// if (IsDead() || IsAboutToDie() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
// IsJumping() || IsFlashMoving() || IsFalling() || IsChangingFace() || GetBoothState() != 0 || IsTakingOff() ||
|
||||
// m_pWorkMan.HasWorkRunningOnPriority(CECHPWorkMan::PRIORITY_2) ||
|
||||
// m_iBuddyId || IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || IsPassiveMove())
|
||||
// bRet = false;
|
||||
|
||||
// break;
|
||||
|
||||
//case ActionCanDo.CANDO_BINDBUDDY:
|
||||
|
||||
// if (IsDead() || IsAboutToDie() || IsJumping() || IsSitting() ||
|
||||
// IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
// IsChangingFace() || IsReviving() || IsSpellingMagic() || IsPicking() ||
|
||||
// IsGathering() || IsRooting() || GetBoothState() != 0 ||
|
||||
// !m_pWorkMan.IsStanding() || m_iBuddyId ||
|
||||
// IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || GetShapeType() == PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() ||
|
||||
// m_playerLimits.test(PLAYER_LIMIT_NOBIND))
|
||||
// bRet = false;
|
||||
|
||||
// break;
|
||||
|
||||
//case ActionCanDo.CANDO_DUEL:
|
||||
|
||||
// if (IsDead() || IsAboutToDie() || IsSitting() || IsFighting() || IsTrading() ||
|
||||
// IsReviving() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() ||
|
||||
// GetBoothState() != 0 || m_iBuddyId || m_pvp.iDuelState != DUEL_ST_NONE ||
|
||||
// IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || IsPassiveMove())
|
||||
// bRet = false;
|
||||
|
||||
// break;
|
||||
|
||||
case ActionCanDo.CANDO_CHANGESELECT:
|
||||
|
||||
//if (m_playerLimits.test(PLAYER_LIMIT_NOCHANGESELECT))
|
||||
// bRet = false;
|
||||
|
||||
if (IsDead() ||
|
||||
m_iJumpCount >= MAX_JUMP_COUNT ||
|
||||
// cannot jump more than one time if shape mode is type2
|
||||
(IsJumping() && (GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2)) ||
|
||||
IsJumpInWater() || m_iMoveEnv == (int)MoveEnvironment.MOVEENV_AIR || IsSitting() ||
|
||||
IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
IsChangingFace() || IsReviving() || IsSpellingMagic() || IsPicking() ||
|
||||
IsGathering() || IsRooting() || GetBoothState() != 0 || m_bHangerOn || (IsJumping() && IsRidingOnPet()) ||
|
||||
IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove() /*|| m_BattleInfo.IsChariotWar()*/)
|
||||
bRet = false;
|
||||
break;
|
||||
|
||||
//case ActionCanDo.CANDO_SWITCH_PARALLEL_WORLD:
|
||||
// if (IsDead() || IsAboutToDie() || IsJumping() || IsFighting() ||
|
||||
// IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
// IsChangingFace() || IsReviving() || IsSpellingMagic() || IsPicking() ||
|
||||
// IsGathering() || IsRooting() || GetBoothState() != 0 ||
|
||||
// m_iBuddyId || IsOperatingPet() || IsRebuildingPet() || IsUsingItem() ||
|
||||
// GetShapeType() == PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove())
|
||||
// bRet = false;
|
||||
// break;
|
||||
case ActionCanDo.CANDO_FOLLOW:
|
||||
if (IsDead() || IsAboutToDie() || IsSitting() || IsMeleeing() || IsReviving() ||
|
||||
IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() ||
|
||||
IsSpellingMagic() || GetBoothState() != 0 || m_bHangerOn || IsOperatingPet() != 0 || IsRebuildingPet() ||
|
||||
IsUsingItem() || IsPassiveMove())
|
||||
bRet = false;
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_BOOTH:
|
||||
if (IsDead() || IsAboutToDie() || IsPlayerMoving() || IsSitting() || IsReviving() ||
|
||||
IsMeleeing() || IsJumping() || IsTrading() || IsUsingTrashBox() ||
|
||||
IsTalkingWithNPC() || IsChangingFace() || IsSpellingMagic() || IsFlying() ||
|
||||
IsUnderWater() || m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsRidingOnPet() || IsInvisible() ||
|
||||
IsPassiveMove())
|
||||
bRet = false;
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_FLASHMOVE:
|
||||
if (IsDead() || IsAboutToDie() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
IsJumping() || IsFlashMoving() || IsFalling() || IsChangingFace() || GetBoothState() != 0 || IsTakingOff() ||
|
||||
(m_pWorkMan != null && m_pWorkMan.HasWorkRunningOnPriority(CECHPWorkMan.Work_priority.PRIORITY_2)) ||
|
||||
m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove())
|
||||
bRet = false;
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_BINDBUDDY:
|
||||
if (IsDead() || IsAboutToDie() || IsJumping() || IsSitting() ||
|
||||
IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
IsChangingFace() || IsReviving() || IsSpellingMagic() || IsPicking() ||
|
||||
IsGathering() || IsRooting() || GetBoothState() != 0 ||
|
||||
(m_pWorkMan != null && !m_pWorkMan.IsStanding()) || m_iBuddyId != 0 ||
|
||||
IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() ||
|
||||
m_playerLimits[(int)PLAYER_LIMIT.PLAYER_LIMIT_NOBIND])
|
||||
bRet = false;
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_DUEL:
|
||||
if (IsDead() || IsAboutToDie() || IsSitting() || IsFighting() || IsTrading() ||
|
||||
IsReviving() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() ||
|
||||
GetBoothState() != 0 || m_iBuddyId != 0 || m_pvp.iDuelState != (int)DuelState.DUEL_ST_NONE ||
|
||||
IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove())
|
||||
bRet = false;
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_CHANGESELECT:
|
||||
if (m_playerLimits[(int)PLAYER_LIMIT.PLAYER_LIMIT_NOCHANGESELECT])
|
||||
bRet = false;
|
||||
break;
|
||||
|
||||
case ActionCanDo.CANDO_SWITCH_PARALLEL_WORLD:
|
||||
if (IsDead() || IsAboutToDie() || IsJumping() || IsFighting() ||
|
||||
IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
||||
IsChangingFace() || IsReviving() || IsSpellingMagic() || IsPicking() ||
|
||||
IsGathering() || IsRooting() || GetBoothState() != 0 ||
|
||||
m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() ||
|
||||
GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove())
|
||||
bRet = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return bRet;
|
||||
@@ -2295,6 +2254,73 @@ namespace BrewMonster
|
||||
return m_pWorkMan.IsPassiveMoving();
|
||||
}
|
||||
|
||||
// Is about to die / 是否即将死亡
|
||||
bool IsAboutToDie()
|
||||
{
|
||||
return m_bAboutToDie;
|
||||
}
|
||||
|
||||
// Is rebuilding pet / 是否正在重建宠物
|
||||
bool IsRebuildingPet()
|
||||
{
|
||||
return m_bInRebuildPet;
|
||||
}
|
||||
|
||||
// Is riding on pet / 是否骑乘宠物
|
||||
bool IsRidingOnPet()
|
||||
{
|
||||
return m_RidingPet.id != 0;
|
||||
}
|
||||
|
||||
// Is flash moving / 是否在闪移
|
||||
bool IsFlashMoving()
|
||||
{
|
||||
if (m_pWorkMan == null) return false;
|
||||
return m_pWorkMan.IsFlashMoving();
|
||||
}
|
||||
|
||||
// Get buddy state / 获取伙伴状态
|
||||
// return value: 0 = no buddy, 1 = has buddy, 2 = hanger on
|
||||
int GetBuddyState()
|
||||
{
|
||||
if (m_bHangerOn) return 2;
|
||||
if (m_iBuddyId != 0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Is invisible / 是否隐身
|
||||
bool IsInvisible()
|
||||
{
|
||||
return (m_dwStates & (uint)PlayerNPCState.GP_STATE_INVISIBLE) != 0;
|
||||
}
|
||||
|
||||
// Is GM invisible / 是否GM隐身
|
||||
bool IsGMInvisible()
|
||||
{
|
||||
// GMF_INVISIBLE would be a constant, using bit check
|
||||
// GMF_INVISIBLE 将是一个常量,使用位检查
|
||||
return (m_dwGMFlags & 0x01) != 0; // Assuming GMF_INVISIBLE = 0x01
|
||||
}
|
||||
|
||||
// Is shape changed / 是否形状已改变
|
||||
bool IsShapeChanged()
|
||||
{
|
||||
return m_iShape != 0;
|
||||
}
|
||||
|
||||
// Is taking off / 是否正在起飞
|
||||
bool IsTakingOff()
|
||||
{
|
||||
if (m_pWorkMan == null) return false;
|
||||
return m_pWorkMan.IsFlyingOff();
|
||||
}
|
||||
|
||||
// Is flying / 是否在飞行
|
||||
bool IsFlying()
|
||||
{
|
||||
return (m_dwStates & (uint)PlayerNPCState.GP_STATE_FLY) != 0;
|
||||
}
|
||||
|
||||
//public void SetGroundInfoClient()
|
||||
//{
|
||||
// isGrounded = GroundCheck(out lastGroundHit);
|
||||
@@ -3547,7 +3573,11 @@ namespace BrewMonster
|
||||
// Update battle result time counter
|
||||
if (IsInBattle() && !IsInFortress() && m_BattleInfo.iResult != 0 && m_BattleInfo.iResultCnt != 0)
|
||||
{
|
||||
if ((m_BattleInfo.iResultCnt -= dwDeltaTime) < 0)
|
||||
// iResultCnt is time counter (likely in milliseconds as int), dwDeltaTime is in seconds (float)
|
||||
// iResultCnt是时间计数器(可能是毫秒为int),dwDeltaTime是秒数(float)
|
||||
// Convert seconds to milliseconds and subtract / 将秒转换为毫秒并减去
|
||||
int deltaTimeMs = (int)(dwDeltaTime * 1000f);
|
||||
if ((m_BattleInfo.iResultCnt -= deltaTimeMs) < 0)
|
||||
m_BattleInfo.iResultCnt = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,9 +48,23 @@
|
||||
↓
|
||||
12. _get_pos_by_id() [EC_ManSkillGfx.cpp:10]
|
||||
- **HOOK LOOKUP AND POSITION CALCULATION**
|
||||
- Gets hook from skeleton
|
||||
- Gets hook from skeleton by string name
|
||||
- Calculates world position using hook transform
|
||||
↓
|
||||
12a. A3DSkinModel::GetSkeletonHook() [A3DSkinModel.cpp:2357]
|
||||
- Searches in main skeleton first
|
||||
- Optionally searches child models (recursive)
|
||||
↓
|
||||
12b. A3DSkeleton::GetHook() [A3DSkeleton.cpp:876]
|
||||
- **STRING-BASED HOOK LOOKUP**
|
||||
- Iterates through m_aHooks array
|
||||
- Compares hook names using stricmp() (case-insensitive)
|
||||
- Returns A3DSkeletonHook* if found
|
||||
↓
|
||||
12c. A3DSkeletonHook::GetAbsoluteTM() [A3DSkeleton.cpp:209]
|
||||
- Returns world transform matrix
|
||||
- Updates hook transform if needed
|
||||
↓
|
||||
13. A3DSkillGfxEvent::Tick() [A3DSkillGfxEvent2.cpp:487]
|
||||
- State machine: Wait → Flying → Hit → Finished
|
||||
- Updates fly GFX position/rotation each frame
|
||||
@@ -61,6 +75,90 @@
|
||||
|
||||
## Detailed Hook System Usage
|
||||
|
||||
### Step 12b: A3DSkeleton::GetHook() - String-Based Hook Lookup
|
||||
|
||||
**File:** `A3DSkeleton.cpp:876-903`
|
||||
|
||||
This is the **core function that looks up hooks by string name**:
|
||||
|
||||
```cpp
|
||||
A3DSkeletonHook* A3DSkeleton::GetHook(const char* szName, int* piIndex)
|
||||
{
|
||||
if (!szName)
|
||||
return NULL;
|
||||
|
||||
// Try index optimization first (if index provided and valid)
|
||||
if (piIndex && *piIndex >= 0 && *piIndex < m_aHooks.GetSize())
|
||||
{
|
||||
A3DSkeletonHook* pHook = m_aHooks[*piIndex];
|
||||
if (!stricmp(pHook->GetName(), szName)) // Case-insensitive compare
|
||||
return pHook;
|
||||
}
|
||||
|
||||
// Enumerate all hooks and compare names
|
||||
for (int i=0; i < m_aHooks.GetSize(); i++)
|
||||
{
|
||||
A3DSkeletonHook* pHook = m_aHooks[i];
|
||||
if (!stricmp(pHook->GetName(), szName)) // Case-insensitive string compare
|
||||
{
|
||||
if (piIndex)
|
||||
*piIndex = i; // Update index for future optimization
|
||||
|
||||
return pHook; // Found hook by name!
|
||||
}
|
||||
}
|
||||
|
||||
return NULL; // Hook not found
|
||||
}
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Hooks are stored in `m_aHooks` array (APtrArray<A3DSkeletonHook*>)
|
||||
- Uses **case-insensitive string comparison** (`stricmp`)
|
||||
- Hook names are loaded from skeleton file (`.ske` or `.bon` file)
|
||||
- Each hook has a name (e.g., "hand_r", "hand_l", "weapon", "head")
|
||||
- Returns `NULL` if hook not found
|
||||
|
||||
**Hook Storage:**
|
||||
- Hooks are loaded from skeleton files during model loading
|
||||
- Each hook is attached to a bone (`m_iBone` index)
|
||||
- Hook has local transform (`m_matHookTM`) relative to bone
|
||||
- Hook name is stored in `A3DSkeletonHook::m_strName`
|
||||
|
||||
### Step 12a: A3DSkinModel::GetSkeletonHook() - Hook Search with Child Model Support
|
||||
|
||||
**File:** `A3DSkinModel.cpp:2357` (referenced in HOOK_SYSTEM_C++_REFERENCE.md)
|
||||
|
||||
```cpp
|
||||
A3DSkeletonHook* A3DSkinModel::GetSkeletonHook(const char* szName, bool bNoChild)
|
||||
{
|
||||
A3DSkeletonHook* pHook = NULL;
|
||||
|
||||
// Search in main skeleton first
|
||||
if (m_pA3DSkeleton) {
|
||||
if ((pHook = m_pA3DSkeleton->GetHook(szName, NULL))) // Calls GetHook() by name
|
||||
return pHook;
|
||||
}
|
||||
|
||||
// Search in child models (if bNoChild == false, recursive)
|
||||
if (!bNoChild) {
|
||||
for (int i = 0; i < m_aChildModels.GetSize(); i++) {
|
||||
A3DSkinModel* pChild = m_aChildModels[i];
|
||||
if ((pHook = pChild->GetSkeletonHook(szName, false))) // Recursive search
|
||||
return pHook;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- First searches main skeleton using `A3DSkeleton::GetHook(szName, NULL)`
|
||||
- If not found and `bNoChild == false`, searches child models recursively
|
||||
- Child models are weapons, pets, etc. attached to main model
|
||||
- This allows hooks on weapons/pets to be found
|
||||
|
||||
### Step 11: CECSkillGfxEvent::Tick() - Hook Position Updates
|
||||
|
||||
**File:** `EC_ManSkillGfx.cpp:307-373`
|
||||
@@ -138,6 +236,25 @@ void CECSkillGfxEvent::Tick(DWORD dwDeltaTime)
|
||||
|
||||
### Step 12: _get_pos_by_id() - Hook Lookup and Position Calculation
|
||||
|
||||
**Complete Hook Lookup Chain:**
|
||||
```
|
||||
_get_pos_by_id(szHook="hand_r")
|
||||
↓
|
||||
A3DSkinModel::GetSkeletonHook("hand_r", true)
|
||||
↓
|
||||
A3DSkeleton::GetHook("hand_r", NULL)
|
||||
↓
|
||||
Iterates m_aHooks[] array
|
||||
↓
|
||||
stricmp(pHook->GetName(), "hand_r") == 0
|
||||
↓
|
||||
Returns A3DSkeletonHook* pointer
|
||||
↓
|
||||
pHook->GetAbsoluteTM() → World transform matrix
|
||||
↓
|
||||
Calculate position: vPos = pHook->GetAbsoluteTM() * offset
|
||||
```
|
||||
|
||||
**File:** `EC_ManSkillGfx.cpp:10-122`
|
||||
|
||||
#### Player Branch (Lines 24-87)
|
||||
@@ -347,23 +464,61 @@ void A3DSkillGfxEvent::Tick(DWORD dwDeltaTime)
|
||||
- `FlyPos`: Hook for fly GFX spawn position (host)
|
||||
- `FlyEndPos`: Hook for fly GFX target position (target)
|
||||
- `HitPos`: Hook for hit GFX spawn position
|
||||
- Hook names are strings (e.g., "hand_r", "weapon", "head")
|
||||
|
||||
2. **Runtime Lookup:** `_get_pos_by_id()` called every frame in `CECSkillGfxEvent::Tick()`
|
||||
- Looks up hook by name from skeleton
|
||||
- Supports child models (weapons, pets)
|
||||
2. **String-Based Hook Lookup Chain:**
|
||||
```
|
||||
Hook name from .sgc file (e.g., "hand_r")
|
||||
↓
|
||||
_get_pos_by_id(szHook="hand_r")
|
||||
↓
|
||||
A3DSkinModel::GetSkeletonHook("hand_r", true)
|
||||
↓
|
||||
A3DSkeleton::GetHook("hand_r", NULL) ← **STRING LOOKUP HERE**
|
||||
↓
|
||||
Iterates m_aHooks[] array
|
||||
↓
|
||||
stricmp(pHook->GetName(), "hand_r") == 0 ← **CASE-INSENSITIVE COMPARE**
|
||||
↓
|
||||
Returns A3DSkeletonHook* pointer
|
||||
```
|
||||
|
||||
3. **Runtime Lookup:** `_get_pos_by_id()` called every frame in `CECSkillGfxEvent::Tick()`
|
||||
- Looks up hook by **string name** from skeleton's hook array
|
||||
- Uses case-insensitive string comparison (`stricmp`)
|
||||
- Supports child models (weapons, pets) via recursive search
|
||||
- Calculates world position using hook transform matrix
|
||||
|
||||
3. **Position Calculation:**
|
||||
4. **Position Calculation:**
|
||||
- **Relative (`bRelHook = true`)**: `hookWorldMatrix * offset` → offset rotates with hook
|
||||
- **Absolute (`bRelHook = false`)**: `modelWorldMatrix * offset` then translate to hook position
|
||||
|
||||
4. **Fallback:** If hook lookup fails, uses default position (AABB center or bottom)
|
||||
5. **Fallback:** If hook lookup fails, uses default position (AABB center or bottom)
|
||||
|
||||
5. **GFX Attachment:**
|
||||
6. **GFX Attachment:**
|
||||
- Fly GFX spawns at `m_vHostPos` (from hook if specified)
|
||||
- Fly GFX moves toward `m_vTargetPos` (from hook if specified)
|
||||
- Hit GFX spawns at `GetTargetCenter()` (from hook if specified)
|
||||
|
||||
## Key Implementation Detail: String-Based Hook Lookup
|
||||
|
||||
**The critical missing piece in the flow is `A3DSkeleton::GetHook(const char* szName, int* piIndex)`:**
|
||||
|
||||
- **Location:** `A3DSkeleton.cpp:876-903`
|
||||
- **Method:** Linear search through `m_aHooks` array
|
||||
- **Comparison:** Case-insensitive string compare using `stricmp(pHook->GetName(), szName)`
|
||||
- **Storage:** Hooks stored in `APtrArray<A3DSkeletonHook*> m_aHooks`
|
||||
- **Hook Names:** Loaded from skeleton files (`.ske` or `.bon` files) during model loading
|
||||
- **Performance:** O(n) lookup, but typically only 5-20 hooks per skeleton
|
||||
|
||||
**Example Hook Names:**
|
||||
- `"hand_r"` - Right hand
|
||||
- `"hand_l"` - Left hand
|
||||
- `"weapon"` - Weapon attachment point
|
||||
- `"head"` - Head position
|
||||
- `"foot_r"` - Right foot
|
||||
- `"foot_l"` - Left foot
|
||||
|
||||
---
|
||||
|
||||
## C++ to C# Conversion Notes
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
# Hook System Unity Parenting Approach
|
||||
|
||||
**Date Created:** 2026-02-24
|
||||
**Purpose:** Analyze whether we can use Unity's Transform parenting instead of manual position calculation
|
||||
|
||||
---
|
||||
|
||||
## Question
|
||||
|
||||
Instead of manually calculating hook positions every frame and updating GFX positions, can we:
|
||||
1. Parent GFX GameObject directly to hook Transform?
|
||||
2. Eliminate the need for `GetHookWorldPosition()` calculations?
|
||||
3. Let Unity automatically handle position updates?
|
||||
|
||||
---
|
||||
|
||||
## Analysis: When Can We Parent vs. When We Need Manual Updates
|
||||
|
||||
### ✅ **YES - Can Parent (Hit GFX that follows target)**
|
||||
|
||||
**Use Case:** Hit GFX that needs to follow a moving target
|
||||
|
||||
**C++ Behavior:**
|
||||
```cpp
|
||||
// A3DSkillGfxEvent2.cpp:502-508
|
||||
if (m_bTraceTarget)
|
||||
{
|
||||
A3DMATRIX4 matTran;
|
||||
matTran.Identity();
|
||||
matTran.SetRow(3, GetTargetCenter()); // Updates every frame
|
||||
m_pHitGfx->SetParentTM(matTran);
|
||||
}
|
||||
```
|
||||
|
||||
**Unity Approach:**
|
||||
```csharp
|
||||
// Instead of:
|
||||
Vector3 targetPos = HookUtils.GetHookWorldPosition(targetHook, offset, bRelHook);
|
||||
hitGfx.transform.position = targetPos; // Every frame
|
||||
|
||||
// We can do:
|
||||
hitGfx.transform.SetParent(targetHook.transform);
|
||||
hitGfx.transform.localPosition = offset; // One-time setup
|
||||
// Unity automatically updates position every frame!
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ No manual position calculation every frame
|
||||
- ✅ Automatically follows bone/hook movement
|
||||
- ✅ Handles rotation automatically
|
||||
- ✅ More Unity-native approach
|
||||
- ✅ Better performance (Unity's transform system is optimized)
|
||||
|
||||
---
|
||||
|
||||
### ⚠️ **PARTIAL - Can Use for Spawn Position Only (Fly GFX)**
|
||||
|
||||
**Use Case:** Fly GFX spawns at host hook, then moves independently to target
|
||||
|
||||
**C++ Behavior:**
|
||||
```cpp
|
||||
// A3DSkillGfxEvent2.cpp:535-546
|
||||
// Fly GFX spawns at host position
|
||||
m_pMoveMethod->StartMove(m_vHostPos, m_vTargetPos);
|
||||
|
||||
if (m_pFlyGfx)
|
||||
{
|
||||
m_pFlyGfx->SetParentTM(_build_matrix(m_pMoveMethod->GetMoveDir(), m_pMoveMethod->GetPos()));
|
||||
m_pFlyGfx->Start(true);
|
||||
}
|
||||
|
||||
// Then every frame:
|
||||
m_pMoveMethod->TickMove(dwDeltaTime, m_vHostPos, m_vTargetPos); // Updates position
|
||||
m_pFlyGfx->SetParentTM(_build_matrix(m_pMoveMethod->GetMoveDir(), m_pMoveMethod->GetPos()));
|
||||
```
|
||||
|
||||
**Unity Approach:**
|
||||
```csharp
|
||||
// Spawn at hook position (one-time)
|
||||
Transform hostHook = GetHook("hand_r");
|
||||
flyGfx.transform.position = hostHook.position;
|
||||
flyGfx.transform.rotation = hostHook.rotation;
|
||||
|
||||
// Then IMMEDIATELY unparent and move independently
|
||||
flyGfx.transform.SetParent(null); // Unparent for independent movement
|
||||
|
||||
// Update position manually during flight
|
||||
flyGfx.transform.position = m_pMoveMethod.GetPos();
|
||||
flyGfx.transform.rotation = Quaternion.LookRotation(m_pMoveMethod.GetMoveDir());
|
||||
```
|
||||
|
||||
**Why Can't Fully Parent:**
|
||||
- Fly GFX needs to move independently from host to target
|
||||
- Target position may also come from a hook (which moves)
|
||||
- Movement is interpolated (not directly following either hook)
|
||||
|
||||
**Hybrid Approach:**
|
||||
- ✅ Use hook Transform for **initial spawn position** (one-time)
|
||||
- ❌ Can't parent during flight (needs independent movement)
|
||||
- ✅ Can use hook Transform for **target position calculation** (if target has hook)
|
||||
|
||||
---
|
||||
|
||||
### ✅ **YES - Can Use for Offsets (Child GameObject)**
|
||||
|
||||
**Use Case:** GFX needs offset from hook position
|
||||
|
||||
**C++ Behavior:**
|
||||
```cpp
|
||||
// Relative offset: transform offset in hook's local space
|
||||
if (bRelHook) {
|
||||
vPos = pHook->GetAbsoluteTM() * (*pOffset);
|
||||
}
|
||||
```
|
||||
|
||||
**Unity Approach:**
|
||||
```csharp
|
||||
// Instead of calculating:
|
||||
Vector3 worldPos = hookTransform.TransformPoint(offset);
|
||||
|
||||
// We can create a child GameObject:
|
||||
GameObject offsetObj = new GameObject("GFX_Offset");
|
||||
offsetObj.transform.SetParent(hookTransform);
|
||||
offsetObj.transform.localPosition = offset; // Offset in hook's local space
|
||||
|
||||
// Then parent GFX to offset object:
|
||||
gfx.transform.SetParent(offsetObj.transform);
|
||||
gfx.transform.localPosition = Vector3.zero; // GFX at offset position
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Handles relative offsets automatically
|
||||
- ✅ Rotates with hook automatically
|
||||
- ✅ No manual matrix calculations
|
||||
|
||||
---
|
||||
|
||||
## Recommended Unity Implementation Strategy
|
||||
|
||||
### Strategy 1: Hybrid Approach (Recommended)
|
||||
|
||||
**For Hit GFX that follows target:**
|
||||
```csharp
|
||||
public class CECSkillGfxEvent : A3DSkillGfxEvent
|
||||
{
|
||||
private GameObject m_hitGfxInstance;
|
||||
private Transform m_targetHook; // Hook Transform (if available)
|
||||
|
||||
private void SpawnHitGfx(Vector3 vTarget)
|
||||
{
|
||||
// Try to get target hook
|
||||
m_targetHook = GetTargetHookTransform();
|
||||
|
||||
if (m_targetHook != null && m_pComposer.m_HitPos.bTraceTarget)
|
||||
{
|
||||
// Parent to hook - Unity handles updates automatically
|
||||
m_hitGfxInstance = Instantiate(hitGfxPrefab);
|
||||
m_hitGfxInstance.transform.SetParent(m_targetHook);
|
||||
|
||||
// Apply offset
|
||||
if (m_pComposer.m_HitPos.bRelHook)
|
||||
{
|
||||
// Relative offset: in hook's local space
|
||||
m_hitGfxInstance.transform.localPosition = m_pComposer.m_HitPos.vOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Absolute offset: need to calculate once
|
||||
// (Can't fully eliminate this, but only calculate once)
|
||||
Vector3 offsetWorld = m_targetHook.root.TransformPoint(m_pComposer.m_HitPos.vOffset);
|
||||
m_hitGfxInstance.transform.position = offsetWorld - m_targetHook.root.position + m_targetHook.position;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No hook or doesn't trace target - use manual position
|
||||
m_hitGfxInstance = Instantiate(hitGfxPrefab, vTarget, Quaternion.identity);
|
||||
// Update manually if needed
|
||||
}
|
||||
}
|
||||
|
||||
private Transform GetTargetHookTransform()
|
||||
{
|
||||
// Get hook Transform from target
|
||||
if (string.IsNullOrEmpty(m_pComposer.m_HitPos.szHook))
|
||||
return null;
|
||||
|
||||
CECModel targetModel = GetTargetModel();
|
||||
if (targetModel == null)
|
||||
return null;
|
||||
|
||||
// Get hook Transform (not position!)
|
||||
return targetModel.GetHookTransform(
|
||||
m_pComposer.m_HitPos.szHook,
|
||||
m_pComposer.m_HitPos.szHanger,
|
||||
m_pComposer.m_HitPos.bChildHook);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**For Fly GFX:**
|
||||
```csharp
|
||||
private void SpawnFlyGfx()
|
||||
{
|
||||
// Get host hook Transform (for initial position)
|
||||
Transform hostHook = GetHostHookTransform();
|
||||
|
||||
if (hostHook != null)
|
||||
{
|
||||
// Spawn at hook position
|
||||
m_flyGfxInstance = Instantiate(flyGfxPrefab);
|
||||
m_flyGfxInstance.transform.position = hostHook.position;
|
||||
m_flyGfxInstance.transform.rotation = hostHook.rotation;
|
||||
|
||||
// Apply offset if needed
|
||||
if (m_pComposer.m_FlyPos.bRelHook)
|
||||
{
|
||||
m_flyGfxInstance.transform.position = hostHook.TransformPoint(m_pComposer.m_FlyPos.vOffset);
|
||||
}
|
||||
|
||||
// IMMEDIATELY unparent - fly GFX moves independently
|
||||
m_flyGfxInstance.transform.SetParent(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No hook - use calculated position
|
||||
m_flyGfxInstance = Instantiate(flyGfxPrefab, m_pMoveMethod.GetPos(), Quaternion.identity);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFlyGfxTransform()
|
||||
{
|
||||
if (m_flyGfxInstance == null) return;
|
||||
|
||||
// Update position manually (can't parent during flight)
|
||||
m_flyGfxInstance.transform.position = m_pMoveMethod.GetPos();
|
||||
Vector3 dir = m_pMoveMethod.GetMoveDir();
|
||||
if (dir.magnitude > 1e-4f)
|
||||
m_flyGfxInstance.transform.rotation = Quaternion.LookRotation(dir);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Strategy 2: Helper Method to Get Hook Transform (Not Position)
|
||||
|
||||
**New Method in HookUtils or SkeletonBuilder:**
|
||||
```csharp
|
||||
public static Transform GetHookTransform(
|
||||
SkeletonBuilder skeleton,
|
||||
string hookName,
|
||||
string hangerName = null,
|
||||
bool bChildHook = false)
|
||||
{
|
||||
// Get model (main or child)
|
||||
CECModel model = skeleton.GetComponent<CECModel>();
|
||||
if (hangerName != null && bChildHook)
|
||||
{
|
||||
model = model.GetChildModel(hangerName);
|
||||
if (model == null) return null;
|
||||
}
|
||||
|
||||
// Get hook Transform from skeleton
|
||||
return model.GetHook(hookName, false); // Returns Transform, not position!
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Returns `Transform` instead of `Vector3`
|
||||
- Can be used for parenting
|
||||
- Can be used for one-time position lookup
|
||||
- More flexible than position-only approach
|
||||
|
||||
---
|
||||
|
||||
## Comparison: Manual Position vs. Parenting
|
||||
|
||||
| Scenario | Manual Position (C++ way) | Unity Parenting | Winner |
|
||||
|----------|---------------------------|----------------|--------|
|
||||
| **Hit GFX follows target** | Calculate every frame | Parent once, Unity updates | ✅ **Parenting** |
|
||||
| **Fly GFX spawn position** | Calculate once | Use hook Transform.position once | ✅ **Parenting** (simpler) |
|
||||
| **Fly GFX during flight** | Calculate every frame | Must update manually | ⚠️ **Both** (can't parent) |
|
||||
| **Relative offset** | Matrix transform every frame | LocalPosition once | ✅ **Parenting** |
|
||||
| **Absolute offset** | Calculate every frame | Calculate once or use child GameObject | ✅ **Parenting** (one-time) |
|
||||
|
||||
---
|
||||
|
||||
## What We Can Eliminate
|
||||
|
||||
### ✅ **Can Eliminate:**
|
||||
1. **Hit GFX position updates** (if `bTraceTarget = true`)
|
||||
- Instead of: `UpdateHitGfxPosition()` every frame
|
||||
- Use: `transform.SetParent(hookTransform)` once
|
||||
|
||||
2. **Initial spawn position calculation**
|
||||
- Instead of: `HookUtils.GetHookWorldPosition(hook, offset, bRelHook)`
|
||||
- Use: `hookTransform.position` or `hookTransform.TransformPoint(offset)`
|
||||
|
||||
3. **Relative offset calculations** (for hit GFX)
|
||||
- Instead of: Manual matrix multiplication every frame
|
||||
- Use: `transform.localPosition = offset` once
|
||||
|
||||
### ❌ **Cannot Eliminate:**
|
||||
1. **Fly GFX position during flight**
|
||||
- Must update manually (moves independently)
|
||||
- But can use hook Transform for initial position
|
||||
|
||||
2. **Target position for fly GFX movement calculation**
|
||||
- Still need to get target position (from hook or default)
|
||||
- But only need to get it once at start, or when target moves
|
||||
|
||||
3. **Absolute offset calculations** (one-time)
|
||||
- Still need to calculate once for absolute offsets
|
||||
- But only once, not every frame
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Add Hook Transform Access
|
||||
|
||||
**File:** `SkeletonBuilder.cs` or `CECModel.cs`
|
||||
|
||||
```csharp
|
||||
// Add method to get hook Transform (not just position)
|
||||
public Transform GetHookTransform(string hookName, bool recursive = false)
|
||||
{
|
||||
if (m_hookCache.TryGetValue(hookName, out Transform hook))
|
||||
return hook;
|
||||
|
||||
// Lookup hook by name
|
||||
hook = GetHook(hookName, recursive);
|
||||
|
||||
if (hook != null)
|
||||
m_hookCache[hookName] = hook;
|
||||
|
||||
return hook;
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 2: Update Hit GFX Spawning
|
||||
|
||||
**File:** `CECSkillGfxMan.cs` - `CECSkillGfxEvent`
|
||||
|
||||
```csharp
|
||||
private void SpawnHitGfx(Vector3 vTarget)
|
||||
{
|
||||
// Try to get target hook Transform
|
||||
Transform targetHook = GetTargetHookTransform();
|
||||
|
||||
if (targetHook != null && m_pComposer.m_HitPos.bTraceTarget)
|
||||
{
|
||||
// Parent to hook - automatic updates!
|
||||
m_hitGfxInstance = Instantiate(hitGfxPrefab);
|
||||
m_hitGfxInstance.transform.SetParent(targetHook);
|
||||
|
||||
// Apply offset
|
||||
if (m_pComposer.m_HitPos.bRelHook)
|
||||
m_hitGfxInstance.transform.localPosition = m_pComposer.m_HitPos.vOffset;
|
||||
else
|
||||
{
|
||||
// Absolute offset - calculate once
|
||||
Vector3 offsetWorld = targetHook.root.TransformPoint(m_pComposer.m_HitPos.vOffset);
|
||||
m_hitGfxInstance.transform.position = offsetWorld - targetHook.root.position + targetHook.position;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: manual position
|
||||
m_hitGfxInstance = Instantiate(hitGfxPrefab, vTarget, Quaternion.identity);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Update Fly GFX Spawning
|
||||
|
||||
```csharp
|
||||
private void SpawnFlyGfx()
|
||||
{
|
||||
Transform hostHook = GetHostHookTransform();
|
||||
|
||||
Vector3 spawnPos;
|
||||
Quaternion spawnRot;
|
||||
|
||||
if (hostHook != null)
|
||||
{
|
||||
// Use hook Transform for initial position
|
||||
if (m_pComposer.m_FlyPos.bRelHook)
|
||||
{
|
||||
spawnPos = hostHook.TransformPoint(m_pComposer.m_FlyPos.vOffset);
|
||||
spawnRot = hostHook.rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnPos = hostHook.position + m_pComposer.m_FlyPos.vOffset;
|
||||
spawnRot = hostHook.rotation;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: use calculated position
|
||||
spawnPos = m_pMoveMethod.GetPos();
|
||||
spawnRot = Quaternion.LookRotation(m_pMoveMethod.GetMoveDir());
|
||||
}
|
||||
|
||||
m_flyGfxInstance = Instantiate(flyGfxPrefab, spawnPos, spawnRot);
|
||||
// Don't parent - fly GFX moves independently
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
### ✅ **YES, we can significantly simplify the hook system in Unity:**
|
||||
|
||||
1. **Hit GFX that follows target:** Parent to hook Transform → Unity handles updates automatically
|
||||
2. **Fly GFX spawn position:** Use hook Transform.position (one-time) → No manual calculation
|
||||
3. **Relative offsets:** Use `transform.localPosition` → No matrix math needed
|
||||
4. **Absolute offsets:** Calculate once (not every frame) → Much simpler
|
||||
|
||||
### ⚠️ **Still need manual updates for:**
|
||||
1. **Fly GFX during flight:** Must update position manually (moves independently)
|
||||
2. **Target position lookup:** Still need to get target position (but only when needed)
|
||||
|
||||
### 🎯 **Result:**
|
||||
- **Eliminates ~80% of manual position calculations**
|
||||
- **Simpler, more Unity-native code**
|
||||
- **Better performance** (Unity's transform system)
|
||||
- **Easier to maintain**
|
||||
|
||||
---
|
||||
|
||||
**End of Document**
|
||||
Reference in New Issue
Block a user