2d85773f0e
separate class to partial class
961 lines
40 KiB
C#
961 lines
40 KiB
C#
using BrewMonster.Managers;
|
|
using BrewMonster.Network;
|
|
using BrewMonster.Scripts;
|
|
using BrewMonster.Scripts.World;
|
|
using CSNetwork;
|
|
using CSNetwork.GPDataType;
|
|
using System;
|
|
using UnityEngine;
|
|
using static BrewMonster.Scripts.CECHPWork;
|
|
using static CECPlayerWrapper;
|
|
|
|
namespace BrewMonster
|
|
{
|
|
public partial class CECHostPlayer
|
|
{
|
|
public void OnMsgHstAttackResult(ECMSG Msg)
|
|
{
|
|
byte[] data = Msg.dwParam1 as byte[];
|
|
cmd_host_attack_result pCmd = EC_Utility.ByteArrayToStructure<cmd_host_attack_result>(data);
|
|
|
|
int iAttackTime = 0;
|
|
TurnFaceTo(pCmd.idTarget);
|
|
|
|
PlayAttackEffect(pCmd.idTarget, 0, 0, pCmd.iDamage, (uint)pCmd.attack_flag, pCmd.attack_speed * 50,
|
|
ref iAttackTime);
|
|
|
|
if (iAttackTime != 0)
|
|
{
|
|
if (m_pWorkMan.GetRunningWork(CECHPWork.Host_work_ID.WORK_HACKOBJECT) is CECHPWorkMelee pCurWork)
|
|
{
|
|
pCurWork.SetIdleTime(iAttackTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnMsgHstAttacked(ECMSG Msg)
|
|
{
|
|
var m_pPlayerMan = EC_ManMessageMono.Instance.EC_ManPlayer;
|
|
cmd_host_attacked pCmd = GPDataTypeHelper.FromBytes<cmd_host_attacked>(Msg.dwParam1 as byte[]);
|
|
|
|
if (pCmd.iDamage != 0 && (pCmd.cEquipment & 0x7f) != 0x7f)
|
|
{
|
|
/* char cEquip = (char)(pCmd.cEquipment & 0x7f);
|
|
CECIvtrEquip pEquip = (CECIvtrEquip)m_pEquipPack.GetItem(cEquip);
|
|
if (pEquip)
|
|
pEquip.AddCurEndurance(ARMOR_RUIN_SPEED);*/
|
|
}
|
|
|
|
// The host player is attacked, we should make an effect here
|
|
if (GPDataTypeHelper.ISPLAYERID(pCmd.idAttacker))
|
|
{
|
|
EC_ElsePlayer pAttacker = m_pPlayerMan.GetElsePlayer(pCmd.idAttacker);
|
|
if (pAttacker)
|
|
{
|
|
if (!pAttacker.IsDead())
|
|
{
|
|
// Face to target
|
|
pAttacker.TurnFaceTo(GetPlayerInfo().cid);
|
|
}
|
|
|
|
int useless_attacktime = 0;
|
|
pAttacker.PlayAttackEffect(GetCharacterID(), 0, 0, pCmd.iDamage, (uint)pCmd.attack_flag,
|
|
pCmd.speed * 50, ref useless_attacktime);
|
|
pAttacker.EnterFightState();
|
|
}
|
|
}
|
|
else if (GPDataTypeHelper.ISNPCID(pCmd.idAttacker))
|
|
{
|
|
CECNPC pAttacker = EC_ManMessageMono.Instance.CECNPCMan.GetNPC(pCmd.idAttacker);
|
|
if (pAttacker)
|
|
{
|
|
pAttacker.OnMsgAttackHostResult(GetCharacterID(), pCmd.iDamage, pCmd.attack_flag, pCmd.speed);
|
|
}
|
|
}
|
|
|
|
//CECAutoPolicy::GetInstance().SendEvent_BeHurt(pCmd.idAttacker);
|
|
}
|
|
|
|
private void OnMsgHstHurtResult(ECMSG Msg)
|
|
{
|
|
//BMLogger.LogError("HoangDev : OnMsgHstHurtResult");
|
|
/* int cmd = Convert.ToInt32(Msg.dwParam2);
|
|
if (cmd == CommandID.BE_HURT)
|
|
{
|
|
cmd_be_hurt pCmd = (cmd_be_hurt)Msg.dwParam1;
|
|
if (pCmd.damage != 0)
|
|
Damaged(pCmd.damage);
|
|
}
|
|
else if (cmd == CommandID.HURT_RESULT)
|
|
{
|
|
cmd_hurt_result pCmd = (cmd_hurt_result)Msg.dwParam1;
|
|
if (pCmd.target_id == m_PlayerInfo.cid)
|
|
return; // Host himself will receive BE_HURT, so ignore this.
|
|
|
|
if (UnityGameSession.Instance.GameSession.ISPLAYERID(pCmd.target_id))
|
|
{
|
|
CECElsePlayer pTarget = m_pPlayerMan.GetElsePlayer(pCmd.target_id);
|
|
if (pTarget)
|
|
pTarget.Damaged(pCmd.damage);
|
|
}
|
|
else if (UnityGameSession.Instance.GameSession.ISNPCID(pCmd.target_id))
|
|
{
|
|
CECNPC pTarget = EC_ManMessageMono.Instance._CECNPCMan.GetNPC(pCmd.target_id);
|
|
if (pTarget)
|
|
pTarget.Damaged(pCmd.damage);
|
|
}
|
|
}*/
|
|
}
|
|
|
|
private void OnMsgHstStartAttack(in ECMSG Msg)
|
|
{
|
|
cmd_host_start_attack pCmd = GPDataTypeHelper.FromBytes<cmd_host_start_attack>((byte[])Msg.dwParam1);
|
|
|
|
// ASSERT(pCmd);
|
|
|
|
// test code...
|
|
// g_pGame.GetRTDebug().OutputNotifyMessage(RTDCOL_WARNING, _AL("start attack !"));
|
|
|
|
// Check whether target is the one that we have selected
|
|
if (m_idSelTarget != pCmd.idTarget)
|
|
// g_pGame.RuntimeDebugInfo(RTDCOL_WARNING, _AL("Target has changed !"));
|
|
|
|
// If target turn to be un-attackable, cancel action
|
|
if (AttackableJudge(pCmd.idTarget, true) == 0)
|
|
{
|
|
UnityGameSession.c2s_CmdCancelAction();
|
|
// g_pGame.RuntimeDebugInfo(RTDCOL_WARNING, _AL("Cannel attacking !"));
|
|
return;
|
|
}
|
|
|
|
// Synchronize ammo amount
|
|
// CECIvtrItem* pItem = m_pEquipPack.GetItem(EQUIPIVTR_PROJECTILE);
|
|
// if (pItem)
|
|
// {
|
|
// if (!pCmd.ammo_remain)
|
|
// m_pEquipPack.SetItem(EQUIPIVTR_PROJECTILE, NULL);
|
|
// else
|
|
// pItem.SetAmount(pCmd.ammo_remain);
|
|
// }
|
|
|
|
CECHPWorkMelee pWork = (CECHPWorkMelee)m_pWorkMan.CreateWork(Host_work_ID.WORK_HACKOBJECT);
|
|
m_pWorkMan.StartWork_p1(pWork);
|
|
|
|
m_bMelee = true;
|
|
// AP_ActionEvent(AP_EVENT_STARTMELEE);
|
|
}
|
|
|
|
private void OnMsgHstStopAttack(in ECMSG Msg)
|
|
{
|
|
// using namespace S2C;
|
|
//
|
|
m_bMelee = false;
|
|
//
|
|
// // if there is an attack event currently, we should let it fire now.
|
|
ClearComActFlagAllRankNodes(true);
|
|
|
|
cmd_host_stop_attack pCmd = GPDataTypeHelper.FromBytes<cmd_host_stop_attack>((byte[])Msg.dwParam1);
|
|
// ASSERT(pCmd);
|
|
|
|
/* If attack stopped for target is leave too far, trace it and continue
|
|
attacking. Stop reason defined as below:
|
|
|
|
0x00: attack is canceled or host want to do some other things.
|
|
0x01: unable to attack anymore (no ammo, weapon is broken etc.)
|
|
0x02: invalid target (target missed or died)
|
|
0x04: target is out of range
|
|
*/
|
|
if ((pCmd.iReason & 0x04) != 0)
|
|
{
|
|
if (!m_pWorkMan.IsMovingToPosition() &&
|
|
!m_pWorkMan.IsTracing())
|
|
{
|
|
if (CmdNormalAttack(false, false, 0, -1)) //m_pComboSkill != NULL
|
|
{
|
|
// AP_ActionEvent(AP_EVENT_MELEEOUTOFRANGE, 1);
|
|
}
|
|
else
|
|
{
|
|
m_pWorkMan.FinishRunningWork(Host_work_ID.WORK_HACKOBJECT);
|
|
// AP_ActionEvent(AP_EVENT_STOPMELEE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// AP_ActionEvent(AP_EVENT_STOPMELEE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pWorkMan.FinishRunningWork(Host_work_ID.WORK_HACKOBJECT);
|
|
// AP_ActionEvent(AP_EVENT_STOPMELEE);
|
|
}
|
|
|
|
// #ifdef _SHOW_AUTOPOLICY_DEBUG
|
|
// a_LogOutput(1, "Stop Attack, Reason = %d", pCmd.iReason);
|
|
// #endif
|
|
}
|
|
|
|
private void OnMsgHstSkillResult(ECMSG Msg)
|
|
{
|
|
cmd_host_skill_attack_result pCmd =
|
|
GPDataTypeHelper.FromBytes<cmd_host_skill_attack_result>((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 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<cmd_object_cast_skill>((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<cmd_object_cast_instant_skill>((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<cmd_object_cast_pos_skill>((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<cmd_player_cast_rune_skill>((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<cmd_player_cast_rune_instant_skill>((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);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnMsgHstTargetIsFar(ECMSG Msg)
|
|
{
|
|
// TO DO: fix later
|
|
//if(CmdNormalAttack(true, m_pComboSkill != null, 0, -1) )
|
|
// AP_ActionEvent(AP_EVENT_MELEEOUTOFRANGE, 1);
|
|
|
|
if (CmdNormalAttack(true, false, 0, -1))
|
|
{
|
|
//AP_ActionEvent(AP_EVENT_MELEEOUTOFRANGE, 1);
|
|
}
|
|
}
|
|
|
|
private void OnMsgHstDied(in ECMSG msg)
|
|
{
|
|
// Mark host player as corpse so CECPlayer.IsDead() returns true
|
|
m_dwStates |= (uint)PlayerNPCState.GP_STATE_CORPSE;
|
|
|
|
EventBus.PublishChannel(GetCharacterID(), new ClearComActFlagAllRankNodesEvent(true));
|
|
PlayAction((int)PLAYER_ACTION_TYPE.ACT_GROUNDDIE);
|
|
if (PopupManager.Instance != null)
|
|
{
|
|
PopupManager.Instance.OnPlayerDied();
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
if (bStartNewWork)
|
|
m_pWorkMan.StartWork_p1(pWorkTrace);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
// 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
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return iRet;
|
|
}
|
|
public bool CannotAttack()
|
|
{
|
|
return (m_dwLIES & (uint)Logic_Influence_Extned_states.LIES_DISABLEFIGHT) != 0;
|
|
}
|
|
public bool glb_GetForceAttackFlag(uint pdwParam)
|
|
{
|
|
/*bool bForceAttack = false;
|
|
CECInputCtrl* pInputCtrl = g_pGame.GetGameRun().GetInputCtrl();
|
|
|
|
if (pdwParam)
|
|
bForceAttack = pInputCtrl.IsCtrlPressed(*pdwParam);
|
|
else
|
|
bForceAttack = pInputCtrl.KeyIsBeingPressed(VK_CONTROL);
|
|
|
|
return bForceAttack;*/
|
|
return false;
|
|
}
|
|
// Start normal attacking to selected target
|
|
public bool CmdNormalAttack(bool bMoreClose /* false */, bool bCombo /* false */,
|
|
int idTarget /* 0 */, int iForceAtk /* -1 */)
|
|
{
|
|
// StackChecker::ACTrace(2);
|
|
|
|
// first of all see if we need to cancel sitdown work.
|
|
// if (m_pWorkMan.IsSitting())
|
|
// {
|
|
// g_pGame.GetGameSession().c2s_CmdStandUp();
|
|
// return false;
|
|
// }
|
|
|
|
if (!CanDo(ActionCanDo.CANDO_MELEE))
|
|
return false;
|
|
|
|
// if (InSlidingState())
|
|
// return false;
|
|
|
|
// if (!bCombo)
|
|
// ClearComboSkill();
|
|
|
|
if (idTarget <= 0)
|
|
idTarget = m_idSelTarget;
|
|
|
|
bool bForceAttack;
|
|
if (iForceAtk < 0)
|
|
bForceAttack = glb_GetForceAttackFlag(0);
|
|
else
|
|
bForceAttack = iForceAtk > 0 ? true : false;
|
|
|
|
if (AttackableJudge(idTarget, bForceAttack) != 1)
|
|
return false;
|
|
|
|
return NormalAttackObject(idTarget, bForceAttack, bMoreClose);
|
|
}
|
|
}
|
|
} |