diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkTrace.cs b/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkTrace.cs index dc8ee489a0..652d2799c3 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkTrace.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkTrace.cs @@ -386,9 +386,11 @@ namespace BrewMonster UnityGameSession.c2s_CmdSelectTarget(m_iObjectId); m_pHost.m_idSelTarget = m_iObjectId; } - if (m_pHost.AttackableJudge(m_iObjectId, m_bForceAttack) == 1) + // Duel: always send PVP/force mask when target is duel opponent so server accepts the attack + bool bUseForceAttack = m_bForceAttack || (m_pHost.IsInDuel() && m_iObjectId == m_pHost.GetDuelOpponentId()); + if (m_pHost.AttackableJudge(m_iObjectId, bUseForceAttack) == 1) { - byte byPVPMask = EC_Utility.glb_BuildPVPMask(m_bForceAttack); + byte byPVPMask = EC_Utility.glb_BuildPVPMask(bUseForceAttack); UnityGameSession.c2s_CmdNormalAttack(byPVPMask); m_pHost.m_bPrepareFight = true; bActionDone = true; diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_HostInputFilter.cs b/Assets/PerfectWorld/Scripts/Managers/EC_HostInputFilter.cs index e083a13eee..3177d02b80 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_HostInputFilter.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_HostInputFilter.cs @@ -148,6 +148,9 @@ namespace BrewMonster CECPlayer pPlayer = EC_ManMessageMono.Instance.EC_ManPlayer.GetPlayer(idObject); if (pPlayer != null && !pPlayer.IsDead()) { + // Duel: so AttackableJudge and server get PVP mask + if (IsInDuel() && idObject == m_pvp.idDuelOpp) + bForceAttack = true; // Already selected: allow trace (attack/talk). Or in duel: first click on duel opp = attack. bool alreadySelected = (m_idSelTarget == idObject); bool duelOppFirstClick = !alreadySelected && IsInDuel() && idObject == m_pvp.idDuelOpp && AttackableJudge(idObject, bForceAttack) == 1; @@ -314,6 +317,9 @@ namespace BrewMonster // In duel: first click on duel opp = attack (no prior select needed). if (pPlayer != null && !pPlayer.IsDead()) { + // Duel: so AttackableJudge and server get PVP mask + if (IsInDuel() && idObject == m_pvp.idDuelOpp) + bForceAttack = true; bool alreadySelected = (m_idSelTarget == idObject); bool duelOppFirstClick = !alreadySelected && IsInDuel() && idObject == m_pvp.idDuelOpp && AttackableJudge(idObject, bForceAttack) == 1; if (alreadySelected || duelOppFirstClick) @@ -382,6 +388,14 @@ namespace BrewMonster return; }*/ + // Duel fallback: if duel opp is already selected and we clicked (e.g. on ground or ray missed), treat as attack + if (idTraceTarget == 0 && IsInDuel() && m_idSelTarget == m_pvp.idDuelOpp && AttackableJudge(m_pvp.idDuelOpp, true) == 1) + { + idTraceTarget = m_pvp.idDuelOpp; + iTraceReason = CECHPWorkTrace.Trace_reason.TRACE_ATTACK; + bForceAttack = true; + } + // Handle trace target / Handle trace target if (idTraceTarget != 0) { @@ -393,6 +407,8 @@ namespace BrewMonster // Trace a object / Trace a object if (iTraceReason == CECHPWorkTrace.Trace_reason.TRACE_ATTACK) { + // So CanDo(CANDO_MELEE) passes: it requires m_idSelTarget set to the attack target + m_idSelTarget = idTraceTarget; if (!CanDo(ActionCanDo.CANDO_MELEE)) return; // When attacking duel opponent, send force/PVP so server accepts (origin: duel = attack but not kill) diff --git a/Assets/Scripts/CECHostPlayer.Combat.cs b/Assets/Scripts/CECHostPlayer.Combat.cs index dcdaa842df..438abe7b3b 100644 --- a/Assets/Scripts/CECHostPlayer.Combat.cs +++ b/Assets/Scripts/CECHostPlayer.Combat.cs @@ -1,4 +1,4 @@ -using BrewMonster.Managers; +using BrewMonster.Managers; using BrewMonster.Network; using BrewMonster.Scripts; using BrewMonster.Scripts.World; @@ -828,75 +828,16 @@ namespace BrewMonster } } } - // TO DO: fix later - //else if (GPDataTypeHelper.ISPLAYERID(idTarget)) - //{ - // // Check duel at first - // if (m_pvp.iDuelState == Duel_state.DUEL_ST_INDUEL && m_pvp.idDuelOpp == idTarget) - // return 1; - // else if (m_pvp.iDuelState == Duel_state.DUEL_ST_STOPPING && m_pvp.idDuelOpp == idTarget) - // return 0; - - // // In sanctuary we cannot attack other players - // if (m_bInSanctuary) - // return 0; - - // //ASSERT(pObject.GetClassID() == CECObject::OCID_ELSEPLAYER); - // EC_ElsePlayer pPlayer = (EC_ElsePlayer)pObject; - // ROLEBASICPROP bp = pPlayer.GetBasicProps(); - // EC_GAME_SETTING gs = g_pGame.GetConfigs().GetGameSettings(); - - // if (m_pvp.bFreePVP) - // { - // if (IsTeamMember(idTarget)) - // return 0; - - // // In free pvp mode, for example, host is in arena. - // if (bForceAttack) - // iRet = 1; - // else if (gs.bAtk_NoMafia && IsFactionMember(pPlayer.GetFactionID())) - // iRet = 0; - // else if (gs.bAtk_NoWhite && !pPlayer.IsInvader() && !pPlayer.IsPariah()) - // iRet = 0; - // else if (gs.bAtk_NoAlliance && g_pGame.GetFactionMan().IsFactionAlliance(pPlayer.GetFactionID())) - // iRet = 0; - // else if (gs.bAtk_NoForce && GetForce() > 0 && GetForce() == pPlayer.GetForce()) - // iRet = 0; - // else - // iRet = 1; - // } - // else if (m_iBattleCamp != GP_BATTLE_CAMP_NONE) - // { - // // Host is in battle - // int iCamp = pPlayer.GetBattleCamp(); - // if (iCamp != GP_BATTLE_CAMP_NONE && iCamp != m_iBattleCamp) - // iRet = 1; - // else - // iRet = 0; - // } - // else // Normal mode - // { - // if (IsTeamMember(idTarget)) - // return 0; - - // if (!IsPVPOpen() || !pPlayer.IsPVPOpen() || m_BasicProps.iLevel < EC_MAXNOPKLEVEL || bp.iLevel < EC_MAXNOPKLEVEL) - // iRet = 0; - // else if (bForceAttack) - // iRet = 1; - // else if (!gs.bAtk_Player) - // iRet = 0; - // else if (gs.bAtk_NoMafia && IsFactionMember(pPlayer.GetFactionID())) - // iRet = 0; - // else if (gs.bAtk_NoWhite && !pPlayer.IsInvader() && !pPlayer.IsPariah()) - // iRet = 0; - // else if (gs.bAtk_NoAlliance && g_pGame.GetFactionMan().IsFactionAlliance(pPlayer.GetFactionID())) - // iRet = 0; - // else if (gs.bAtk_NoForce && GetForce() > 0 && GetForce() == pPlayer.GetForce()) - // iRet = 0; - // else - // iRet = 1; - // } - //} + else if (GPDataTypeHelper.ISPLAYERID(idTarget)) + { + // Duel: allow attack only on duel opponent while in duel + if (IsInDuel() && m_pvp.idDuelOpp == idTarget) + return 1; + if (m_pvp.iDuelState == Duel_state.DUEL_ST_STOPPING && m_pvp.idDuelOpp == idTarget) + return 0; + // Other player PVP not implemented here; treat as not attackable + return 0; + } else { return -1; @@ -952,6 +893,10 @@ namespace BrewMonster else bForceAttack = iForceAtk > 0 ? true : false; + // Duel: server accepts attack on opponent only with PVP/force mask + if (IsInDuel() && idTarget == m_pvp.idDuelOpp) + bForceAttack = true; + if (AttackableJudge(idTarget, bForceAttack) != 1) return false; diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index 15e89333ac..2db325eeeb 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -19,6 +19,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using UnityEngine; using UnityEngine.UI; @@ -579,7 +580,6 @@ namespace BrewMonster case EC_MsgDef.MSG_HST_SELTARGET: OnMsgHstSelTarget(Msg); break; case EC_MsgDef.MSG_HST_USEITEM: - OnMsgHstUseItem(Msg); break; case EC_MsgDef.MSG_HST_ATKRESULT: OnMsgHstAttackResult(Msg); break; case EC_MsgDef.MSG_HST_ATTACKED: OnMsgHstAttacked(Msg); break; @@ -600,10 +600,7 @@ namespace BrewMonster case EC_MsgDef.MSG_HST_TARGETISFAR: OnMsgHstTargetIsFar(Msg); break; case EC_MsgDef.MSG_PM_PLAYERGATHER: OnMsgPlayerGather(Msg); break; case EC_MsgDef.MSG_HST_COOLTIMEDATA: OnMsgHstCoolTimeData(Msg); break; - case EC_MsgDef.MSG_HST_SETCOOLTIME: OnMsgHstSetCoolTime(Msg); break; case EC_MsgDef.MSG_HST_PRESSCANCEL: OnMsgHstPressCancel(Msg); break; - case EC_MsgDef.MSG_HST_LEARNSKILL: OnMsgHstLearnSkill(Msg); break; - case EC_MsgDef.MSG_HST_COMBO_SKILL_PREPARE: OnMsgComboSkillPrepare(Msg); break; case EC_MsgDef.MSG_PM_PLAYERFLY: OnMsgPlayerFly(Msg); break; case EC_MsgDef.MSG_HST_PETOPT: OnMsgHstPetOpt(Msg); break; case EC_MsgDef.MSG_HST_SETPLAYERLIMIT: OnMsgHstSetPlayerLimit(Msg); break; @@ -613,7 +610,7 @@ namespace BrewMonster case EC_MsgDef.MSG_HST_LEAVETEAM: OnMsgHstLeaveTeam(Msg); break; case EC_MsgDef.MSG_HST_NEWTEAMMEM: OnMsgHstNewTeamMem(Msg); break; case EC_MsgDef.MSG_HST_TEAMMEMBERDATA: OnMsgHstTeamMemberData(Msg); break; - case EC_MsgDef.MSG_HST_CONTINUECOMBOSKILL: OnMsgContinueComboSkill(Msg); break; + case EC_MsgDef.MSG_PM_DUELOPT: OnMsgHstDuelOpt(Msg); break; } /*if (bActionStartSkill) @@ -715,1179 +712,13 @@ namespace BrewMonster byte refuseBless = EC_Utility.glb_BuildRefuseBLSMask(); UnityGameSession.c2s_SendCmdNotifyForceAttack(glb_BuildPVPMask(bForceAttack), refuseBless); } - - private void OnMsgContinueComboSkill(ECMSG Msg) - { - bool bMeleeing = ((int)Msg.dwParam1 == 1); - if (bMeleeing != m_bMelee) bMeleeing = m_bMelee; - int iGroupID = (int)Msg.dwParam2; - if (m_pComboSkill != null && m_pComboSkill.GetGroupIndex() == iGroupID && !m_pComboSkill.IsStop()) - m_pComboSkill.Continue(bMeleeing); - } - - private void OnMsgComboSkillPrepare(ECMSG Msg) - { - cmd_combo_skill_prepare cmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - uint skillID = (uint)cmd.skill_id; - - ComboSkillState comboSkillState = new() - { - skillid = skillID, - arg = new int[ComboSkillState.MAX_COMBO_ARG] - }; - comboSkillState.arg = cmd.args; - /* if (cmd.args != null) - { - Array.Copy(cmd.args, comboSkillState.arg, Math.Min(cmd.args.Length, ComboSkillState.MAX_COMBO_ARG)); - }*/ - - Dictionary comboSkillList = ElementSkill.GetComboSkActivated(comboSkillState); - CECComboSkillState.Instance.SetComboSkillState(comboSkillList, ref comboSkillState); - } - - private void OnMsgHstLearnSkill(ECMSG Msg) - { - cmd_learn_skill pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - - CECSkill pSkill = GetNormalSkill(pCmd.skill_id); - if (pSkill != null) - { - if (pCmd.skill_level > 0) - { - pSkill.LevelUp(); - ElementSkill.SetLevel((uint)pCmd.skill_id, pCmd.skill_level); - } - else - { - RemoveNormalSkill(pCmd.skill_id); - } - } - else if (pCmd.skill_level > 0) - { - pSkill = new CECSkill(pCmd.skill_id, pCmd.skill_level); - if (pSkill == null) - { - Debug.Assert(pSkill != null); - return; - } - - if (!pSkill.GetJunior().Empty()) - { - ReplaceJuniorSkill(pSkill); - } - else - { - if (pSkill.GetType() != (int)CECSkill.SkillType.TYPE_PASSIVE && - pSkill.GetType() != (int)CECSkill.SkillType.TYPE_PRODUCE && - pSkill.GetType() != (int)CECSkill.SkillType.TYPE_LIVE) - m_aPtSkills.Add(pSkill); - else - m_aPsSkills.Add(pSkill); - } - - ElementSkill.SetLevel((uint)pCmd.skill_id, pCmd.skill_level); - } - - CECHostSkillModel.Instance.OnLearnSkill(pCmd.skill_id, pCmd.skill_level); - } - - private void OnMsgHstSetCoolTime(ECMSG Msg) - { - cmd_set_cooldown pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - - if (pCmd.cooldown_index < 0) - { - BMLogger.LogError("pCmd.cooldown_index >= 0 :" + (pCmd.cooldown_index >= 0)); - return; - } - - if (pCmd.cooldown_index < (int)CoolTimeIndex.GP_CT_MAX) - { - COOLTIME ct = m_aCoolTimes[pCmd.cooldown_index]; - ct.iCurTime = pCmd.cooldown_time; - ct.iMaxTime = pCmd.cooldown_time; - Math.Min(ct.iCurTime, ct.iMaxTime); - m_aCoolTimes[pCmd.cooldown_index] = ct; - if (pCmd.cooldown_index == (int)CoolTimeIndex.GP_CT_CAST_ELF_SKILL) - { - int i; - for (i = 0; i < m_aGoblinSkills.Count; i++) - { - if (m_aGoblinSkills[i] != null && m_aGoblinSkills[i].GetCoolingCnt() == 0) - { - int fakeRef = 0; - int coolTime = GetCoolTime((int)CoolTimeIndex.GP_CT_CAST_ELF_SKILL, out fakeRef); - m_aGoblinSkills[i].StartCooling(coolTime, coolTime); - } - } - - for (i = 0; i < m_aPsSkills.Count; i++) - { - CECSkill pSkill = GetPassiveSkillByIndex(i); - if (pSkill != null && (pSkill.GetCommonCoolDown() & (1 << (pCmd.cooldown_index - (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN0))) != 0) - { - int fakeRef = 0; - int coolTime = GetCoolTime(pCmd.cooldown_time, out fakeRef); - pSkill.StartCooling(coolTime, coolTime); - } - } - } - - if (pCmd.cooldown_index >= (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN0 && pCmd.cooldown_index <= (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN4) - { - // other player skills should be set public cool down too. - uint mask = (uint)(1 << (pCmd.cooldown_index - (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN0)); - for (int i = 0; i < GetPositiveSkillNum(); i++) - { - CECSkill pSkill = GetPositiveSkillByIndex(i); - int fakeRef = 0; - if (pSkill != null && (pSkill.GetCommonCoolDown() & mask) != 0) - { - int coolTime = GetCoolTime(pCmd.cooldown_index, out fakeRef); - pSkill.StartCooling(coolTime, coolTime); - //pSkill.StartCooling(GetCoolTime(pCmd.cooldown_index, out fakeRef), GetCoolTime(pCmd.cooldown_index, out fakeRef)); - } - } - /*const std::map&inherentSkillMap = CECComboSkillState::Instance().GetInherentSkillMap(); - std::map < unsigned int, CECSkill*>::const_iterator it; - for (it = inherentSkillMap.begin(); it != inherentSkillMap.end(); ++it) - { - it.second.StartCooling(GetCoolTime(pCmd.cooldown_index), GetCoolTime(pCmd.cooldown_index)); - }*/ - } - } - else if (pCmd.cooldown_index > (int)CoolTimeIndex.GP_CT_SKILL_START) - { - int idSkill = pCmd.cooldown_index - (int)CoolTimeIndex.GP_CT_SKILL_START; - - COOLTIME ct; - if (!m_skillCoolTime.TryGetValue(idSkill, out ct)) - { - // Key doesn't exist, create new entry - ct = new COOLTIME(); - m_skillCoolTime[idSkill] = ct; - } - ct.iCurTime = pCmd.cooldown_time; - ct.iMaxTime = pCmd.cooldown_time; - ct.iCurTime = Math.Clamp(ct.iCurTime, 0, ct.iMaxTime); - m_skillCoolTime[idSkill] = ct; - //Math.Clamp(ct.iCurTime, 0, ct.iMaxTime); - - CECSkill pSkill = GetNormalSkill(idSkill); - if (pSkill != null) - { - pSkill.StartCooling(pCmd.cooldown_time, pCmd.cooldown_time); - } - /* else if (pSkill = CECComboSkillState::Instance().GetInherentSkillByID(idSkill)) - { - pSkill.StartCooling(pCmd.cooldown_time, pCmd.cooldown_time); - }*/ - else if (GetEquipSkillByID(idSkill) == null) - { - BMLogger.LogError("HoangDev: pSkill " + pSkill); - } - else - { - pSkill = CECComboSkillState.Instance.GetInherentSkillByID((uint)idSkill); - if (pSkill != null) - { - pSkill.StartCooling(pCmd.cooldown_time, pCmd.cooldown_time); - } - else if (GetEquipSkillByID(idSkill) == null) - { - Debug.LogWarning($"OnMsgHstSetCoolTime: Skill {idSkill} not found in nomal/equip skills"); - } - } - } - else - { - // This is a annoying assert and mean nothing, so we ignore it. - // ASSERT(0); - } - - UpdateEquipSkillCoolDown(pCmd.cooldown_index); - } - - public CECSkill GetPassiveSkillByIndex(int n) - { - return m_aPsSkills[n]; - } - - private void OnMsgHstCoolTimeData(ECMSG Msg) - { - cmd_cooltime_data pCmd = default; - var data = (byte[])Msg.dwParam1; - pCmd.count = GPDataTypeHelper.FromBytes(data, 0); - long offset = Marshal.SizeOf(typeof(ushort)); - pCmd.list = new item_t[pCmd.count]; - for (int i = 0; i < pCmd.count; i++) - { - pCmd.list[i] = GPDataTypeHelper.FromBytes(data, ref offset); - } - - m_skillCoolTime.Clear(); - - for (int i = 0; i < pCmd.count; i++) - { - item_t item = pCmd.list[i]; - if (item.idx > (int)CoolTimeIndex.GP_CT_SKILL_START) - { - // Is skill cool time - int idSkill = item.idx - (int)CoolTimeIndex.GP_CT_SKILL_START; - - COOLTIME ct = default; - ct.iCurTime = item.cooldown; - ct.iMaxTime = item.max_cooltime; - Mathf.Clamp(ct.iCurTime, 0, ct.iMaxTime); - - CECSkill pSkill = GetNormalSkill(idSkill); - if (pSkill != null) - { - pSkill.StartCooling(item.max_cooltime, item.cooldown); - } - /* else if (pSkill = CECComboSkillState::Instance().GetInherentSkillByID(idSkill)) - { - pSkill.StartCooling(item.max_cooltime, item.cooldown); - }*/ - else if (GetEquipSkillByID(idSkill) == null) - { - // Add to goblin skill list - pSkill = new CECSkill(idSkill, 1); - pSkill.StartCooling(item.max_cooltime, item.cooldown); - m_aGoblinSkills.Add(pSkill); - } - m_skillCoolTime[idSkill] = ct; - } - else if (item.idx >= 0 && item.idx < (int)CoolTimeIndex.GP_CT_MAX) - { - // Other cool time - COOLTIME ct = m_aCoolTimes[item.idx]; - ct.iCurTime = item.cooldown; - ct.iMaxTime = item.max_cooltime; - Mathf.Clamp(ct.iCurTime, 0, ct.iMaxTime); - - if (item.idx >= (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN0 && item.idx <= (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN4) - { - // other player skills should be set public cool down too. - uint mask = (uint)(1 << (item.idx - (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN0)); - for (int y = 0; y < GetPositiveSkillNum(); y++) - { - CECSkill pSkill = GetPositiveSkillByIndex(y); - if (pSkill != null && ((pSkill.GetCommonCoolDown() & mask) != 0)) - pSkill.StartCooling(item.max_cooltime, item.cooldown); - } - } - } - else - { - BMLogger.LogError("item.idx >= 0: " + (item.idx >= 0)); - } - } - - UpdateEquipSkillCoolDown(); - } - - private void OnMsgEnchantResult(ECMSG msg) - { - // 从消息中获取cmd_enchant_result结构 // Get cmd_enchant_result structure from message - cmd_enchant_result pCmd = GPDataTypeHelper.FromBytes((byte[])msg.dwParam1); - - // 初始化掩码为全1 // Initialize mask to all 1s - uint mask = 0xFFFFFFFF; - - // 如果目标不是主机玩家且当前不是主机玩家,则过滤会引起气泡文本的修饰符 - // We should filter out these things that will cause bubble texts - CECHostPlayer pHost = EC_ManMessageMono.Instance.GetECManPlayer.GetHostPlayer(); - if (pCmd.target != pHost.GetCharacterID() && !IsHostPlayer()) - { - mask &= (uint)(MOD.MOD_PHYSIC_ATTACK_RUNE | MOD.MOD_MAGIC_ATTACK_RUNE | - MOD.MOD_CRITICAL_STRIKE | MOD.MOD_ENCHANT_FAILED); - } - - // 获取修饰符 // Get modifier - uint dwModifier = (uint)pCmd.attack_flag; - - // 获取技能类型 // Get skill type - int nDamage = -2; // 默认为-2,不会引起受伤动作 // Default to -2, will not cause wounded action - - if (ElementSkill.GetType((uint)pCmd.skill) == (byte)skill_type.TYPE_ATTACK) - { - // 只有攻击技能会引起受伤动作,伤害值为-1 // Only attack skill will cause wounded action, when damage is -1 - nDamage = -1; - } - else - { - // 其他技能不会引起受伤动作,伤害值设为-2 // Other skills will not cause wounded action, so we set damage to -2 - nDamage = -2; - } - - // 播放攻击效果 // Play attack effect - int attackTime = 0; - PlayAttackEffect(pCmd.target, pCmd.skill, pCmd.level, nDamage, - dwModifier & mask, 0, ref attackTime, pCmd.section); - } - - private void OnMsgPlayerCastSkill(ECMSG Msg) - { - - bool bDoOtherThing = false; - int idTarget = 0; - - bool bActionStartSkill = false; - int iActionTime = 1000; - CECPlayerWrapper pWrapper = CECAutoPolicy.GetInstance().GetPlayerWrapper(); - - switch (Convert.ToInt32(Msg.dwParam2)) - { - case CommandID.OBJECT_CAST_SKILL: - { - cmd_object_cast_skill pCmd = - GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - - if (m_pCurSkill != null) - { - m_pCurSkill.EndCharging(); - } - m_pCurSkill = GetPositiveSkillByID(pCmd.skill); - if (m_pCurSkill == null) m_pCurSkill = GetEquipSkillByID(pCmd.skill); - if (m_pCurSkill == null) - m_pCurSkill = CECComboSkillState.Instance.GetInherentSkillByID((uint)pCmd.skill); - if (m_pCurSkill == null) - { - Debug.Assert(m_pCurSkill != null, "Current skill should not be null"); - return; - } - - if (m_pCurSkill.IsChargeable()) - m_pCurSkill.StartCharging(pCmd.time); - - int iWaitTime = -1; - if (m_pCurSkill.GetExecuteTime() >= 0) - iWaitTime = pCmd.time + m_pCurSkill.GetExecuteTime(); - - CECHPWorkSpell pWork = (CECHPWorkSpell)m_pWorkMan.CreateWork(Host_work_ID.WORK_SPELLOBJECT); - - pWork.PrepareCast(pCmd.target, m_pCurSkill, iWaitTime); - m_pWorkMan.StartWork_p1(pWork); - - // Start time counter for some type skill - // 为某些类型的技能启动时间计数器 - if (!m_pCurSkill.IsChargeable()) - { - int iTime = pCmd.time; - if (iTime < 10) iTime = 10; // a_ClampFloor - m_IncantCnt.SetPeriod(iTime); - m_IncantCnt.Reset(); - } - else - { - // make sure the counter is correct shown - // 确保计数器正确显示 - m_IncantCnt.Reset(true); - } - - m_bSpellDSkill = false; - - TurnFaceTo(pCmd.target); - - m_idCurSkillTarget = pCmd.target; - PlaySkillCastAction(m_pCurSkill.GetSkillID()); - - bActionStartSkill = true; - iActionTime = iWaitTime; - - // Special logging for return-to-town skill (167) - // 回城技能(167)的特殊日志 - if (m_pCurSkill.GetSkillID() == ID_RETURNTOWN_SKILL) - { - Debug.Log($"Return-to-town skill (167) cast - State2 should trigger SetReturntown(1) on server"); - } - break; - } - case CommandID.SKILL_PERFORM: - { - // Skill perform - // 技能执行 - m_pPrepSkill = null; - - if (m_pCurSkill != null && m_pCurSkill.IsDurative()) - m_bSpellDSkill = true; - - // Special handling for return-to-town skill (167) - // 回城技能(167)的特殊处理 - // When skill 167 reaches State2, server calls SetReturntown(1) which should trigger MSG_HST_GOTO - // 当技能167到达State2时,服务器调用SetReturntown(1),这应该触发MSG_HST_GOTO - if (m_pCurSkill != null && m_pCurSkill.GetSkillID() == ID_RETURNTOWN_SKILL) - { - Debug.Log($"Skill 167 (Return to Town) performed - waiting for MSG_HST_GOTO from server"); - } - - break; - } - case CommandID.HOST_STOP_SKILL: - { - m_pPrepSkill = null; - - CECSkill pSkillToMatch = m_pCurSkill; - if (m_pCurSkill != null) - { - ClearComActFlagAllRankNodes(true); - - if (((m_pComboSkill != null && !m_pComboSkill.IsStop()) || - m_pCurSkill.ChangeToMelee()) && !m_pWorkMan.HasDelayedWork()) - { - bDoOtherThing = true; - idTarget = m_idCurSkillTarget; - } - - m_pCurSkill.EndCharging(); - m_pCurSkill = null; - } - - AP.AP_ActionEvent((int)AP_EVENT.AP_EVENT_STOPSKILL); - if (pSkillToMatch != null) - { - // m_pWorkMan中的当前任何Work为最高优先级或、或 CECHPWorkSpell 处于最高优先级且队列中有Delay时间 - // 此时此处无法准确匹配,而导致的 CECHPWorkSpell 执行时将落后于其它 CECHPWorkSpell 执行期间将无法响应 - // 在这种方法执行完成后,我们使用回城机制来 - // If the current Work in m_pWorkMan is the highest priority or CECHPWorkSpell is at the highest priority and there is Delay time in the queue - // At this point, it cannot be accurately matched, causing the CECHPWorkSpell execution to lag behind other CECHPWorkSpell execution and cannot respond - // After this method is executed, we use the return to city mechanism - m_pWorkMan.FinishWork(new CECHPWorkSpellMatcher(pSkillToMatch)); - } - - StopSkillAttackAction(); - - m_idCurSkillTarget = 0; - break; - } - case CommandID.SELF_SKILL_INTERRUPTED: - { - // Skill interrupted - // 技能被打断 - int skill_id = 0; - m_pPrepSkill = null; - - CECSkill - pSkillToMatch = m_pCurSkill; // 保存指针值,用在后面函数调用中 | Save pointer value for later function call - if (m_pCurSkill != null) - { - skill_id = m_pCurSkill.GetSkillID(); - - ClearComActFlagAllRankNodes(false); - - if (((m_pComboSkill != null && !m_pComboSkill.IsStop()) || - m_pCurSkill.ChangeToMelee()) && !m_pWorkMan.HasDelayedWork()) - { - bDoOtherThing = true; - idTarget = m_idCurSkillTarget; - } - - m_pCurSkill.EndCharging(); - m_pCurSkill = null; - } - - m_idCurSkillTarget = 0; - - - if (pSkillToMatch != null) - { - // 逻辑同 HOST_STOP_SKILL 分支处理 | Logic same as HOST_STOP_SKILL branch - m_pWorkMan.FinishWork(new CECHPWorkSpellMatcher(pSkillToMatch)); - } - - StopSkillCastAction(); - - // Print a notify message - // 打印提示消息 - //EC_Game.GetGameRun().AddFixedMessage(FIXMSG_SKILLINTERRUPT); - - AP.AP_ActionEvent((int)AP_EVENT.AP_EVENT_STOPSKILL); - - // 通知策略技能被打断 | Notify policy that skill is interrupted - CECAutoPolicy.GetInstance().SendEvent_SkillInterrupt(skill_id); - break; - } - case CommandID.OBJECT_CAST_INSTANT_SKILL: - { - // Cast instant skill - // 施放即时技能 - cmd_object_cast_instant_skill pCmd = - GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - Debug.Assert(pCmd.caster == m_PlayerInfo.cid); - - CECSkill pSkill = GetPositiveSkillByID(pCmd.skill); - if (pSkill == null) pSkill = GetEquipSkillByID(pCmd.skill); - if (pSkill == null) - { - Debug.Assert(pSkill != null, "Skill should not be null"); - return; - } - - if (pCmd.target != 0 && pCmd.target != m_PlayerInfo.cid) - TurnFaceTo(pCmd.target); - - PlaySkillCastAction(pSkill.GetSkillID()); - bActionStartSkill = true; - break; - } - case CommandID.OBJECT_CAST_POS_SKILL: - { - // Cast position skill (target position instead of target object) - // 施放位置技能(目标位置而不是目标对象) - cmd_object_cast_pos_skill pCmd = - GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - Debug.Assert(pCmd.caster == m_PlayerInfo.cid); - - CECSkill pSkill = GetNormalSkill(pCmd.skill); - if (pSkill == null) pSkill = GetEquipSkillByID(pCmd.skill); - if (pSkill == null) - { - Debug.Assert(pSkill != null, "Skill should not be null"); - break; - } - - TurnFaceTo(pCmd.target); - - if (pSkill.GetRangeType() != (int)CECSkill.RangeType.RANGE_SLEF && - pSkill.GetRangeType() != (int)CECSkill.RangeType.RANGE_SELFSPHERE && - (pSkill.GetSkillID() == 1095 || // 瞬影瞬斩 | Shadow instant slash - pSkill.GetSkillID() == 1278 || // 魔·瞬影瞬斩 | Demon Shadow instant slash - pSkill.GetSkillID() == 1279 || // 妖瞬影瞬斩 | Monster Shadow instant slash - pSkill.GetSkillID() == 2313)) - { - A3DVECTOR3 vPos = pCmd.pos; - if (!IsPosCollideFree(vPos)) - { - // Add a little height to ensure player's AABB won't embed with building - // 添加一点高度以确保玩家的AABB不会嵌入建筑物 - vPos += new A3DVECTOR3(0, 1, 0) * 0.1f; - } - - // Ensure we are not under ground - // 确保我们不在地下 - A3DVECTOR3 vNormal = new A3DVECTOR3(); - float vTerrainHeight = CECWorld.Instance.GetTerrainHeight(vPos, ref vNormal); - if (vPos.y < vTerrainHeight) - vPos.y = vTerrainHeight; - - SetPos(EC_Utility.ToVector3(vPos)); - - m_CDRInfo.vTPNormal = vPos.y <= vTerrainHeight + 0.1f ? vNormal : new A3DVECTOR3(0); - m_CDRInfo.fYVel = 0.0f; - m_CDRInfo.vAbsVelocity = new A3DVECTOR3(0); - ResetJump(); - - m_MoveCtrl.SetHostLastPos(vPos); - m_MoveCtrl.SetLastSevPos(vPos); - - // Update camera - // 更新相机 - //UpdateFollowCamera(false, 10); - } - else - { - CECHPWorkFMove pWork = - (CECHPWorkFMove)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_FLASHMOVE); - - // 检查技能执行时间,防止出现浮点数0,出现错误 | Check skill execute time to prevent float 0 error - int nExecuteTime = pSkill.GetExecuteTime(); - if (nExecuteTime < 50) nExecuteTime = 50; // a_ClampFloor - - if (pSkill.GetSkillID() == 1145 || // 刺客的瞬移技能,需要增加地面效果1145:百步穿杨瞬斩 | Assassin teleport skill - pSkill.GetSkillID() == 1314 || // 魔·百步穿杨瞬斩 | Demon hundred steps instant slash - pSkill.GetSkillID() == 1315 || // 妖·百步穿杨瞬斩 | Monster hundred steps instant slash - pSkill.GetSkillID() == 1362 || // 影遁回杨 | Shadow escape return - pSkill.GetSkillID() == 1690 || // 影遁魔·回杨 | Shadow escape demon return - pSkill.GetSkillID() == 1691 || // 影遁妖回杨 | Shadow escape monster return - pSkill.GetSkillID() == 1845 || // 星湖之狐·瞬移技能 | Star Lake Fox teleport - pSkill.GetSkillID() == 1844 || // 星湖之狐·瞬移技能 | Star Lake Fox teleport - pSkill.GetSkillID() == 1815 || // 妖精 飞 | Fairy fly - pSkill.GetSkillID() == 2272 || // 一步登天 | One step to heaven - pSkill.GetSkillID() == 2315 || // 百步穿杨瞬斩 | Hundred steps instant slash - pSkill.GetSkillID() == 2285 || // 回杨 | Return - pSkill.GetSkillID() == 2340 || // 突袭 | Raid - pSkill.GetSkillID() == 2341 || // 突袭 | Raid - pSkill.GetSkillID() == 2342 || // 突袭 | Raid - pSkill.GetSkillID() == 2553 || // 飞箭雨 | Arrow rain - pSkill.GetSkillID() == 2740 || // 魔·飞箭雨 | Demon arrow rain - pSkill.GetSkillID() == 2741 || // 妖飞箭雨 | Monster arrow rain - pSkill.GetSkillID() == 2559 || // 闪电 | Lightning - pSkill.GetSkillID() == 2752 || // 魔·闪电 | Demon lightning - pSkill.GetSkillID() == 2753) // 妖闪电 | Monster lightning - { - A3DVECTOR3 vPos = pCmd.pos; - if (!IsPosCollideFree(vPos)) - { - // Add a little height to ensure player's AABB won't embed with building - // 添加一点高度以确保玩家的AABB不会嵌入建筑物 - vPos += new A3DVECTOR3(0, 1, 0) * 0.1f; - } - - // Ensure we are not under ground - // 确保我们不在地下 - A3DVECTOR3 vNormal = new A3DVECTOR3(); - float vTerrainHeight = CECWorld.Instance.GetTerrainHeight(vPos, ref vNormal); - if (vPos.y < vTerrainHeight) - vPos.y = vTerrainHeight; - - m_CDRInfo.vTPNormal = vPos.y <= vTerrainHeight + 0.1f ? vNormal : new A3DVECTOR3(0); - m_CDRInfo.fYVel = 0.0f; - m_CDRInfo.vAbsVelocity = new A3DVECTOR3(0); - ResetJump(); - - pWork.PrepareMove(pCmd.pos, nExecuteTime * 0.001f, pSkill.GetSkillID()); - } - else - { - if (pSkill.GetRangeType() == (int)CECSkill.RangeType.RANGE_SLEF || - pSkill.GetRangeType() == (int)CECSkill.RangeType.RANGE_SELFSPHERE) - { - m_CDRInfo.vTPNormal = m_MoveCtrl.m_vFlashTPNormal; - } - - pWork.PrepareMove(pCmd.pos, nExecuteTime * 0.001f, 0); - } - - m_pWorkMan.StartWork_p2(pWork); - iActionTime = nExecuteTime; - } - - bActionStartSkill = true; - break; - } - case CommandID.PLAYER_CAST_RUNE_SKILL: - { - // Cast rune skill (item skill) - // 施放符文技能(物品技能) - cmd_player_cast_rune_skill pCmd = - GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - Debug.Assert(pCmd.caster == m_PlayerInfo.cid); - - if (m_pTargetItemSkill != null) - { - // Delete old target item skill - m_pTargetItemSkill = null; - } - - m_pTargetItemSkill = new CECSkill(pCmd.skill, pCmd.level); - if (m_pTargetItemSkill == null) - { - Debug.Assert(m_pTargetItemSkill != null, "Target item skill should not be null"); - return; - } - - m_pCurSkill = m_pTargetItemSkill; - - if (m_pCurSkill.IsChargeable()) - m_pCurSkill.StartCharging(pCmd.time); - - int iWaitTime = -1; - if (m_pCurSkill.GetExecuteTime() >= 0) - iWaitTime = pCmd.time + m_pCurSkill.GetExecuteTime(); - - CECHPWorkSpell pWork = - (CECHPWorkSpell)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_SPELLOBJECT); - pWork.PrepareCast(pCmd.target, m_pCurSkill, iWaitTime); - m_pWorkMan.StartWork_p1(pWork); - - // Start time counter for some type skill - // 为某些类型的技能启动时间计数器 - if (!m_pCurSkill.IsChargeable()) - { - int iTime = pCmd.time; - if (iTime < 10) iTime = 10; // a_ClampFloor - m_IncantCnt.SetPeriod(iTime); - m_IncantCnt.Reset(); - } - else - { - // make sure the counter is correct shown - // 确保计数器正确显示 - m_IncantCnt.Reset(true); - } - - m_bSpellDSkill = false; - - TurnFaceTo(pCmd.target); - - m_idCurSkillTarget = pCmd.target; - PlaySkillCastAction(m_pCurSkill.GetSkillID()); - - bActionStartSkill = true; - iActionTime = iWaitTime; - Debug.Log($"Cast rune skill({m_pCurSkill.GetSkillID()})"); - break; - } - case CommandID.PLAYER_CAST_RUNE_INSTANT_SKILL: - { - // Cast instant rune skill - // 施放即时符文技能 - cmd_player_cast_rune_instant_skill pCmd = - GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - Debug.Assert(pCmd.caster == m_PlayerInfo.cid); - - if (m_pTargetItemSkill != null) - { - // Delete old target item skill - m_pTargetItemSkill = null; - } - - m_pTargetItemSkill = new CECSkill(pCmd.skill, pCmd.level); - if (m_pTargetItemSkill == null) - { - Debug.Assert(m_pTargetItemSkill != null, "Target item skill should not be null"); - return; - } - - if (pCmd.target != 0 && pCmd.target != m_PlayerInfo.cid) - TurnFaceTo(pCmd.target); - - PlaySkillCastAction(m_pTargetItemSkill.GetSkillID()); - bActionStartSkill = true; - break; - } - case CommandID.ERROR_MESSAGE: - bDoOtherThing = true; - break; - - default: - Debug.Assert(false, "Unknown message type in OnMsgPlayerCastSkill"); - break; - } - - if (bActionStartSkill) - AP.AP_ActionEvent((int)AP_EVENT.AP_EVENT_STARTSKILL, iActionTime); - - if (bDoOtherThing) - { - if (m_pComboSkill != null && !m_pComboSkill.IsStop()) - { - // Continue combo skill - // 继续连击技能 - if (CECAutoPolicy.GetInstance().IsAutoPolicyEnabled()) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CONTINUECOMBOSKILL, MANAGER_INDEX.MAN_PLAYER, 0, 0, m_pComboSkill.GetGroupIndex()); - else - m_pComboSkill.Continue(false); - } - else - { - if (idTarget != 0 && idTarget != m_PlayerInfo.cid) - NormalAttackObject(idTarget, true); - } - } - } - - private void OnMsgHstSkillResult(ECMSG Msg) - { - cmd_host_skill_attack_result pCmd = - GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - int refFake = 0; - PlayAttackEffect(pCmd.idTarget, pCmd.idSkill, 0, pCmd.iDamage, (uint)pCmd.attack_flag, - pCmd.attack_speed * 50, ref refFake, pCmd.section); - } - - private void OnMsgHstSkillData(ECMSG Msg) - { - cmd_skill_data pCmd = default; - pCmd.skill_count = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - int offset = sizeof(uint); - int skillSize = Marshal.SizeOf(); - pCmd.skill_list = new cmd_skill_data.SKILL[pCmd.skill_count]; - for (int i = 0; i < pCmd.skill_count; i++) - { - pCmd.skill_list[i] = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1, offset); - offset += skillSize; - } - - if (pCmd.skill_list == null) - { - BMLogger.LogError("OnMsgHstSkillData: cmd is null"); - return; - } - - List skillSCConfigArray1 = new List(); - List skillSCConfigArray2 = new List(); - List skillGrpSCConfigArray1 = new List(); - List skillGrpSCConfigArray2 = new List(); - - if (HostIsReady()) - { - m_pWorkMan.FinishWork(new CECHPTraceSpellMatcher()); - m_pPrepSkill = null; - m_pCurSkill = null; - ClearComboSkill(); - SaveSkillShortcut(skillSCConfigArray1, m_aSCSets1, (int)Shortcut.NUM_HOSTSCSETS1); - SaveSkillShortcut(skillSCConfigArray2, m_aSCSets2, (int)Shortcut.NUM_HOSTSCSETS2); - SaveSkillGrpShortcut(skillGrpSCConfigArray1, m_aSCSets1, (int)Shortcut.NUM_HOSTSCSETS1); - SaveSkillGrpShortcut(skillGrpSCConfigArray2, m_aSCSets2, (int)Shortcut.NUM_HOSTSCSETS2); - /* - for (int i = 0; i < HostConstants.NUM_HOSTSCSETS1; i++) - { - if (hostPlayer.m_aSCSets1[i] != null) - { - hostPlayer.m_aSCSets1[i].RemoveSkillShortcuts(); - } - } - - for (int i = 0; i < HostConstants.NUM_HOSTSCSETS2; i++) - { - if (hostPlayer.m_aSCSets2[i] != null) - { - hostPlayer.m_aSCSets2[i].RemoveSkillShortcuts(); - } - }*/ - - // Release passive skills - m_aSCSets1 = new CECShortcutSet[HostCfgConstants.NUM_HOSTSCSETS1]; - m_aSCSets2 = new CECShortcutSet[HostCfgConstants.NUM_HOSTSCSETS2]; - m_aPtSkills.Clear(); - m_aPsSkills.Clear(); - } - - // Load skill data from command - // C++: GNET::ElementSkill::LoadSkillData(pCmd); - ElementSkill.LoadSkillData(pCmd); - // Create skill objects from command data - for (int i = 0; i < pCmd.skill_count; i++) - { - cmd_skill_data.SKILL data = pCmd.skill_list[i]; - CECSkill skill = new CECSkill(data.id_skill, data.level); - - // Categorize skills into positive and passive - int skillType = skill.GetType(); - if (skillType != (int)CECSkill.SkillType.TYPE_PASSIVE && - skillType != (int)CECSkill.SkillType.TYPE_PRODUCE && - skillType != (int)CECSkill.SkillType.TYPE_LIVE) - { - m_aPtSkills.Add(skill); - } - else - { - m_aPsSkills.Add(skill); - } - } - // Restore and convert shortcuts after loading new skills - if (HostIsReady()) - { - ConvertSkillShortcut(skillSCConfigArray1); - AssignSkillShortcut(skillSCConfigArray1, m_aSCSets1); - ConvertSkillShortcut(skillSCConfigArray2); - AssignSkillShortcut(skillSCConfigArray2, m_aSCSets2); - ConvertComboSkill(); - ValidateSkillGrpShortcut(skillGrpSCConfigArray1); - AssignSkillGrpShortcut(skillGrpSCConfigArray1, m_aSCSets1); - ValidateSkillGrpShortcut(skillGrpSCConfigArray2); - AssignSkillGrpShortcut(skillGrpSCConfigArray2, m_aSCSets2); - } - - if (HostIsReady()) - { - // Update UI when profession changes, save all shortcut configurations - // to remove effects from intermediate skills (invalid pointers) - // C++: CECGameUIMan *pGameUIMan = g_pGame.GetGameRun().GetUIManager().GetInGameUIMan(); - // pGameUIMan.UpdateSkillRelatedUI(); - CECUIManager.Instance.UpdateSkillRelatedUI(); - } - } - public void AssignSkillGrpShortcut(List skillGrpSCConfigArray, CECShortcutSet[] aSCSets) - { - for (int i = 0; i < skillGrpSCConfigArray.Count; i++) - { - SkillGrpShortCutConfig cfg = skillGrpSCConfigArray[i]; - - if (cfg.groupIndex != -1) - { - // C++ không check null; nếu muốn an toàn hơn thì check aSCSets[cfg.setNum] != null - aSCSets[cfg.setNum].CreateSkillGroupShortcut(cfg.slotNum, cfg.groupIndex); - } - } - } - public void ValidateSkillGrpShortcut(List skillGrpSCConfigArray) - { - for (int i = 0; i < skillGrpSCConfigArray.Count; i++) - { - // C++ đang lấy VideoSettings trong mỗi vòng lặp (giữ nguyên hành vi) - EC_VIDEO_SETTING vs = EC_Game.GetConfigs().GetVideoSettings(); - - SkillGrpShortCutConfig cfg = skillGrpSCConfigArray[i]; - - if (vs.comboSkill[cfg.groupIndex].nIcon == 0) - cfg.groupIndex = -1; // -1 biểu thị shortcut combo-skill không hợp lệ - - skillGrpSCConfigArray[i] = cfg; // cần nếu SkillGrpShortCutConfig là struct - } - } - - public void ConvertComboSkill() - { - EC_VIDEO_SETTING vs = EC_Game.GetConfigs().GetVideoSettings(); - - for (int i = 0; i < EC_ConfigConstants.EC_COMBOSKILL_NUM; i++) - { - if (vs.comboSkill[i].nIcon == 0) - continue; - - for (int j = 0; j < EC_ConfigConstants.EC_COMBOSKILL_LEN; j++) - { - int oldSkillId = vs.comboSkill[i].idSkill[j]; - if (oldSkillId == 0) - continue; - - int convertedSkillId = CECSkillConvert.Instance.GetConvertSkill(oldSkillId); - int newSkillId = (convertedSkillId == 0) ? oldSkillId : convertedSkillId; - - // C++: nếu skill tồn tại, hoặc là -1/-2 (loop / normal attack shortcut) thì giữ - if (GetNormalSkill(newSkillId) != null || newSkillId == -1 || newSkillId == -2) - { - vs.comboSkill[i].idSkill[j] = (short)newSkillId; - } - else - { - // Không hợp lệ -> clear combo theo oldSkillId, icon về 0, thoát vòng lặp j - vs.comboSkill[i].Clear(oldSkillId); - vs.comboSkill[i].nIcon = 0; - break; - } - } - } - - EC_Game.GetConfigs().SetVideoSettings(vs); - } - public void AssignSkillShortcut(List skillSCConfigArray, CECShortcutSet[] aSCSets) - { - for (int i = 0; i < skillSCConfigArray.Count; i++) - { - SkillShortCutConfig cfg = skillSCConfigArray[i]; - if (cfg.skillId == 0) - { - BMLogger.LogError("AssignSkillShortcut: skillId is zero"); - } - CECSkill convertSkill = GetNormalSkill(cfg.skillId); - if (convertSkill != null) - { - // C++ không check null set => nếu muốn y hệt thì bỏ check này - if (aSCSets[cfg.setNum] != null) - { - aSCSets[cfg.setNum].CreateSkillShortcut(cfg.slotNum, convertSkill); - } - } - } - } - public void ConvertSkillShortcut(List skillSCConfigArray) - { - for (int i = 0; i < skillSCConfigArray.Count; i++) - { - if (skillSCConfigArray[i].skillId == 0) - { - BMLogger.LogError("ConvertSkillShortcut: skillId is zero"); - } - - int oldSkillId = skillSCConfigArray[i].skillId; - int convertSkillId = CECSkillConvert.Instance.GetConvertSkill(oldSkillId); - - // it->skillId = (convertSkillId == 0) ? it->skillId : convertSkillId; - SkillShortCutConfig cfg = skillSCConfigArray[i]; - cfg.skillId = (convertSkillId == 0) ? oldSkillId : convertSkillId; - skillSCConfigArray[i] = cfg; // needed if SkillShortCutConfig is a struct - } - } - public void SaveSkillGrpShortcut( - List skillGrpSCConfigArray, - CECShortcutSet[] aSCSets, - int count) - { - for (int i = 0; i < count; i++) - { - if (aSCSets[i] == null) continue; - - for (int j = 0; j < aSCSets[i].GetShortcutNum(); j++) - { - CECShortcut pSC = aSCSets[i].GetShortcut(j); - if (pSC == null || pSC.GetType() != (int)ShortcutType.SCT_SKILLGRP) - continue; - - // C-style cast -> safe cast - if (pSC is not CECSCSkillGrp pSkillGrpSC) - continue; - - SkillGrpShortCutConfig skillGrpSCConfig = new SkillGrpShortCutConfig(); - skillGrpSCConfig.setNum = i; - skillGrpSCConfig.slotNum = j; - skillGrpSCConfig.groupIndex = pSkillGrpSC.GetGroupIndex(); - - skillGrpSCConfigArray.Add(skillGrpSCConfig); - } - } - } - - public void SaveSkillShortcut( - List skillSCConfigArray, - CECShortcutSet[] aSCSets, - int count) - { - for (int i = 0; i < count; i++) - { - if (aSCSets[i] == null) continue; - - for (int j = 0; j < aSCSets[i].GetShortcutNum(); j++) - { - CECShortcut pSC = aSCSets[i].GetShortcut(j); - if (pSC == null || pSC.GetType() != (int)ShortcutType.SCT_SKILL) - continue; - - if (pSC is not CECSCSkill pSkillSC) - continue; - - int iOldSkillId = pSkillSC.GetSkill()?.GetSkillID() ?? 0; - if (iOldSkillId == 0) - continue; - - SkillShortCutConfig skillSCConfig = new SkillShortCutConfig(); - skillSCConfig.setNum = i; - skillSCConfig.slotNum = j; - skillSCConfig.skillId = iOldSkillId; - - skillSCConfigArray.Add(skillSCConfig); - } - } - } - + #if UNITY_EDITOR /// /// Cycles through learned skills by removing all shortcuts and adding 2 new skills to slots 0 and 1. /// If m_startingSkillID is set (>0), uses that specific skill ID and the next one (ID+1). /// Otherwise, cycles through all learned skills in pairs. /// - public void CycleSkillShortcuts() - { - // Get shortcut set 1 - CECShortcutSet pSCS = GetShortcutSet1(0); - if (pSCS == null) - { - Debug.LogWarning("CycleSkillShortcuts: Shortcut Set 1 is null"); - return; - } - - // Remove all shortcuts - pSCS.RemoveAllShortcuts(); - - // If starting skill ID is configured, cycle through pairs starting from that ID - if (m_startingSkillID > 0) - { - // Calculate the current skill IDs based on cycle index - // First press: startingID + 0, startingID + 1 (e.g., 5, 6) - // Second press: startingID + 2, startingID + 3 (e.g., 7, 8) - // Third press: startingID + 4, startingID + 5 (e.g., 9, 10) - int currentSkillID1 = m_startingSkillID + (m_currentSkillCycleIndex * 2); - int currentSkillID2 = currentSkillID1 + 1; - BMLogger.LogError($"CycleSkillShortcuts: Trying to add skills {currentSkillID1} and {currentSkillID2}"); - // Find and add first skill - CECSkill pSkill1 = GetPositiveSkillByID(currentSkillID1); - if (pSkill1 != null) - { - pSCS.CreateSkillShortcut(0, pSkill1); - Debug.LogError($"CycleSkillShortcuts: Added skill ID {currentSkillID1} to slot 0"); - } - else - { - Debug.LogError($"CycleSkillShortcuts: Skill with ID {currentSkillID1} not found in learned skills"); - int find = 1; - while (pSkill1 == null || find == 100) - { - m_startingSkillID++; - Debug.LogError($"CycleSkillShortcuts: m_startingSkillID {m_startingSkillID} "); - currentSkillID1 = m_startingSkillID + (m_currentSkillCycleIndex * 2); - pSkill1 = GetPositiveSkillByID(currentSkillID1); - find++; - } - pSCS.CreateSkillShortcut(0, pSkill1); - } - - // Find and add second skill - CECSkill pSkill2 = GetPositiveSkillByID(currentSkillID2); - if (pSkill2 != null) - { - pSCS.CreateSkillShortcut(1, pSkill2); - Debug.LogError($"CycleSkillShortcuts: Added skill ID {currentSkillID2} to slot 1"); - } - else - { - Debug.LogError($"CycleSkillShortcuts: Skill with ID {currentSkillID2} not found in learned skills"); - int find = 1; - while (pSkill2 == null || find == 100) - { - m_startingSkillID++; - Debug.LogError($"CycleSkillShortcuts: m_startingSkillID {m_startingSkillID} "); - currentSkillID2 = m_startingSkillID + (m_currentSkillCycleIndex * 2) +1; - pSkill2 = GetPositiveSkillByID(currentSkillID2); - find++; - } - pSCS.CreateSkillShortcut(1, pSkill2); - } - - // Increment cycle index for next press - m_currentSkillCycleIndex++; - - // Update UI - CDlgQuickBar cDlgQuickBar2 = CECUIManager.Instance?.GetCDlgQuickBar(); - cDlgQuickBar2?.UpdateShortcuts(); - return; - } - - // Original cycling behavior - // Get the list of learned skills - int skillCount = GetPositiveSkillNum(); - - // If no skills learned, just clear shortcuts (already done above) - if (skillCount == 0) - { - // Update UI - CDlgQuickBar cDlgQuickBar1 = CECUIManager.Instance?.GetCDlgQuickBar(); - cDlgQuickBar1?.UpdateShortcuts(); - return; - } - - // Calculate how many pairs we can make - int maxPairs = (skillCount + 1) / 2; // Round up division - - // Wrap around if we've reached the end - if (m_currentSkillCycleIndex >= maxPairs) - { - m_currentSkillCycleIndex = 0; - } - - // Calculate skill indices for this cycle - int skillIndex1 = m_currentSkillCycleIndex * 2; - int skillIndex2 = skillIndex1 + 1; - - // Add first skill to slot 0 - if (skillIndex1 < skillCount) - { - CECSkill pSkill1 = GetPositiveSkillByIndex(skillIndex1); - if (pSkill1 != null) - { - pSCS.CreateSkillShortcut(0, pSkill1); - } - } - - // Add second skill to slot 1 (if available) - if (skillIndex2 < skillCount) - { - CECSkill pSkill2 = GetPositiveSkillByIndex(skillIndex2); - if (pSkill2 != null) - { - pSCS.CreateSkillShortcut(1, pSkill2); - } - } - - // Increment cycle index for next time - m_currentSkillCycleIndex++; - - // Update UI - CDlgQuickBar cDlgQuickBar = CECUIManager.Instance?.GetCDlgQuickBar(); - cDlgQuickBar?.UpdateShortcuts(); - } #endif public bool HostIsReady() @@ -2502,237 +1333,13 @@ namespace BrewMonster // SetPlayerModel(); //Debug.LogError("Pos Character = " + pos); } - - private bool NormalAttackObject(int idTarget, bool bForceAttack, bool bMoreClose = false) - { - if (idTarget == 0 || idTarget == m_PlayerInfo.cid) - { - // We should have check target isn't dead - return false; - } - - //if (!EC_Game.GetGameRun().GetWorld().GetObject(idTarget, 1)) - // return false; - bool bStartNewWork = false; - - bool bUseAutoPF = false; - //CECPlayerWrapper* pWrapper = CECAutoPolicy::GetInstance().GetPlayerWrapper(); - //if (CECAutoPolicy::GetInstance().IsAutoPolicyEnabled() && pWrapper.GetAttackError() >= 2) - //bUseAutoPF = true; - - CECHPWorkTrace pWorkTrace = null; - CECHPWork pWork = null; - if ((pWork = m_pWorkMan.GetWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT)) != null) - { - pWorkTrace = pWork as CECHPWorkTrace; - } - else if ((pWork = m_pWorkMan.GetWork(CECHPWork.Host_work_ID.WORK_HACKOBJECT)) != null) - { - if ((pWork as CECHPWorkMelee).GetTarget() == idTarget) - return false; // Host is attacking the target - - pWorkTrace = (CECHPWorkTrace)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT); - bStartNewWork = true; - } - else if (m_pWorkMan.CanStartWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT)) - { - pWorkTrace = (CECHPWorkTrace)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT); - bStartNewWork = true; - } - - if (pWorkTrace != null) - { - pWorkTrace.SetTraceTarget( - pWorkTrace.CreatTraceTarget(idTarget, CECHPWorkTrace.Trace_reason.TRACE_ATTACK, bForceAttack), - bUseAutoPF); - pWorkTrace.SetMoveCloseFlag(bMoreClose); - - // Always start/re-start so trace runs (reuse path only updated target; work may be queued not current) - m_pWorkMan.StartWork_p1(pWorkTrace); - return true; - } - - return false; - } - + /// Use host's m_pvp (we update it from S2C duel packets). Base IsInDuel() reads CECPlayer.m_pvp which is never set. public new bool IsInDuel() { return m_pvp.iDuelState == Duel_state.DUEL_ST_INDUEL; } /// Duel opponent character id when in duel; 0 otherwise. Used by HPWork so trace/melee send correct PVP mask. public int GetDuelOpponentId() { return m_pvp.idDuelOpp; } - public int AttackableJudge(int idTarget, bool bForceAttack) - { - if (CannotAttack()) - return 0; - - //if (CDlgAutoHelp::IsAutoHelp()) - // return 0; - - if (idTarget == 0 || idTarget == m_PlayerInfo.cid) - return -1; - - CECObject pObject = EC_ManMessageMono.Instance.GetObject(idTarget, 1); - if (!pObject) - return -1; - - // If target is pet, it's attacked possibility depends on it's monster - if (GPDataTypeHelper.ISNPCID(idTarget)) - { - CECNPC pNPC = (CECNPC)pObject; - int idMaster = pNPC.GetMasterID(); - if (idMaster != 0) - { - // master¿ÉÄÜÊÇhostplayer - if (idMaster == m_PlayerInfo.cid) - return 0; - - //// Follow pet cannot be attacked - //if (pNPC.IsPetNPC() && ((CECPet)pNPC).IsFollowPet()) - // return 0; - - idTarget = idMaster; - pObject = EC_ManMessageMono.Instance.GetObject(idTarget, 1); - if (!pObject) - return -1; - } - } - - int iRet = 0; - - if (GPDataTypeHelper.ISNPCID(idTarget)) - { - CECNPC pNPC = (CECNPC)pObject; - - // If this npc is host's pet, cannot be attacked - if (pNPC.GetMasterID() == m_PlayerInfo.cid) - return 0; - - // If it's a pet and can not be attacked, pet can be attacked only if it's a fighting pet - //if (pNPC.IsPetNPC() && !((CECPet)pNPC).CanBeAttacked()) - // return 0; - - if (IsInBattle()) // Host is in battle - { - if (InSameBattleCamp(pNPC)) - iRet = 0; - else - { - if (pNPC.IsMonsterNPC()) - iRet = 1; - else if (pNPC.IsServerNPC() && - (IsInFortress() || - pNPC.GetRoleInBattle() == 8)) // ¶Ô·þÎñÐÍNPCµÄ¹¥»÷£¬°ïÅÉ»ùµØ»ò³Çսʱ¿ÉÓà - iRet = 1; - else - iRet = 0; - } - } - else if (pNPC.IsServerNPC()) - { - // In sanctuary we cannot attack NPCs - if (!IsPVPOpen() || m_bInSanctuary || !bForceAttack) - iRet = 0; - else - iRet = 1; - } - else // Is monster - { - iRet = 1; - } - - if (iRet == 1 && pNPC.GetOwnerFaction() > 0) - { - // Õë¶Ô°ïÅÉ PVP Õ½ÕùÖнûÖ¹²¿·Ö¹¥»÷ - if (GetFactionID() == pNPC.GetOwnerFaction() || // ²»¹¥»÷ͬ°ï¹Ö - pNPC.IsFactionPVPMineCar() && !CanAttackFactionPVPMineCar() || // ÎÞ·¨ÔÙ¹¥»÷Ëû°ï¿ó³µÇé¿ö - pNPC.IsFactionPVPMineBase() && !CanAttackFactionPVPMineBase()) - { - // ÎÞ·¨ÔÙ¹¥»÷Ëû°ï´æ¿óµãÇé¿ö - iRet = 0; - } - } - } - else if (GPDataTypeHelper.ISPLAYERID(idTarget)) - { - // Duel: can attack only duel opponent while in duel (origin: attack but not kill) - if (m_pvp.iDuelState == Duel_state.DUEL_ST_INDUEL && m_pvp.idDuelOpp == idTarget) - return 1; - if (m_pvp.iDuelState == Duel_state.DUEL_ST_STOPPING && m_pvp.idDuelOpp == idTarget) - return 0; - - // In sanctuary we cannot attack other players - if (m_bInSanctuary) - return 0; - - // Other player: no attack when not in duel (full PVP/free PVP/battle camp can be ported later) - iRet = 0; - } - // TO DO: full PVP branch ported from origin (commented below) - // //ASSERT(pObject.GetClassID() == CECObject::OCID_ELSEPLAYER); - // EC_ElsePlayer pPlayer = (EC_ElsePlayer)pObject; - // ROLEBASICPROP bp = pPlayer.GetBasicProps(); - // EC_GAME_SETTING gs = g_pGame.GetConfigs().GetGameSettings(); - - // if (m_pvp.bFreePVP) - // { - // if (IsTeamMember(idTarget)) - // return 0; - - // // In free pvp mode, for example, host is in arena. - // if (bForceAttack) - // iRet = 1; - // else if (gs.bAtk_NoMafia && IsFactionMember(pPlayer.GetFactionID())) - // iRet = 0; - // else if (gs.bAtk_NoWhite && !pPlayer.IsInvader() && !pPlayer.IsPariah()) - // iRet = 0; - // else if (gs.bAtk_NoAlliance && g_pGame.GetFactionMan().IsFactionAlliance(pPlayer.GetFactionID())) - // iRet = 0; - // else if (gs.bAtk_NoForce && GetForce() > 0 && GetForce() == pPlayer.GetForce()) - // iRet = 0; - // else - // iRet = 1; - // } - // else if (m_iBattleCamp != GP_BATTLE_CAMP_NONE) - // { - // // Host is in battle - // int iCamp = pPlayer.GetBattleCamp(); - // if (iCamp != GP_BATTLE_CAMP_NONE && iCamp != m_iBattleCamp) - // iRet = 1; - // else - // iRet = 0; - // } - // else // Normal mode - // { - // if (IsTeamMember(idTarget)) - // return 0; - - // if (!IsPVPOpen() || !pPlayer.IsPVPOpen() || m_BasicProps.iLevel < EC_MAXNOPKLEVEL || bp.iLevel < EC_MAXNOPKLEVEL) - // iRet = 0; - // else if (bForceAttack) - // iRet = 1; - // else if (!gs.bAtk_Player) - // iRet = 0; - // else if (gs.bAtk_NoMafia && IsFactionMember(pPlayer.GetFactionID())) - // iRet = 0; - // else if (gs.bAtk_NoWhite && !pPlayer.IsInvader() && !pPlayer.IsPariah()) - // iRet = 0; - // else if (gs.bAtk_NoAlliance && g_pGame.GetFactionMan().IsFactionAlliance(pPlayer.GetFactionID())) - // iRet = 0; - // else if (gs.bAtk_NoForce && GetForce() > 0 && GetForce() == pPlayer.GetForce()) - // iRet = 0; - // else - // iRet = 1; - // } - //} - else - { - return -1; - } - - return iRet; - } - public void ClearAnimation() { EventBus.PublishChannel(GetCharacterID(), new ClearComActFlagAllRankNodesEvent(true)); @@ -3003,295 +1610,7 @@ namespace BrewMonster return fSpeedSev; } - - public bool ApplySkillShortcut(int idSkill, bool bCombo = false /* false */, - int idSelTarget = 0 /* 0 */, int iForceAtk = -1 /* -1 */) - { - //StackChecker::ACTrace(4); - - if (m_pActionSwitcher != null) - m_pActionSwitcher.PostMessge((int)EMsgActionSwitcher.MSG_CASTSKILL); - - // Return-town skill is very special, handle it separately - // 回城技能非常特殊,单独处理 - if (idSkill == ID_RETURNTOWN_SKILL) - { - Debug.Log($"ApplySkillShortcut: Skill 167 detected, calling ReturnToTargetTown"); - return ReturnToTargetTown(0, bCombo); - } - - //if (idSkill == ID_SUMMONPLAYER_SKILL) - // return SummonPlayer(idSelTarget, bCombo); - - if (!CanDo(ActionCanDo.CANDO_SPELLMAGIC)) - return false; - - if (InSlidingState()) - return false; - - if (!bCombo) - ClearComboSkill(); - - if (idSelTarget == 0) - idSelTarget = m_idSelTarget; - - CECSkill pSkill = GetPositiveSkillByID(idSkill); - if (pSkill == null) pSkill = GetEquipSkillByID(idSkill); - if (pSkill == null) pSkill = CECComboSkillState.Instance.GetInherentSkillByID((uint)idSkill); - if (pSkill == null) - { - return false; - } - - //// If we press a chargeable skill again when it's being charged, - //// we cast it out at once - if (IsSpellingMagic() && m_pCurSkill != null && m_pCurSkill.IsCharging() && - m_pCurSkill.GetSkillID() == pSkill.GetSkillID()) - { - m_pCurSkill.EndCharging(); - UnityGameSession.c2s_SendCmdContinueAction(); - return true; - } - - int iCon = CheckSkillCastCondition(pSkill); - if (iCon != 0) - { - ProcessSkillCondition(iCon); - return false; - } - - //// Get force attack flag - bool bForceAttack = false; - if (iForceAtk < 0) - bForceAttack = glb_GetForceAttackFlag(0); - else - bForceAttack = iForceAtk > 0 ? true : false; - - //// Check negative effect skill - if (pSkill.GetType() == (int)skill_type.TYPE_ATTACK || pSkill.GetType() == (int)skill_type.TYPE_CURSE) - { - if (idSelTarget == m_PlayerInfo.cid) - { - // Host cannot spell negative effect magic to himself. - //EC_Game.GetGameRun().AddFixedChannelMsg(FIXMSG_TARGETWRONG, GP_CHAT_FIGHT); - return false; - } - else if (idSelTarget != 0) - { - if (AttackableJudge(idSelTarget, bForceAttack) != 1) - return false; - } - } - - //// Check whether target type match - int idCastTarget = idSelTarget; - int iTargetType = pSkill.GetTargetType(); - - if (pSkill.GetType() == (int)skill_type.TYPE_BLESS || - pSkill.GetType() == (int)skill_type.TYPE_NEUTRALBLESS) - { - if (iTargetType == 0 || !GPDataTypeHelper.ISPLAYERID(idSelTarget)) - idCastTarget = m_PlayerInfo.cid; - - // In some case, we shouldn't add bless effect to other players - if (GPDataTypeHelper.ISPLAYERID(idCastTarget) && idCastTarget != m_PlayerInfo.cid) - { - // If host has set bless skill filter only to himself, bless skill couldn't add to other players - byte byBLSMask = EC_Utility.glb_BuildBLSMask(); - - if (pSkill.GetRangeType() == (int)range_type.RANGE_POINT) - { - if (!IsTeamMember(idCastTarget)) - { - if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_SELF) != 0) - idCastTarget = m_PlayerInfo.cid; - else - { - EC_ElsePlayer pPlayer = - (EC_ElsePlayer)EC_ManMessageMono.Instance.GetECManPlayer.GetPlayer(idCastTarget); - if (pPlayer == null) - { - // Ä¿±êÏûʧ - return false; - } - - if (pPlayer.IsInvader() || pPlayer.IsPariah()) - { - if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NORED) != 0) - idCastTarget = m_PlayerInfo.cid; - } - - if (!IsFactionMember(pPlayer.GetFactionID())) - { - if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NOMAFIA) != 0) - idCastTarget = m_PlayerInfo.cid; - } - - if (!IsFactionAllianceMember(pPlayer.GetFactionID())) - { - if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NOALLIANCE) != 0) - idCastTarget = m_PlayerInfo.cid; - } - - if (GetForce() != pPlayer.GetForce()) - { - if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NOFORCE) != 0) - idCastTarget = m_PlayerInfo.cid; - } - } - } - } - - // If host is in duel, bless skill couldn't add to opponent - if (IsInDuel() && idSelTarget == m_pvp.idDuelOpp) - idCastTarget = m_PlayerInfo.cid; - - // If host is in battle, bless skill couldn't add to enemies - if (IsInBattle()) - { - EC_ElsePlayer pPlayer = EC_ManMessageMono.Instance.GetECManPlayer.GetElsePlayer(idCastTarget); - if (!InSameBattleCamp(pPlayer)) - idCastTarget = m_PlayerInfo.cid; - } - } - } - /* else if (pSkill.GetType() == CECSkill::TYPE_BLESSPET) - { - CECPet* pPet = g_pGame.GetGameRun().GetWorld().GetNPCMan().GetPetByID(idSelTarget); - if (!pPet || pPet.GetMasterID() == GetCharacterID()) - { - // Spell skill on host's pet - CECPetData* pPetData = m_pPetCorral.GetActivePet(); - if (!pPetData || - pPetData.GetClass() != GP_PET_CLASS_COMBAT && - pPetData.GetClass() != GP_PET_CLASS_SUMMON && - pPetData.GetClass() != GP_PET_CLASS_EVOLUTION) - return false; - - idCastTarget = m_pPetCorral.GetActivePetNPCID(); - } - // Only fighting pet can be blessed. - if (pPet && !pPet.CanBeAttacked()) - return false; - }*/ - else - { - if (iTargetType != 0 && idCastTarget == 0) - return false; - } - - // iTargetType == 4 means target must be pet. The problem is that pet will - // disappear from world after it died, so GetWorld().GetObject() will return - // NULL when host spells revive-pet skill on his dead pet. So, the target - // type of revive-pet skill should be 0 - if (iTargetType != 0) - { - // Target shoundn't be a corpse ? - int iAliveFlag = 0; - if (iTargetType == 1) - iAliveFlag = 1; - else if (iTargetType == 2) - iAliveFlag = 2; - - /* CECObject pObject = EC_Game.GetGameRun().GetWorld().GetObject(idCastTarget, iAliveFlag); - if (!pObject) - return false;*/ - } - - if (!IsMeleeing() && !IsSpellingMagic() && - (iTargetType == 0 || idCastTarget == m_PlayerInfo.cid)) - { - // Cast this skill need't checking cast distance - if (!pSkill.ReadyToCast()) - return false; - - if (!pSkill.IsInstant() && pSkill.GetType() != (int)Skilltype.TYPE_FLASHMOVE) - { - if (!NaturallyStopMoving()) - return false; // Couldn't stop naturally, so cancel casting skill - } - else if (pSkill.GetType() == (int)Skilltype.TYPE_FLASHMOVE) - { - if (!CanDo(ActionCanDo.CANDO_FLASHMOVE)) - return false; - } - - m_pPrepSkill = pSkill; - CastSkill(m_PlayerInfo.cid, bForceAttack); - } - else if (IsSpellingMagic() && m_pCurSkill == pSkill) - { - // If we are casting the same skill and it's in cooling time - return false; - } - else // Have to trace selected object before cast skill - { - if (!pSkill.ReadyToCast()) - return false; - - // Duel: so trace and CastSkill send correct PVP mask when touching duel opp - if (IsInDuel() && idCastTarget == m_pvp.idDuelOpp) - bForceAttack = true; - - if (CECCastSkillWhenMove.Instance.IsSkillSupported(pSkill.GetSkillID(), this) && - m_pWorkMan.IsMovingToPosition() && - m_pWorkMan.CanCastSkillImmediately(pSkill.GetSkillID())) - { - m_pPrepSkill = pSkill; - return CastSkill(idCastTarget, bForceAttack); - } - else - { - bool bTraceOK = false; - bool bUseAutoPF = false; - CECPlayerWrapper pWrapper = CECAutoPolicy.GetInstance().GetPlayerWrapper(); - if (CECAutoPolicy.GetInstance().IsAutoPolicyEnabled() && pWrapper.GetAttackError() >= 2) - bUseAutoPF = true; - - if (idCastTarget == 0) - { - idCastTarget = - GetCharacterID(); // ±ÜÃâË²ÒÆµÈ¼¼ÄÜʱ idCastTarget Ϊ0µ¼Ö CECWorkTrace::CreateTraceTarget ·µ»Ø¿Õ - } - - CECHPWork pWork = m_pWorkMan.GetWork(Host_work_ID.WORK_TRACEOBJECT); - if (pWork != null) - { - CECHPWorkTrace pWorkTrace = (CECHPWorkTrace)(pWork); - if (pWorkTrace.GetTraceReason() == Trace_reason.TRACE_SPELL && - pWorkTrace.GetTarget() == idCastTarget && - pWorkTrace.GetPrepSkill() == pSkill) - return false; // We are just doing the same thing - - pWorkTrace.SetTraceTarget( - pWorkTrace.CreatTraceTarget(idCastTarget, Trace_reason.TRACE_SPELL, bForceAttack), - bUseAutoPF); - pWorkTrace.SetPrepSkill(pSkill); - bTraceOK = true; - } - else if (m_pWorkMan.CanStartWork(Host_work_ID.WORK_TRACEOBJECT)) - { - CECHPWorkTrace pWork2 = (CECHPWorkTrace)m_pWorkMan.CreateWork(Host_work_ID.WORK_TRACEOBJECT); - pWork2.SetTraceTarget( - pWork2.CreatTraceTarget(idCastTarget, Trace_reason.TRACE_SPELL, bForceAttack), bUseAutoPF); - pWork2.SetPrepSkill(pSkill); - m_pWorkMan.StartWork_p1(pWork2); - bTraceOK = true; - } - - if (!bTraceOK) return false; - // } - //} - } - } - - return true; - } - - public CECSkill GetPrepSkill() - { - return m_pPrepSkill; - } + public void PrepareNPCService(int idSev) { @@ -3471,328 +1790,6 @@ namespace BrewMonster return true; } - public bool CastSkill(int idTarget, bool bForceAttack, CECObject pTarget = null) - { - // Check if prep skill is valid, ready to cast, and not currently spelling magic - if (m_pPrepSkill == null || !m_pPrepSkill.ReadyToCast() || IsSpellingMagic()) - { - // Check if skill can change to melee attack - if (m_pPrepSkill != null && m_pPrepSkill.ChangeToMelee()) - { - bool bFlag = m_pPrepSkill.ReadyToCast(); - - // Finish any tracing work - if (m_pWorkMan.IsTracing()) - m_pWorkMan.FinishRunningWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT); - - // Handle combo skill or normal attack - if (m_pComboSkill != null) - m_pComboSkill.Continue(false); - else - { - // Perform normal attack instead - NormalAttackObject(idTarget, true); - } - } - - m_pPrepSkill = null; - return false; - } - - if (m_pPrepSkill.GetType() == (int)CECSkill.SkillType.TYPE_ATTACK || - m_pPrepSkill.GetType() == (int)CECSkill.SkillType.TYPE_CURSE) - { - if (idTarget != 0 && AttackableJudge(idTarget, bForceAttack) != 1) - { - m_pPrepSkill = null; - return false; - } - } - - // Duel: server accepts attack/skill on player only with PVP/force mask - if (IsInDuel() && idTarget == m_pvp.idDuelOpp) - bForceAttack = true; - - //TODO: Check cast condition - method not yet implemented - int iRet = CheckSkillCastCondition(m_pPrepSkill); - if (iRet != 0) - { - switch (iRet) - { - case 2: // Need MP - // g_pGame.GetGameRun().AddFixedMessage(FIXMSG_NEEDMP); - break; - case 8: // Need AP - // g_pGame.GetGameRun().AddFixedMessage(FIXMSG_NEEDAP); - break; - case 10: // Pack full - // g_pGame.GetGameRun().AddFixedMessage(FIXMSG_PACKFULL1); - break; - case 20: // Need item - // g_pGame.GetGameRun().AddFixedMessage(FIXMSG_NEEDITEM); - break; - case 12: // HP unsatisfied - // g_pGame.GetGameRun().AddFixedMessage(FIXMSG_HP_UNSATISFIED); - break; - } - - m_pPrepSkill = null; - return false; - } - - byte byPVPMask = glb_BuildPVPMask(bForceAttack); - - Debug.Log($"HoangDev: Cast Skill ID={m_pPrepSkill.GetSkillID()}"); - - // Handle instant skills - if (m_pPrepSkill.IsInstant()) - { - int countTarget = 1; - targetsCastSkill = new int[countTarget]; - targetsCastSkill[0] = idTarget; - UnityGameSession.c2s_CmdCastInstantSkill(m_pPrepSkill.GetSkillID(), byPVPMask, countTarget, targetsCastSkill); - m_pPrepSkill = null; - } - // Handle flash move skills (瞬移技能) - else if (m_pPrepSkill.GetType() == (int)CECSkill.SkillType.TYPE_FLASHMOVE) - { - // Self or self-sphere range types - if (m_pPrepSkill.GetRangeType() == (int)CECSkill.RangeType.RANGE_SLEF || - m_pPrepSkill.GetRangeType() == (int)CECSkill.RangeType.RANGE_SELFSPHERE) - { - A3DVECTOR3 vDir = GetDir(); - float fDist = m_pPrepSkill.GetCastRange(m_ExtProps.ak.AttackRange, GetPrayDistancePlus()); - - // 左侧之翼,左跳 (Left wing skill - jump left) - if (m_pPrepSkill.GetSkillID() == 1844) - { - vDir = A3d_RotatePosAroundY(-vDir, Mathf.PI / 2); - } - // 右侧之翼,右跳 (Right wing skill - jump right) - else if (m_pPrepSkill.GetSkillID() == 1845) - { - vDir = A3d_RotatePosAroundY(vDir, Mathf.PI / 2); - } - // 范围小于0则后跳 (If range < 0, jump backward) - else if (fDist < 0.0f) - { - vDir = -vDir; - } - - fDist = Mathf.Abs(fDist); - A3DVECTOR3 vDest = m_MoveCtrl.FlashMove(vDir, 100.0f, fDist); - UnityGameSession.c2s_CmdCastPosSkill(m_pPrepSkill.GetSkillID(), EC_Utility.ToVector3(vDest), - byPVPMask, 0, 0); - m_pPrepSkill = null; - } - else - { - // 刺客如影随行类技能 (Assassin shadow-following skills) - bool bSuccess = false; - - while (true) - { - // Break if no target or self-target - if (idTarget == 0 || idTarget == GetCharacterID()) - break; - - // Get target object - CECObject pObject = CECWorld.Instance.GetObject(idTarget, 0); - if (pObject == null) break; - - A3DVECTOR3 vHostPos = EC_Utility.ToA3DVECTOR3(transform.position); // GetPos() - A3DVECTOR3 vTargetPos = EC_Utility.ToA3DVECTOR3(pObject.transform.position); // pObject.GetPos() - - // 判断技能释放距离限制是否满足 (Check if skill cast distance is satisfied) - float fTouchRadius = 0.0f; - - if (GPDataTypeHelper.ISNPCID(idTarget)) - { - CECNPC pNPC = pObject as CECNPC; - if (pNPC != null) - fTouchRadius = pNPC.GetTouchRadius(); - else - break; - } - else if (GPDataTypeHelper.ISPLAYERID(idTarget)) - { - EC_ElsePlayer pElsePlayer = pObject as EC_ElsePlayer; - if (pElsePlayer != null) - fTouchRadius = pElsePlayer.GetTouchRadius(); - else - break; - } - else - break; - - if (!CanTouchTarget(vTargetPos, fTouchRadius, 2)) - { - // Target is far - show message - // g_pGame.GetGameRun().AddFixedMessage(FIXMSG_TARGETISFAR); - Debug.Log("Target is too far"); - break; - } - - A3DVECTOR3 vMoveDir = vTargetPos - vHostPos; - float fDist = EC_Utility.ToVector3(vMoveDir).magnitude; - - // 距离目标太近,不处理 (Too close to target, don't process) - float fNearDist = 0.0f; - // TODO: Implement IsTooNear - if (IsTooNear(vTargetPos, ref fNearDist)) - { - Debug.Log("Target is too near"); - break; - } - - // 计算要移往的目标位置(默认值) (Calculate target position to move to) - vMoveDir.Normalize(); - A3DVECTOR3 vMovePos = vHostPos + vMoveDir * (fDist - fNearDist); - - // TODO: Implement ClampAboveGround - float fClampedHeight = ClampAboveGround(vMovePos); - if (Mathf.Abs(fClampedHeight - vMovePos.y) >= 5.0f) - { - Debug.Log("Would stuck or so"); - break; - } - - vMovePos.y = fClampedHeight; - bool bPosVerified = false; - - // 目标为带凸包的 NPC 时,单独处理 (Special handling for NPCs with collision) - if (GPDataTypeHelper.ISNPCID(idTarget)) - { - // TODO: Implement CalcCollideFreePos for NPC AABB - CECNPC pNPC = pObject as CECNPC; - A3DAABB aabbNPC = new A3DAABB(); - if (pNPC.GetCHAABB(ref aabbNPC)) - { - A3DVECTOR3 vTestPos; - if (CalcCollideFreePos(aabbNPC, out vTestPos)) - { - vMovePos = vTestPos; - bPosVerified = true; - } - else - { - Debug.Log("Would stuck or so"); - break; - } - } - } - - // TODO: Implement collision checking - if (!bPosVerified && !IsPosCollideFree(vMovePos)) - { - A3DVECTOR3 vTestPos2; - if (!CalcVerticalCollideFreePos(vMovePos, out vTestPos2)) - { - Debug.Log("Would stuck or so"); - break; - } - - vMovePos = vTestPos2; - bPosVerified = true; - } - - //TODO: Implement IsTooNear check for final position - float reffake = 0; - if (IsTooNear(vMovePos, ref reffake)) - { - Debug.Log("Target is too near"); - break; - } - - // 发送协议 (Send protocol) - UnityGameSession.c2s_CmdCastPosSkill(m_pPrepSkill.GetSkillID(), EC_Utility.ToVector3(vMovePos), - byPVPMask, 1, idTarget); - bSuccess = true; - } - - m_pPrepSkill = null; - return bSuccess; - } - } - else - { - // Regular skill casting - byte byPVPMask2 = glb_BuildPVPMask(bForceAttack); - int targets = 1; - targetsCastSkill = new int[targets]; - targetsCastSkill[0] = idTarget; - UnityGameSession.c2s_CmdCastSkill(m_pPrepSkill.GetSkillID(), byPVPMask2, targets, targetsCastSkill); - } - - return true; - } - - // Return to a target town through skill - // 通过技能返回目标城镇 - private bool ReturnToTargetTown(int idTarget, bool bCombo = false) - { - - if (!CanDo(ActionCanDo.CANDO_SPELLMAGIC)) - { - return false; - } - - int idSkill = ID_RETURNTOWN_SKILL; - CECSkill pSkill = GetPositiveSkillByID(idSkill); - if (pSkill == null) pSkill = GetEquipSkillByID(idSkill); - if (pSkill == null) - { - Debug.LogError("ReturnToTargetTown: Skill 167 not found"); - return false; - } - - - if (!bCombo) - { - // ClearComboSkill(); // Uncomment if ClearComboSkill exists - } - - // Check skill cast condition (commented out in ApplySkillShortcut, so skip for now) - // int iCon = CheckSkillCastCondition(pSkill); - // if (iCon) - // { - // ProcessSkillCondition(iCon); - // return false; - // } - - // If this skill is in cooling time or we are casting other skill, return - // 如果此技能在冷却时间或我们正在施放其他技能,返回 - if (!pSkill.ReadyToCast() || - !m_pWorkMan.CanCastSkillImmediately(pSkill.GetSkillID())) - { - // If the current Work in m_pWorkMan is CECHPWorkSpell or CECHPWorkFly, it should be executed first - // Otherwise, when receiving OBJECT_CAST_SKILL protocol, CECHPWorkSpell cannot be executed - // This causes CECHostPlayer::IsSpellingMagic() to return false, causing the client to send c2s_CmdCancelAction - // When this CECHPWorkSpell executes, it cannot respond - // After this method is executed, we use the return to city mechanism - // 如果 m_pWorkMan 中的当前 Work 是 CECHPWorkSpell 或 CECHPWorkFly,则应先执行 - // 否则,当收到 OBJECT_CAST_SKILL 协议时,CECHPWorkSpell 无法执行 - // 这会导致 CECHostPlayer::IsSpellingMagic() 返回 false,导致客户端发送 c2s_CmdCancelAction - // 当此 CECHPWorkSpell 执行时,它无法响应 - // 在此方法执行完成后,我们使用回城机制 - Debug.LogError($"ReturnToTargetTown: Skill not ready - ReadyToCast={pSkill.ReadyToCast()}, CanCastSkillImmediately={m_pWorkMan.CanCastSkillImmediately(pSkill.GetSkillID())}"); - return false; - } - - m_pPrepSkill = pSkill; - byte byPVPMask = glb_BuildPVPMask(false); - - // Call c2s_CmdCastSkill with target parameter - // 使用目标参数调用 c2s_CmdCastSkill - int targets = 1; - int[] targetsCastSkill = new int[targets]; - targetsCastSkill[0] = idTarget; - UnityGameSession.c2s_CmdCastSkill(idSkill, byPVPMask, targets, targetsCastSkill); - - - return true; - } - public A3DVECTOR3 GetDir() { // Return forward direction from transform @@ -3954,6 +1951,10 @@ namespace BrewMonster return true; } + // Duel: always allow selecting the duel opponent so we can attack/cast (distance checked when trace runs) + if (IsInDuel() && idTarget == m_pvp.idDuelOpp) + return true; + CECObject pTarget = null; if (GPDataTypeHelper.ISPLAYERID(idTarget)) { @@ -5709,606 +3710,7 @@ namespace BrewMonster public int GetRealmSubLevel() { return m_RealmLevel % 100; } public static int GetRealmLayer(int realmLevel) { return realmLevel > 0 ? (realmLevel + 9) / 10 : 0; } public static int GetRealmSubLevel(int realmLevel) { return realmLevel > 0 ? (realmLevel % 10 > 0 ? realmLevel % 10 : 10) : 0; } - public bool HaveHealthStones() - { - var pPack = GetPack(); - var items = new int[] { 36764, 36765, 36766, 36767 }; - for (int i = 0; i < items.Length; ++i) - { - if (pPack.FindItem(items[i]) >= 0) - return true; - } - return false; - } - - public bool UseItemInPack(int iPack, int iSlot, bool showMsg = true) - { - if (!CanDo(ActionCanDo.CANDO_USEITEM)) - return false; - - EC_Inventory pPack = GetPack(iPack); - if (pPack == null) - return false; - - EC_IvtrItem pItem = pPack.GetItem(iSlot); - if (pItem == null || pItem.IsFrozen()) - return false; - - if (pItem.Use_Persist() && (IsJumping() || IsFalling())) - return false; - - CECGameRun pGameRun = EC_Game.GetGameRun(); - //CECGameSession pSession = g_pGame.GetGameSession(); - - if (pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_FIREWORK) - { - if (GetProfession() == (int)PROFESSION.PROF_GHOST && IsInvisible()) - { - if (showMsg) - pGameRun.AddFixedMessage((int)FixedMsg.FIXMSG_CANNOT_USE_WHEN_INVISIBLE); - return false; - } - } - - if (pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_INCSKILLABILITY) - { - EC_IvtrIncSkillAbility pIncSkill = pItem as EC_IvtrIncSkillAbility; - //if (pIncSkill != null) - //{ - // var pDBEssence = pIncSkill.GetDBEssence(); - // CECSkill pSkill = GetNormalSkill(pDBEssence.id_skill); - // if (pSkill != null) - // { - // if (pSkill.GetSkillLevel() != pDBEssence.level_required) - // { - // if (showMsg) - // pGameRun.AddFixedMessage((int)FixedMsg.FIXMSG_PRODUCE_LEVEL_INVALID); - // return false; - // } - // if (GetSkillAbilityPercent(pDBEssence.id_skill) >= 100) - // { - // if (showMsg) - // pGameRun.AddFixedMessage((int)FixedMsg.FIXMSG_PRODUCE_ABILITY_FULL); - // return false; - // } - // } - //} - } - - if (pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_TRANSMITSCROLL) - { - CECGameUIMan pGameUI = pGameRun.GetUIManager().GetInGameUIMan(); - if (pGameUI != null && !IsFighting()) - { - // TODO: Implement travel map dialog - //CDlgWorldMap* pMap = (CDlgWorldMap*)pGameUI->GetDialog("Win_WorldMapTravel"); - //pMap->BuildTravelMap(DT_TRANSMITSCROLL_ESSENCE, (void*)iSlot); - //pMap->Show(true); - } - return true; - } - - if (pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_SHOPTOKEN) - { - CECGameUIMan pGameUI = pGameRun.GetUIManager().GetInGameUIMan(); - if (pGameUI != null && !IsFighting()) - { - //CDlgTokenShop* pDlg = dynamic_cast(pGameUI->GetDialog("Win_TokenShop")); - //if (pDlg) - //{ - // pDlg->InitTokenShopItem(pItem->GetTemplateID()); - // pDlg->Show(!pDlg->IsShow()); - //} - } - return true; - } - - if (pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_UNIVERSAL_TOKEN) - { - // TODO: Implement universal token when available - EC_IvtrUniversalToken pUniversalToken = pItem as EC_IvtrUniversalToken; - //if (pUniversalToken != null && pUniversalToken.HasAnyUsage()) - //{ - // CECUseUniversalTokenCommandManager.Instance.Use(pUniversalToken, pUniversalToken.UsageIndexAt(0)); - // return true; - //} - } - - if (pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_TASKDICE) - { - EC_IvtrTaskDice pTaskDice = pItem as EC_IvtrTaskDice; - if (pTaskDice != null) - { - if (pTaskDice != null) - { - if (IsFlying() && pTaskDice.GetDBEssence().no_use_in_combat == 1) - { - if (showMsg) - pGameRun.AddFixedMessage((int)FixedMsg.FIXMSG_CANNOT_USE_IN_BATTLE); - return false; - } - } - } - } - - if (pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_TARGETITEM) - { - EC_IvtrTargetItem pTargetItem = pItem as EC_IvtrTargetItem; - if (pTargetItem == null) - return false; - - var essence = pTargetItem.GetDBEssence(); - - if (!pTargetItem.IsEssenceLoaded()) - return false; - - if (IsFighting() && essence.use_in_combat == 0) - { - if (showMsg) - if (showMsg) pGameRun.AddFixedMessage((int)FixedMsg.FIXMSG_CANNOT_USE_IN_BATTLE); - return false; - } - - if (essence.use_in_sanctuary_only != 0 && !IsInSanctuary()) - { - if (showMsg) - pGameRun.AddFixedMessage((int)FixedMsg.FIXMSG_USE_IN_SANCTUARY_ONLY); - return false; - } - - int iCurrMap = pGameRun.GetWorld().GetInstanceID(); - if (pTargetItem.GetDBEssence().num_area != 0) - { - bool found = false; - for (int i = 0; i < pTargetItem.GetDBEssence().num_area; i++) - { - if (pTargetItem.GetDBEssence().area_id[i] == iCurrMap) - { - found = true; - break; - } - } - if (!found) - { - if (showMsg) - pGameRun.AddFixedMessage((int)FixedMsg.FIXMSG_CANNOT_USE_IN_CURR_MAP); - return false; - } - } - - if (!CanDo(ActionCanDo.CANDO_SPELLMAGIC)) - return false; - - if (InSlidingState()) - return false; - - if (m_idSelTarget == 0) - return false; - - CECSkill pSkill = pTargetItem.GetTargetSkill(); - if (pSkill == null) - return false; - - if (IsSpellingMagic() && m_pCurSkill != null && m_pCurSkill.IsCharging() && - m_pCurSkill.GetSkillID() == pSkill.GetSkillID()) - { - m_pCurSkill.EndCharging(); - UnityGameSession.c2s_SendCmdContinueAction(); - return true; - } - - int iCon = CheckSkillCastCondition(pSkill); - if (iCon != 0) - { - if (showMsg) - ProcessSkillCondition(iCon); - return false; - } - - bool bForceAttack = glb_GetForceAttackFlag(0); - if (pSkill.GetType() == (int)CECSkill.SkillType.TYPE_ATTACK || - pSkill.GetType() == (int)CECSkill.SkillType.TYPE_CURSE) - { - if (m_idSelTarget == m_PlayerInfo.cid) - { - if (showMsg) - pGameRun.AddFixedChannelMsg((int)FixedMsg.FIXMSG_TARGETWRONG, (int)ChatChannel.GP_CHAT_FIGHT); - return false; - } - else if (m_idSelTarget != 0) - { - if (AttackableJudge(m_idSelTarget, bForceAttack) != 1) - return false; - } - } - // Duel: server accepts skill on opponent only with PVP/force mask - if (IsInDuel() && m_idSelTarget == m_pvp.idDuelOpp) - bForceAttack = true; - - int idCastTarget = m_idSelTarget; - int iTargetType = pSkill.GetTargetType(); - - if (pSkill.GetType() == (int)CECSkill.SkillType.TYPE_BLESS || - pSkill.GetType() == (int)CECSkill.SkillType.TYPE_NEUTRALBLESS) - { - if (iTargetType == 0 || !GPDataTypeHelper.ISPLAYERID(m_idSelTarget)) - idCastTarget = m_PlayerInfo.cid; - - if (GPDataTypeHelper.ISPLAYERID(idCastTarget) && idCastTarget != m_PlayerInfo.cid) - { - byte byBLSMask = EC_Utility.glb_BuildBLSMask(); - - if (pSkill.GetRangeType() == (int)CECSkill.RangeType.RANGE_POINT) - { - if (!IsTeamMember(idCastTarget)) - { - if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_SELF) != 0) - { - idCastTarget = m_PlayerInfo.cid; - } - else - { - EC_ElsePlayer pPlayer = EC_ManMessageMono.Instance.GetECManPlayer.GetElsePlayer(idCastTarget) as EC_ElsePlayer; - if (pPlayer == null) - return false; - - if (pPlayer.IsInvader() || pPlayer.IsPariah()) - { - if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NORED) != 0) - idCastTarget = m_PlayerInfo.cid; - } - - if (!IsFactionMember(pPlayer.GetFactionID())) - { - if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NOMAFIA) != 0) - idCastTarget = m_PlayerInfo.cid; - } - - if (!IsFactionAllianceMember(pPlayer.GetFactionID())) - { - if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NOALLIANCE) != 0) - idCastTarget = m_PlayerInfo.cid; - } - - if (GetForce() != pPlayer.GetForce()) - { - if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NOFORCE) != 0) - idCastTarget = m_PlayerInfo.cid; - } - } - } - } - - // If host is in dule - if (IsInDuel() && m_idSelTarget == m_pvp.idDuelOpp) - idCastTarget = m_PlayerInfo.cid; - - // If host is in battle - if (IsInBattle()) - { - EC_ElsePlayer pPlayer = EC_ManMessageMono.Instance.GetECManPlayer.GetElsePlayer(idCastTarget); - if (!InSameBattleCamp(pPlayer)) - idCastTarget = m_PlayerInfo.cid; - - } - } - } - else if (pSkill.GetType() == (int)CECSkill.SkillType.TYPE_BLESS) - { - // TODO: Implement pet blessing when petsystem is available - //CECSCPet pPet = EC_Game.GetGameRun().GetWorld().GetPetByID(m_idSelTarget); - //if (pPet == null || pPet.GetMasterID() == GetCharacterID()) - //{ - // CECPetData pPetData = m_pPetCorral.GetActivePet(); - // if (pPetData == null || - // pPetData.GetClass() != GP_PET_TYPE.GP_PET_CLASS_COMBAT && - // pPetData.GetClass() != GP_PET_TYPE.GP_PET_CLASS_SUMMON && - // pPetData.GetClass() != GP_PET_TYPE.GP_PET_CLASS_EVOLUTION) - // return false; - - // idCastTarget = m_pPetCorral.GetActivePetNPCID(); - //} - //if(iTargetType != 0 && idCastTarget == 0) - // return false; - } - - if (iTargetType != 0) - { - int iAliveFlag = 0; - if (iTargetType == 1) - iTargetType = 1; - else if (iTargetType == 2) - iTargetType = 2; - - CECObject pObject = EC_ManMessageMono.Instance.GetObject(idCastTarget, iAliveFlag); - if (pObject == null) - return false; - } - - if (!IsMeleeing() && !IsSpellingMagic() && - (iTargetType == 0 || idCastTarget == m_PlayerInfo.cid)) - { - if (!pSkill.ReadyToCast()) - return false; - - if (!pSkill.IsInstant() && pSkill.GetType() != (int)CECSkill.SkillType.TYPE_FLASHMOVE) - { - if (!NaturallyStopMoving()) - return false; - } - else if (pSkill.GetType() == (int)CECSkill.SkillType.TYPE_FLASHMOVE) - { - if (!CanDo(ActionCanDo.CANDO_FLASHMOVE)) - return false; - } - - m_pPrepSkill = pSkill; - } - else if (IsSpellingMagic() && m_pCurSkill == pSkill && !pSkill.ReadyToCast()) - { - return false; - } - } - if (pItem.IsEquipment()) - { - if (iPack == Inventory_type.IVTRTYPE_EQUIPPACK) - { - // Take off equipment - int iEmpty = m_pPack.SearchEmpty(); - if (iEmpty < 0) - return false; - - UnityGameSession.RequestEquipItemAsync((byte)iEmpty, (byte)iSlot, null); - return true; - } - - EC_IvtrEquip pEquip = pItem as EC_IvtrEquip; - if (pEquip == null) - return false; - - int iReason = 0; - if (!CanUseEquipment(pEquip, ref iReason)) - return false; - - int iFirstFree = -1, iFirstCan = -1; - for (int i = 0; i < InventoryConst.SIZE_ALL_EQUIPIVTR; i++) - { - if (pItem.CanEquippedTo(i)) - { - if (iFirstCan < 0) - iFirstCan = i; - - if (m_pEquipPack.GetItem(i) == null && iFirstFree < 0) - { - iFirstFree = i; - break; - } - } - } - - int iDst; - if (iFirstFree >= 0) - iDst = iFirstFree; - else if (iFirstCan >= 0) - iDst = iFirstCan; - else - { - Debug.Assert(false); - return false; - } - - if (pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_DYNSKILLEQUIP) - { - int iSameIDPos = m_pEquipPack.FindItem(pItem.GetTemplateID()); - if (iSameIDPos >= 0) - { - iDst = iSameIDPos; - } - - } - - if (pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_ARROW || - pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_DYNSKILLEQUIP) - { - EC_IvtrItem pDstItem = m_pEquipPack.GetItem(iDst); - if (pDstItem == null || pItem.GetTemplateID() != pDstItem.GetTemplateID()) - UnityGameSession.RequestEquipItemAsync((byte)iSlot, (byte)iDst, null); - else - { - // TODO: Implement c2s_CmdMoveItemToEquip when available - //UnityGameSession.c2s_CmdMoveItemToEquip((byte)iSlot, (byte)iDst); - } - } - else - { - if (pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_GENERALCARD) - { - //TODO: Add general card equip request - EC_IvtrGeneralCard pCard = pItem as EC_IvtrGeneralCard; - if (pCard != null) - { - iDst = InventoryConst.EQUIPIVTR_GENERALCARD1 + pCard.GetEssence().type; - } - } - UnityGameSession.RequestEquipItemAsync((byte)iSlot, (byte)iDst, null); - } - return true; - } - - if (iPack != Inventory_type.IVTRTYPE_PACK) - return false; - - if (!pItem.CheckUseCondition()) - { - if (showMsg) - pGameRun.AddFixedMessage((int)FixedMsg.FIXMSG_ITEM_CANNOTUSE); - return false; - } - - int piMax = -1; - if (pItem.GetCoolTime(out piMax) > 0) - { - if (showMsg) - pGameRun.AddFixedMessage((int)FixedMsg.FIXMSG_ITEM_INCOOLTIME); - return false; - } - - if (pItem.Use_AtkTarget() || pItem.Use_Target()) - { - if (pItem.Use_AtkTarget() && CannotAttack()) - return false; - - if (m_idSelTarget == 0 || m_idSelTarget == m_PlayerInfo.cid) - { - if (showMsg) - { - CECStringTab pStrTab = EC_Game.GetFixedMsgs(); - pGameRun.AddChatMessage(pStrTab.GetWideString((int)FixedMsg.FIXMSG_NOTARGET), (int)ChatChannel.GP_CHAT_SYSTEM); - } - return false; - } - - float fAattackRange = 10000.0f; - if (pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_TOSSMAT) - { - EC_IvtrTossMat pTossMat = pItem as EC_IvtrTossMat; - if (pTossMat != null) - fAattackRange = pTossMat.GetDBEssence().attack_range; - } - else if (pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_TANKCALLIN) - { - fAattackRange = 5.0f; - } - else if (pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_TARGETITEM) - { - EC_IvtrTargetItem pTargetItem = pItem as EC_IvtrTargetItem; - if (pTargetItem != null && pTargetItem.GetTargetSkill() != null) - { - fAattackRange = pTargetItem.GetTargetSkill().GetCastRange(m_ExtProps.ak.AttackRange, GetPrayDistancePlus()); - } - } - - float fDist = 0, fTargetRag = 0; - CECObject pObject = null; - if (CalcDist(m_idSelTarget, out fDist, out pObject)) - { - return false; - } - - if (GPDataTypeHelper.ISNPCID(m_idSelTarget)) - { - pObject = EC_ManMessageMono.Instance.CECNPCMan.GetNPC(m_idSelTarget); - CECNPC pNPC = pObject as CECNPC; - if (pNPC != null) - fTargetRag = pNPC.GetTouchRadius(); - } - else if (GPDataTypeHelper.ISPLAYERID(m_idSelTarget)) - { - pObject = EC_ManMessageMono.Instance.GetECManPlayer.GetElsePlayer(m_idSelTarget); - EC_ElsePlayer pPlayer = pObject as EC_ElsePlayer; - if (pPlayer != null) - fTargetRag = pPlayer.GetTouchRadius(); - } - - if (fDist - fTargetRag > fAattackRange * 0.8f) - { - if (showMsg) - { - CECStringTab pStrTab = EC_Game.GetFixedMsgs(); - pGameRun.AddChatMessage(pStrTab.GetWideString((int)FixedMsg.FIXMSG_TARGETISFAR), (int)ChatChannel.GP_CHAT_SYSTEM); - } - return false; - } - - byte byPVPMask = glb_BuildPVPMask(glb_GetForceAttackFlag(0)); - UnityGameSession.c2s_SendCmdUseItemWithTarget((byte)iPack, (byte)iSlot, pItem.GetTemplateID(), byPVPMask); - } - else - { - if (pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_DOUBLEEXP) - { - EC_IvtrDoubleExp pDoubleExp = pItem as EC_IvtrDoubleExp; - if (pDoubleExp != null) - { - if (pDoubleExp.GetDBEssence().double_exp_time + pGameRun.GetRemainDblExpTime() > 3600 * 4) - { - if (showMsg) - { - CECGameUIMan pGameUI = pGameRun.GetUIManager().GetInGameUIMan(); - //pGameUI.MessageBox("", pGameUI.GetStringFromTable(828), MB_OK, new Color32(1, 1, 1, 0.6)); - } - return false; - } - } - } - if (pItem.GetClassID() == (int)EC_IvtrItem.InventoryClassId.ICID_SHARPENER) - { - if (showMsg) - pGameRun.AddFixedMessage((int)FixedMsg.FIXMSG_SHARPEN_ON_DRAG); - return false; - } - UnityGameSession.c2s_SendCmdUseItem((byte)iPack, (byte)iSlot, pItem.GetTemplateID(), 1); - } - return true; - } - - private void OnMsgHstUseItem(ECMSG Msg) - { - cmd_host_use_item pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - EC_Inventory pPack = GetPack(pCmd.byPackage); - if (pPack == null) - { - return; - } - - EC_IvtrItem pItem = pPack.GetItem(pCmd.bySlot, false); - if (pItem == null || pItem.GetTemplateID() != pCmd.item_id) - { - return; - } - - pItem.Use(); - - if (pCmd.use_count > 0) - { - bool removed = pPack.RemoveItem(pCmd.bySlot, pCmd.use_count); - if (removed) - { - if (pPack.GetItem(pCmd.bySlot, false) == null) - { - //Debug.Log($"[OnMsgHstUseItem] Item {pCmd.item_id} removed from slot {pCmd.bySlot}"); - } - } - else - { - //Debug.LogError($"[OnMsgHstUseItem] Failed to remove item {pCmd.item_id} from slot {pCmd.bySlot}"); - } - - var ui = GameObject.FindFirstObjectByType(); - if (ui != null) - { - ui.RefreshAll(); - ui.UpdateCooldownOverlays(); - } - else - { - //Debug.LogError("[OnMsgHstUseItem] EC_InventoryUI not found, UI may not update"); - } - } - - if (m_pWorkMan != null) - { - CECHPWork pWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_USEITEM); - if (pWork is CECHPWorkUse useWork) - { - if (useWork.GetItem() == pCmd.item_id) - { - m_pWorkMan.FinishRunningWork(Host_work_ID.WORK_USEITEM); - } - } - } - } - + // // Calculate distance to an object and optionally retrieve the object reference // 计算到对象的距离,并可选地获取对象引用