using Games.AI_Logic; using Games.GlobeDefine; using Games.ImpactModle; using Games.SkillModle; using GCGame.Table; using Module.Log; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; namespace Games.LogicObj { /// /// 主角普攻和技能预判断模块 /// public partial class Obj_MainPlayer { // 自动寻路并且使用技能时,释放距离减少的长度 public const float skillCastOverwalk = skillCastReduce + 0.2f; // 客户端释放技能判定减少数值,注:普攻AoE类型技能无法用服务器补偿距离,因此统一客户端减损距离 public const float skillCastReduce = 0.5f; public readonly List simpleAttackSkills = new List(); // 缓存跑商时禁用的技能表 private int[] _skillsBannedByEscort; public int NextAttackIndex { get; private set; } //按顺序存储所有普攻 策划需求普攻技能ID顺序不固定 private void SetSimpleAttackId() { if (simpleAttackSkills.Count < 1) { var simpleSkills = new List(); for (var i = 0; i < OwnSkillInfo.Length; i++) { var skillInfo = OwnSkillInfo[i]; var skillBase = skillInfo.SkillBaseTable; if (skillBase != null && skillBase.SkillClass.ContainFlag((int)SKILLCLASS.SIMPLEATTACK | (int)SKILLCLASS.ACTIVE)) { var skillEx = CommonUtility.TryGetTable(skillInfo.SkillId, a => TableManager.GetSkillExByID(a, 0)); if (skillEx != null) simpleSkills.Add(skillInfo); } } simpleAttackSkills.Clear(); if (simpleSkills.Count == 1) { simpleAttackSkills.Add(simpleSkills[0]); } else if (simpleSkills.Count > 0) { // 对SimpleSkills进行链表排序,从最后一段开始反向选择 var previousSkill = simpleSkills.Find(a => a.SkillExTable.NextSkillId <= 0); if (previousSkill == null) LogModule.ErrorLog(string.Format("职业{0}普攻配置是无限循环!", Profession)); else while (previousSkill != null) { simpleAttackSkills.Add(previousSkill); previousSkill = simpleSkills.Find(a => a.ComboExTable.NextSkillId == previousSkill.ComboExTable.SkillExID); } simpleAttackSkills.Reverse(); } } } /// /// 试图使用一个普攻技能 /// public SkillCastableCheckResult TryStartSimpleAttack(bool breakAutoCombat = true) { var nextAttack = GetNextSimpleAttack(); if (nextAttack == null) return SkillCastableCheckResult.FailOther; return TryStartTargetSkill(nextAttack, breakAutoCombat: breakAutoCombat); } public override bool UseSkill(Tab_SkillEx skillEx, Tab_SkillBase skillBase, int firstSkillId, int targetId, Vector3 facePoint, bool skipWindup = false) { // 优先更新技能冷却时间,以服务器为准 UpdateSkillCooldown(firstSkillId); var result = base.UseSkill(skillEx, skillBase, firstSkillId, targetId, facePoint, skipWindup); // 无需预测连段的技能,就在释放成功后立刻解除锁定 //if (skillBase.IsPredict < 1) //RemovePredictSkill(skillEx.SkillExID); return result; } /// /// 使用失败的技能,是否可以立刻被再次使用 /// public bool CanFailedSkillBeUsed(SKILLUSEFAILTYPE failType) { return failType == SKILLUSEFAILTYPE.CE_IN_DEATH || failType == SKILLUSEFAILTYPE.CE_BACKSWINGCD1_NOT_COOL || failType == SKILLUSEFAILTYPE.CE_BACKSWINGCD2_NOT_COOL || failType == SKILLUSEFAILTYPE.CE_CUR_SKILL_CANNOT_BREAK || failType == SKILLUSEFAILTYPE.CE_MOUNT_FREEZE || failType == SKILLUSEFAILTYPE.CE_TARGET_IN_HIDING || failType == SKILLUSEFAILTYPE.CE_TARGET_ERROR; } /// /// 使用一个技能失败的接口 /// public void FailSkill(int skillExId, int firstSkillId, SKILLUSEFAILTYPE failType) { var skillEx = TableManager.GetSkillExByID(firstSkillId, 0); if (skillEx != null) { var canBeUsed = CanFailedSkillBeUsed(failType); // 如果是普攻失败,回退普攻连段 if (!canBeUsed) { for (var i = 0; i < simpleAttackSkills.Count; i++) if (simpleAttackSkills[i].IsValid() && simpleAttackSkills[i].SkillBaseTable.Id == skillEx.BaseId) NextAttackIndex = 0; } if (skillExId == firstSkillId) { // 多段技能中,仅仅第一段技能会回退连段 // 注:三段以上技能出错后会混乱,但是当前逻辑可以保证两段技能无误 var ownSkill = OwnSkillInfo.Find(a => a.IsValid() && a.SkillBaseTable.Id == skillEx.BaseId); if (ownSkill != null) { if (!canBeUsed) ownSkill.CleanComboState(); if (!ownSkill.SkillBaseTable.SkillClass.ContainFlag((int)SKILLCLASS.SIMPLEATTACK)) { // 记录使用技能出错的情况 var aiPlayerCombat = GetComponent(); if (aiPlayerCombat != null) aiPlayerCombat.AddSkillFailRecord(skillEx.BaseId, canBeUsed); } } RemovePredictByBaseId(skillEx.BaseId); } } } /// /// 试图使用一个目标指向技能 /// /// 技能数据 /// 技能目标位置 /// 是否打断自动战斗 /// 攻击目标 /// 技能是否使用成功 public SkillCastableCheckResult TryStartTargetSkill(OwnSkillData ownSkill, Vector2 attackPoint = default(Vector2), Obj_Character target = null, bool breakAutoCombat = true) { var castable = SkillCastableCheckResult.FailCasterDeath; if (!IsDie()) { castable = SkillCastableCheckResult.FailNoData; // 已经预判当前技能,等待上一个包收到回复;非自动连续技普攻会自动升段,防止降段后一段时间无法使用普攻 if (IsPredictSkill && PredictSkillBase.Id == ownSkill.ComboBaseTable.Id && (!PredictSkillBase.SkillClass.ContainFlag((int)SKILLCLASS.SIMPLEATTACK) || PredictSkillBase.SkillClass.ContainFlag((int)SKILLCLASS.AUTOREPEAT))) { castable = SkillCastableCheckResult.FailCurrent; } // 如果角色正试图使用该技能,则忽略这次输入 else if (ownSkill.ComboExTable != PendingSkillEx) { // 无论技能是否释放,使用技能都是玩家主动操作,都应该解除自动战斗 if (breakAutoCombat) BreakAutoCombatState(); // 检查特殊技能流程是否允许技能的使用 castable = SkillCastableCheckResult.Success; if (castable == SkillCastableCheckResult.Success) castable = CheckSkillCastable(ownSkill.SkillBaseTable, ownSkill.SkillExTable, ownSkill.IsCooldownFinish()); if (castable == SkillCastableCheckResult.Success) { // 非向目标释放技能,试图获得最适合目标 if (IsDisableState(DISABLESTATE.Disable_Select)) target = null; else if (target == null && !ownSkill.ComboExTableFinal.IsTargetGround()) target = GetSkillTarget(ownSkill.ComboBaseTable, ownSkill.ComboExTableFinal); if (target == null) { if (ownSkill.ComboBaseTable.IsReleaseWithoutTarget < 1) { // 技能不允许无目标释放,并且当前无目标时 GUIData.AddNotifyData(StrDictionary.GetClientDictionaryString("#{1246}")); castable = SkillCastableCheckResult.FailNoTarget; } } else if (target == enemyTarget && !ValidSkillPkCondition(ownSkill.ComboExTableFinal, ownSkill.ComboBaseTable, target)) { castable = SkillCastableCheckResult.FailPkProtection; } if (castable == SkillCastableCheckResult.Success) ProcessSkill(ownSkill, target, attackPoint); } } } return castable; } /// /// 检查一个技能是否可以被释放;该检查无视角色是否可以释放技能; /// public SkillCastableCheckResult CheckSkillCanBeCasted(Tab_SkillBase skillBase, Tab_SkillEx skillEx, bool isCDFinish) { if (skillBase == null || skillEx == null) return SkillCastableCheckResult.FailNoData; var result = SkillCastableCheckResult.Success; // 任务类型判定条件 if (IsInPaoShang()) { // 构造数据表缓存 if (_skillsBannedByEscort == null) { // Escort表是零散数据,应该是只有一列; var escortData = TableManager.GetEscortByID(1, 0); _skillsBannedByEscort = new int[escortData.getSkillLimitBaseIDCount()]; for (var i = 0; i < _skillsBannedByEscort.Length; i++) _skillsBannedByEscort[i] = escortData.GetSkillLimitBaseIDbyIndex(i); } if (_skillsBannedByEscort.Contain(skillBase.Id)) result = SkillCastableCheckResult.FailMission; } if (result == SkillCastableCheckResult.Success) { var sceneTable = CommonUtility.TryGetTable(GameManager.gameManager.RunningScene, a => TableManager.GetSceneClassByID(a, 0)); if (sceneTable != null && sceneTable.IsBanUseSkill > 0) result = SkillCastableCheckResult.FailScene; } if (result == SkillCastableCheckResult.Success) if (skillBase.SkillClass.ContainFlag((int)SKILLCLASS.AUTOREPEAT)) if (IsPredictSkill && IsSkillSequence(skillEx, PredictSkill) || IsUsingSkill && IsSkillSequence(skillEx, SkillCore.CurrentSkill)) result = SkillCastableCheckResult.FailAutoRepeat; if (result == SkillCastableCheckResult.Success) if (skillBase.SkillClass.ContainFlag((int)SKILLCLASS.SIMPLEATTACK)) { if (!SkillAllowSimpleAttack) result = SkillCastableCheckResult.FailOtherSkill; } // 技能判定条件 else { if (!SkillAllowSkill) result = SkillCastableCheckResult.FailOtherSkill; } if (result == SkillCastableCheckResult.Success) { // 检测技能冷却时间 if (!isCDFinish) result = SkillCastableCheckResult.FailCooldown; } // 检测消耗类型 if (result == SkillCastableCheckResult.Success) result = CheckSkillCost(skillEx, 0); if (result == SkillCastableCheckResult.Success) result = CheckSkillCost(skillEx, 1); // 检测单体技能情况 if (result == SkillCastableCheckResult.Success && skillBase.IsReleaseWithoutTarget < 1) if (IsDisableState(DISABLESTATE.Disable_Select)) result = SkillCastableCheckResult.FailSelect; return result; } /// /// 检查角色是否处于能够释放技能的状态;该检查无视技能本身是否可以释放; /// public SkillCastableCheckResult CheckCanCastSkill() { var result = SkillCastableCheckResult.Success; if (MovementState > MoveState.MainPlayMove) result = SkillCastableCheckResult.FailMovement; else if (IsDisableState(DISABLESTATE.Disable_UseSkill)) result = SkillCastableCheckResult.FailDisable; else if (SkillCore == null) result = SkillCastableCheckResult.FailNoCore; return result; } /// /// 检查角色是否可以释放某个技能;相当于CheckCanCastSkill + CheckSkillCanBeCasted; /// public SkillCastableCheckResult CheckSkillCastable(Tab_SkillBase skillBase, Tab_SkillEx skillEx, bool isCDFinish, bool showWarning = true) { var result = CheckCanCastSkill(); if (result == SkillCastableCheckResult.Success) result = CheckSkillCanBeCasted(skillBase, skillEx, isCDFinish); if (showWarning) switch (result) { case SkillCastableCheckResult.FailScene: GUIData.AddNotifyData(StrDictionary.GetClientDictionaryString("#{2997}")); break; case SkillCastableCheckResult.FailMission: GUIData.AddNotifyData(StrDictionary.GetClientDictionaryString("#{34001}")); break; case SkillCastableCheckResult.FailNoTarget: GUIData.AddNotifyData(StrDictionary.GetClientDictionaryString("#{1246}")); break; case SkillCastableCheckResult.FailCostHp: case SkillCastableCheckResult.FailCostHpRate: SendNoticMsg(false, "#{1247}"); break; case SkillCastableCheckResult.FailCostMp: case SkillCastableCheckResult.FailCostMpRate: SendNoticMsg(false, "#{1248}"); break; case SkillCastableCheckResult.FailCostXp: case SkillCastableCheckResult.FailCostXpRate: SendNoticMsg(false, "#{1244}"); break; case SkillCastableCheckResult.FailDisable: GUIData.AddNotifyData(StrDictionary.GetClientDictionaryString("#{1249}")); break; } return result; } /// /// 更新技能CD数值 /// public void UpdateSkillCooldown(int skillId) { var skillEx = TableManager.GetSkillExByID(skillId, 0); if (skillEx != null) { var ownSkill = OwnSkillInfo.Find(a => a.IsValid() && a.SkillBaseTable.Id == skillEx.BaseId); if (ownSkill != null) { if (ownSkill.ComboBaseTable.UseType.ContainFlag((int)SKILLUSETYPE.YINCHANG)) { } // 连段技能除最后一段外,不增加CD else if (ownSkill.FullCombo) { ownSkill.ResetCooldown(); //var nextSkill = OwnSkillInfo.Find(a => a.IsValid() && a.ComboBaseTable.Id == skillEx.BaseId); //if (nextSkill != null) // nextSkill.ResetCooldown(); } } } } /// /// 前技能连段技能中,是否拥有后技能 /// private bool IsSkillSequence(Tab_SkillEx a, Tab_SkillEx b) { var result = false; if (a.BaseId == b.BaseId) result = true; else while (a.NextSkillId > 0) { var skillEx = TableManager.GetSkillExByID(a.NextSkillId, 0); if (skillEx == null) break; else if (skillEx.BaseId == b.BaseId) { result = true; break; } else a = skillEx; } return result; } private SkillCastableCheckResult CheckSkillCost(Tab_SkillEx skillEx, int typeIndex) { var costType = (SKILLDELANDGAINTYPE)skillEx.GetDelTypebyIndex(typeIndex); var costValue = skillEx.GetDelNumbyIndex(typeIndex); return CheckForUseSkillNeed(costType, costValue); } private Obj_Character GetSkillTarget(Tab_SkillBase skillBase, Tab_SkillEx skillEx, int overrideTargetType = -1) { var targetType = overrideTargetType < 0 ? skillBase.PriorityTarget : overrideTargetType; var result = ValidClientTarget(targetType); if (result == null || result.IsDisableState(DISABLESTATE.Disable_BeSelect)) { result = SearchSkillTarget(skillBase, skillEx, targetType); } //// 如果是方向攻击技能,需要额外检查是否有更符合方向选择的目标 //else if (IsDirectionalAttack(skillBase) && !IsDirectionalAttackTarget(skillBase, result)) //{ // var candidate = SearchSkillTarget(skillBase, skillEx, targetType); // if (candidate != null && IsDirectionalAttackTarget(skillBase, candidate)) // result = candidate; //} return result; } /// /// 检测一个技能是否是方向索敌技能 /// private static bool IsDirectionalAttack(Tab_SkillBase skillBase) { return skillBase.SearchEnemyAngle > 0f; } /// /// 检测目标是否满足方向条件 /// private bool IsDirectionalAttackTarget(Tab_SkillBase skillBase, Obj_Character target) { var result = true; if (IsDirectionalAttack(skillBase)) { var targetDirection = target.Position - Position; targetDirection.y = 0f; if (targetDirection != Vector3.zero) result = Vector3.Angle(GetCurrentInputDirectional(), targetDirection) < skillBase.SearchEnemyAngle; } return result; } private Vector3 GetCurrentInputDirectional() { // 获得控制器输入方向 var direction = ProcessInput.Instance == null ? Vector3.zero : ProcessInput.Instance.GetCurrentInputDirectionInWorld(); // 控制器无输入方向时,使用默认角色方向 if (direction == Vector3.zero) direction = m_ObjTransform.forward; return direction; } /// /// 开始使用技能 /// public bool ProcessSkill(OwnSkillData ownSkill, Obj_Character target, Vector2 attackPoint = default(Vector2)) { CleanPendingSkill(); // 配置目标为当前选定目标; if (target != null) SetSelectTarget(target); // 是否可以立刻释放技能 var success = true; if (target != null) { success = ValidSkillRangeForNonDash(ownSkill.ComboExTableFinal, ownSkill.ComboBaseTable, target); if (success) { // 冲锋类技能检查阻挡,检查失败时,会直接执行空放 var moveImpact = ownSkill.ComboExTableFinal.GetMoveSkillImpact(); if (moveImpact != null) { if (!CanDashToTarget(moveImpact, target)) { attackPoint = target.Position.RemoveY() - Position.RemoveY(); target = null; } } } } if (success) StartSkillAction(ownSkill, target, attackPoint); else CreatePendingSkill(ownSkill, target); return success; } private bool StartSkillAction(OwnSkillData ownSkill, Obj_Character target, Vector2 attackPoint = default(Vector2)) { var skipTime = false; var skillEx = ownSkill.ComboExTableFinal; var skillBase = ownSkill.ComboBaseTable; // 普攻特殊处理连段和跳过前摇条件 if (skillBase.SkillClass.ContainFlag((int)SKILLCLASS.SIMPLEATTACK)) { // 提高普攻连段数目 NextAttackIndex++; if (NextAttackIndex > simpleAttackSkills.Count - 1) NextAttackIndex = 0; skipTime = Time.time < SkillCore.CurrentSkillTime + skillEx.ConnectCradleTime.ToClientTime(); } // 特殊技能自行处理特殊运作规则 SetPredictSkill(ownSkill, skipTime); // 不允许移动的技能,提前停止角色移动 if (skillBase.IsMove < 1) { StopMove(); // Hack:2018.08.17前 - 使用技能后,直接跳过停止帧,阻止Stop协议发出 // 然后在发送技能使用之前发送Stop协议 ResetNavAgentState(); // 2018.08.17按服务器需求,修改为使用技能跳过Stop包 //SendStopToServer(); } // 特殊:可以移动切移动打断的技能,锁定操作一段时间 else if (skillBase.IsMoveBreak >= 1) { LockJoyStick(); } SendUseSkillPacket(ownSkill, target, attackPoint); // 必须发送协议后再提升,否则会发送下一段的SkillId ownSkill.AddCombo(); return true; } /// /// 构造一个保证可以检定通过的TargetType /// /// /// 检查目标是否处于Pk保护状态 /// public bool ValidSkillPkCondition(Tab_SkillEx skillEx, Tab_SkillBase skillBase, Obj_Character target, bool showWarning = true) { return !skillBase.PriorityTarget.ContainFlag(CharacterDefine.SkillBaseTargetType.enemy) || ValidPkProtection(target, showWarning); } public bool ValidPkProtection(Obj_Character target, bool showWarning = true) { var result = true; if (target.ObjType == GameDefine_Globe.OBJ_TYPE.OBJ_OTHER_PLAYER) { var sceneData = TableManager.GetSceneClassByID(GameManager.gameManager.RunningScene, 0); if (sceneData != null) { var pvpRule = TableManager.GetPVPRuleByID(sceneData.PVPRule, 0); if (pvpRule != null) if (target.BaseAttr.Level < pvpRule.ProtectLevel) { result = false; if (showWarning) SendNoticMsg(false, "#{1112}"); } } } return result; } /// /// 检查目标是否处于技能射程之内 /// public bool ValidSkillRangeOnTarget(Tab_SkillEx skillEx, Tab_SkillBase skillBase, Obj_Character target, bool isSearch = false, float rangeReduce = -1f) { bool result; // 搜索范围不考虑寻路模型限制 if (isSearch) { if (rangeReduce < 0f) rangeReduce = 0f; var distance = DistanceToAnotherEdge(target); result = distance < skillEx.SearchRadius - rangeReduce; } else { result = ValidSkillRangeForNonDash(skillEx, skillBase, target, rangeReduce); if (result) { var moveImpact = skillEx.GetMoveSkillImpact(); if (moveImpact != null) result = CanDashToTarget(moveImpact, target); } } return result; } /// /// 检查非冲锋类技能是否可以攻击目标,不作冲锋阻挡判定 /// /// public bool ValidSkillRangeForNonDash(Tab_SkillEx skillEx, Tab_SkillBase skillBase, Obj_Character target, float rangeReduce = -1f) { var distance = DistanceToAnotherEdge(target); if (rangeReduce < 0f) rangeReduce = skillCastReduce; // 攻击距离不允许低于1f return distance < Mathf.Max(1f, skillEx.Radius - rangeReduce); } /// /// 检查地形是否允许冲锋类型技能到达目标 /// public bool CanDashToTarget(Tab_Impact moveImpact, Obj_Character target) { var result = true; NavMeshHit hit; if (NavMesh.Raycast(Position, target.Position, out hit, NavMesh.AllAreas)) { // 冲锋逻辑额外增加一个冲锋攻击距离 var attackRange = moveImpact.LogicID == GlobeVar.chargeSkillLogicId ? moveImpact.GetBaseParamValuebyIndex(2) * 0.01f : 0f; // 如果碰撞位置处于目标半径内,视为允许攻击 result = target.BaseAttr != null && target.BaseAttr.RoleData != null && (hit.position - target.Position).RemoveY().sqrMagnitude < (target.BaseAttr.RoleData.ModelRadius + attackRange).ToSquare(); } return result; } /// /// 按照SkillBase里面的TargetType定义处理角色关系 /// public static bool ValidTargetTypeBySkillBase(Obj_Character character, int targetType) { // 非激活角色不能作为目标 // 主角不是宠物,不可能有主人 var result = character.gameObject.activeSelf && !targetType.ContainFlag(CharacterDefine.SkillBaseTargetType.master); // 对齐角色死亡标签 if (result) result = targetType.ContainFlag(CharacterDefine.SkillBaseTargetType.die) == character.IsDie(); if (result) if (character.ObjType == GameDefine_Globe.OBJ_TYPE.OBJ_MAIN_PLAYER) { result = targetType.ContainFlag(CharacterDefine.SkillBaseTargetType.self); } else { // 队友标签 result = targetType.ContainFlag(CharacterDefine.SkillBaseTargetType.team) && GameManager.gameManager.PlayerDataPool.IsTeamMem(character.GUID); // 如果不是队友,才进行阵营判定 if (!result) result = targetType.ContainFlag(CharacterDefine.SkillBaseTargetType.friend) && Reputation.IsFriend(character) || targetType.ContainFlag(CharacterDefine.SkillBaseTargetType.enemy) && Reputation.CanAttack(character); } return result; } public static TargetSelectOrder GetTargetSelectOrder(Tab_SkillEx skillEx) { TargetSelectOrder result; var selector = skillEx.GetSkillSelector(); if (selector == null) result = TargetSelectOrder.Distance; else { switch (selector.GetRangParambyIndex(2)) { case 0: result = TargetSelectOrder.Distance; break; case 1: result = TargetSelectOrder.Angle; break; case 2: result = TargetSelectOrder.HealthRatio; break; case 3: result = TargetSelectOrder.Health; break; default: result = TargetSelectOrder.Distance; break; } } return result; } public float GetWeightByOrder(Obj_Character character, TargetSelectOrder selectOrder) { float result; switch (selectOrder) { case TargetSelectOrder.Distance: result = DistanceToAnotherEdge(character); break; case TargetSelectOrder.Angle: result = Vector2.Angle(m_ObjTransform.forward.RemoveY(), (character.Position - Position).RemoveY()); break; case TargetSelectOrder.HealthRatio: { var baseAttr = character.BaseAttr; if (baseAttr == null || baseAttr.CurMaxHP <= 0) result = float.MaxValue; else result = (float)baseAttr.HP / baseAttr.CurMaxHP; } break; case TargetSelectOrder.Health: { var baseAttr = character.BaseAttr; result = baseAttr == null ? float.MaxValue : baseAttr.HP; } break; default: result = 0f; break; } return result; } public Obj_Character SearchSkillTarget(Tab_SkillBase skillBase, Tab_SkillEx skillEx, int overrideTargetType = -1) { Obj_Character result = null; var targetType = overrideTargetType < 0 ? skillBase.PriorityTarget : overrideTargetType; var candidates = new List(); if (ValidTargetTypeBySkillBase(this, targetType)) candidates.Add(this); var targets = Singleton.GetInstance().ObjPools; foreach (var keyValue in targets) { var obj = keyValue.Value as Obj_Character; if (obj != null && obj != this && obj.ServerID != ServerID && !obj.isInvisible && !obj.IsDisableState(DISABLESTATE.Disable_BeSelect) && ValidTargetTypeBySkillBase(obj, targetType) && ValidSkillRangeOnTarget(skillEx, skillBase, obj, true)) candidates.Add(obj); } var targetOrder = GetTargetSelectOrder(skillEx); // 上一个目标权重值 var lastWeight = float.PositiveInfinity; // 上一个目标是否处于直接距离 var lastInRange = false; for (var i = 0; i < candidates.Count; i++) { var obj = candidates[i]; var inRange = ValidSkillRangeOnTarget(skillEx, skillBase, candidates[i]); // 优先选择可以原地释放的目标 if (!lastInRange || inRange) { var weight = GetWeightByOrder(obj, targetOrder); if (weight < lastWeight) { result = obj; lastWeight = weight; lastInRange = inRange; } } } return result; } /// /// 获得普攻技能的Ex /// public OwnSkillData GetFirstSimpleAttack() { OwnSkillData result = null; SetSimpleAttackId(); if (simpleAttackSkills.Count > 0) result = simpleAttackSkills[0]; return result; } /// /// 获得下一次普攻的技能Ex /// public OwnSkillData GetNextSimpleAttack() { // 注:应该只需要在这个位置初始化普攻Id // 无论任何逻辑,都不应该在不知道下一刀普攻技能的情况下调用普攻 SetSimpleAttackId(); OwnSkillData result = null; if (simpleAttackSkills.Count > 0) { // 如果是自动连续技,则直接选中第一个普攻 if (simpleAttackSkills[0].SkillBaseTable.SkillClass.ContainFlag((int)SKILLCLASS.AUTOREPEAT)) NextAttackIndex = 0; // 循环一圈回到第一段 else if (NextAttackIndex >= simpleAttackSkills.Count) NextAttackIndex = 0; // 如果是连段攻击,则检查是否满足连段条件 if (NextAttackIndex > 0) if (IsPredictSkill && PredictSkillBase.SkillClass.ContainFlag((int)SKILLCLASS.SIMPLEATTACK) && PredictSkill.NextSkillId == simpleAttackSkills[NextAttackIndex].SkillId) { // 普攻可连段时间超过 // 超过平砍连接时间 if (Time.time > PredictSkillTime + PredictSkill.ConnectNextSkillTime.ToClientTime()) NextAttackIndex = 0; } // 上次技能不是普攻,重置 else { NextAttackIndex = 0; } result = simpleAttackSkills[NextAttackIndex]; } return result; } public override void EndSkillByBaseId(int baseId = -1) { base.EndSkillByBaseId(baseId); // 非需要保持预测的技能,就直接关闭 if (PredictSkillBase != null && PredictSkillBase.IsPredict < 1) RemovePredictByBaseId(PredictSkillBase.Id); } private void SendUseSkillPacket(OwnSkillData ownSkill, Obj_Character target, Vector2 attackPoint) { //发送技能使用包 // 之前逻辑是在发送技能使用数据包时,计算对应的数据,先保留这个结构 var useSkill = (CG_SKILL_USE)PacketDistributed.CreatePacket(MessageID.PACKET_CG_SKILL_USE); useSkill.SetSkillId(ownSkill.ComboExTable.SkillExID); //if (ownSkill.IsComboSkill()) // useSkill.SetFirststagekillid(ownSkill.SkillId); var skillEx = ownSkill.ComboExTableFinal; var skillBase = ownSkill.ComboBaseTable; int dirRadian; Vector2 worldAttackPoint; // 在有目标的情况下,攻击位置以目标位置为准 if (target != null) { // 目标等于自己时,面朝方向配置为当前面向 if (target == this) { worldAttackPoint = Vector2.zero; dirRadian = CommonUtility.DirToServerRadian(m_ObjTransform.forward.RemoveY()); } // 目标不等于自己时,配置攻击方向为目标位置 else { worldAttackPoint = target.Position.RemoveY(); var attackDir = worldAttackPoint - Position.RemoveY(); dirRadian = CommonUtility.DirToServerRadian(attackDir); } } else { // 校正普攻空放和冲锋技能 var isChargeForward = skillEx.MaxMoveDistance > 0 || skillEx.IsMoveSkill(); var isTargetGround = skillEx.IsTargetGround(); // 冲锋类和对地攻击类技能使用目标位置作为攻击点 if (isTargetGround || isChargeForward) { var selector = skillEx.GetSkillSelector(); var isLine = selector != null && selector.RangType == 3; // 直线或者冲锋会补正AttackPoint到最大值 if (isLine || isChargeForward) { if (attackPoint == default(Vector2)) attackPoint = IsDirectionalAttack(skillBase) ? GetCurrentInputDirectional().RemoveY() : ObjTransform.forward.RemoveY(); worldAttackPoint = Position.RemoveY(); // 陷阱类带冲锋属性直接使用落地位置;非陷阱类补正到最大攻击距离; if (skillEx.MaxMoveDistance > 0) worldAttackPoint += attackPoint * skillEx.MaxMoveDistance * 0.01f; else if (selector != null && selector.RangCenter >= 1) worldAttackPoint += attackPoint; else worldAttackPoint += attackPoint.normalized * skillEx.Radius; // 冲锋额外使用行走区域碰撞进行补正 if (isChargeForward) { NavMeshHit hit; if (NavMesh.Raycast(Position, worldAttackPoint.InsertY(Position.y), out hit, NavMesh.AllAreas)) worldAttackPoint = hit.position.RemoveY(); } } else worldAttackPoint = Position.RemoveY() + attackPoint; var dir = worldAttackPoint - Position.RemoveY(); if (dir == default(Vector2)) dir = attackPoint; dirRadian = CommonUtility.DirToServerRadian(dir); } // 非陷阱非冲锋不上传攻击目标点 else { worldAttackPoint = Vector2.zero; if (attackPoint == default(Vector2)) attackPoint = IsDirectionalAttack(skillBase) ? GetCurrentInputDirectional().RemoveY() : ObjTransform.forward.RemoveY(); dirRadian = CommonUtility.DirToServerRadian(attackPoint); } } // 快速处理一个关于治疗链的问题留下的坑。 // 当前目标为自己时,不发送目标 if (target == this) target = null; useSkill.SetDesposx(Mathf.FloorToInt(worldAttackPoint.x * 100f)); useSkill.SetDespoxz(Mathf.FloorToInt(worldAttackPoint.y * 100f)); useSkill.SetFacedir(dirRadian); useSkill.SetTargetId(target == null ? 0 : target.ServerID); useSkill.SetRoleposx(Position.x.ToServerPos()); useSkill.SetRoleposy(Position.y.ToServerPos()); useSkill.SetRolepoxz(Position.z.ToServerPos()); useSkill.SendPacket(); } /// /// 计算两个角色之间的距离,会减去对方体型半径,不减自己半径 /// // 该方法仅主角可以使用,检查两个非主角应该都是用ServerPos public float DistanceToAnotherEdge(Obj_Character character) { var result = Vector2.Distance(character.Position.RemoveY(), Position.RemoveY()); if (character.BaseAttr != null && character.BaseAttr.RoleData != null) result = Mathf.Max(0f, result - character.BaseAttr.RoleData.ModelRadius); return result; } #region 寻路到目标再释放技能 private bool CreatePendingSkill(OwnSkillData ownSkill, Obj_Character target) { var result = false; // 自动战斗期间,不允许叠加其他自动逻辑 // 原始结构修改需要改一些架构,暂时用这个标志位检查处理 if (!isAutoCombat) { // 特殊处理 - 如果是普攻技能,降低到第一段普攻的距离标准 if (ownSkill.SkillBaseTable.SkillClass.ContainFlag((int)SKILLCLASS.SIMPLEATTACK)) ownSkill = GetFirstSimpleAttack(); if (MainPlayMoveToTarget(target, new MoveToTargetTillSkill(ownSkill), new MoveSuccessToUseSkill(this, ownSkill))) { result = true; PendingSkillEx = ownSkill.ComboExTable; } else { CleanPendingSkill(); } } return result; } // 注:PendingSkill使用不修正的SkillEx public Tab_SkillEx PendingSkillEx { get; private set; } // 注:打断行为的操作,应该同时覆盖行为 private void CleanPendingSkill() { PendingSkillEx = null; } #endregion // 注:主角除了SkillCore以外,自身会额外预判技能使用条件 // 自身预判依照技能协议被发出时机为准,然后使用协议返回数值予以校正 // 因此玩家连招输入频率不受到稳定ping值的影响 // 预判断仅仅影响玩家输入指令,不影响角色对指令的响应 #region 主角预判断技能状态记录 // 预判断使用的技能 // 预判使用等级修正后技能 public Tab_SkillEx PredictSkill { get; private set; } // 预判断使用的技能BaseInfo public Tab_SkillBase PredictSkillBase { get; private set; } // 预判断技能开始时间 public float PredictSkillTime { get; private set; } // 是否预判断使用技能 public bool IsPredictSkill { get { return PredictSkill != null && PredictSkillTime + SkillDuration(PredictSkill, PredictSkillBase) > Time.time; } } private void SetPredictSkill(OwnSkillData ownSkill, bool skipWindup) { PredictSkillTime = Time.time - (skipWindup ? ownSkill.ComboExTableFinal.CradleTime.ToClientTime() : 0f); PredictSkill = ownSkill.ComboExTableFinal; PredictSkillBase = ownSkill.ComboBaseTable; } private void RemovePredictByBaseId(int baseId = -1) { if (PredictSkill != null && (baseId < 0 || baseId == PredictSkill.BaseId)) { PredictSkill = null; PredictSkillBase = null; } } /// /// 技能状态是否允许移动 /// // 技能衔接移动的条件,由SkillCore为准;Predict数值仅保证协议返回前不被主角打断 public override bool SkillAllowMove { get { if (FollowState == TeamFollowState.followMove) { return base.SkillAllowMove; } var result = !IsUsingSkill || SkillCore.CurrentSkillBase.IsMove > 0 || Time.time > SkillToSimpleDelay(SkillCore.CurrentSkill, SkillCore.CurrentSkillBase) + SkillCore.CurrentSkillTime; if (result) result = PredictSkill == null || PredictSkillBase.IsMove > 0 || Time.time > SkillToSimpleDelay(PredictSkill, PredictSkillBase) + PredictSkillTime; return result; } } /// /// 技能状态是否允许角色进行普攻 /// public bool SkillAllowSimpleAttack { get { if (IsPredictSkill) if (PredictSkillBase.IsMove > 0) return false; else return Time.time > SkillToSimpleDelay(PredictSkill, PredictSkillBase) + PredictSkillTime; return true; } } /// /// 技能状态是否允许角色释放技能 /// public bool SkillAllowSkill { get { if (IsPredictSkill) return Time.time > SkillToSkillDelay(PredictSkill, PredictSkillBase) + PredictSkillTime; return true; } } #endregion } /// /// 技能是否可以释放检查结果 /// public enum SkillCastableCheckResult { Success, // 可以释放 FailCasterDeath, // 技能释放者已死亡 FailCurrent, // 已经预测当前技能 FailNoData, // 技能数据无法找到 FailCooldown, // 技能处于CD中 FailDisable, // 角色不能释放技能 FailSelect, // 角色不能选定目标 FailOtherSkill, // 其他技能阻止释放 FailNoCore, // 没有SkillCore FailNoOwnSkill, // 没有服务器返回的技能注册 FailMission, // 任务期间不允许使用该技能 FailNoTarget, // 单体选定技能没有目标的情况 FailAutoRepeat, // 自动连续技能不允许打断自己的后续 FailPkProtection, // 不允许攻击PK保护的目标 FailCostHp, // 技能生命消耗不足 FailCostHpRate, // 技能生命百分比消耗不足 FailCostMp, // 技能法力消耗不足 FailCostMpRate, // 技能法力百分比消耗不足 FailCostXp, // 技能Xp消耗不足 FailCostXpRate, // 技能Xp百分比消耗不足 FailMovement, // 特殊移动状态不允许使用技能 FailScene, // 当前场景不允许释放技能 FailOther // 其他情况 } /// /// 技能目标选择顺序 /// public enum TargetSelectOrder { Distance, // 最小距离优先 Angle, // 最小角度优先 HealthRatio, // 生命百分比最低优先 Health, // 生命绝对值最低优先 } }