Files
JJBB/Assets/Project/Script/Obj_MainPlayer_Attack.cs
2024-08-23 15:49:34 +08:00

1115 lines
48 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
{
/// <summary>
/// 主角普攻和技能预判断模块
/// </summary>
public partial class Obj_MainPlayer
{
// 自动寻路并且使用技能时,释放距离减少的长度
public const float skillCastOverwalk = skillCastReduce + 0.2f;
// 客户端释放技能判定减少数值普攻AoE类型技能无法用服务器补偿距离因此统一客户端减损距离
public const float skillCastReduce = 0.5f;
public readonly List<OwnSkillData> simpleAttackSkills = new List<OwnSkillData>();
// 缓存跑商时禁用的技能表
private int[] _skillsBannedByEscort;
public int NextAttackIndex { get; private set; }
//按顺序存储所有普攻 策划需求普攻技能ID顺序不固定
private void SetSimpleAttackId()
{
if (simpleAttackSkills.Count < 1)
{
var simpleSkills = new List<OwnSkillData>();
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();
}
}
}
/// <summary>
/// 试图使用一个普攻技能
/// </summary>
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;
}
/// <summary>
/// 使用失败的技能,是否可以立刻被再次使用
/// </summary>
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;
}
/// <summary>
/// 使用一个技能失败的接口
/// </summary>
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<AI_PlayerCombat>();
if (aiPlayerCombat != null)
aiPlayerCombat.AddSkillFailRecord(skillEx.BaseId, canBeUsed);
}
}
RemovePredictByBaseId(skillEx.BaseId);
}
}
}
/// <summary>
/// 试图使用一个目标指向技能
/// </summary>
/// <param name="ownSkill">技能数据</param>
/// <param name="attackPoint">技能目标位置</param>
/// <param name="breakAutoCombat">是否打断自动战斗</param>
/// <param name="target">攻击目标</param>
/// <returns>技能是否使用成功</returns>
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;
}
/// <summary>
/// 检查一个技能是否可以被释放;该检查无视角色是否可以释放技能;
/// </summary>
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;
}
/// <summary>
/// 检查角色是否处于能够释放技能的状态;该检查无视技能本身是否可以释放;
/// </summary>
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;
}
/// <summary>
/// 检查角色是否可以释放某个技能相当于CheckCanCastSkill + CheckSkillCanBeCasted
/// </summary>
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;
}
/// <summary>
/// 更新技能CD数值
/// </summary>
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();
}
}
}
}
/// <summary>
/// 前技能连段技能中,是否拥有后技能
/// </summary>
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;
}
/// <summary>
/// 检测一个技能是否是方向索敌技能
/// </summary>
private static bool IsDirectionalAttack(Tab_SkillBase skillBase)
{
return skillBase.SearchEnemyAngle > 0f;
}
/// <summary>
/// 检测目标是否满足方向条件
/// </summary>
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;
}
/// <summary>
/// 开始使用技能
/// </summary>
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();
// Hack2018.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;
}
/// <summary>
/// 构造一个保证可以检定通过的TargetType
/// </summary>
/// <summary>
/// 检查目标是否处于Pk保护状态
/// </summary>
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;
}
/// <summary>
/// 检查目标是否处于技能射程之内
/// </summary>
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;
}
/// <summary>
/// 检查非冲锋类技能是否可以攻击目标,不作冲锋阻挡判定
/// </summary>
/// <returns></returns>
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);
}
/// <summary>
/// 检查地形是否允许冲锋类型技能到达目标
/// </summary>
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;
}
/// <summary>
/// 按照SkillBase里面的TargetType定义处理角色关系
/// </summary>
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<Obj_Character>();
if (ValidTargetTypeBySkillBase(this, targetType))
candidates.Add(this);
var targets = Singleton<ObjManager>.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;
}
/// <summary>
/// 获得普攻技能的Ex
/// </summary>
public OwnSkillData GetFirstSimpleAttack()
{
OwnSkillData result = null;
SetSimpleAttackId();
if (simpleAttackSkills.Count > 0)
result = simpleAttackSkills[0];
return result;
}
/// <summary>
/// 获得下一次普攻的技能Ex
/// </summary>
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();
}
/// <summary>
/// 计算两个角色之间的距离,会减去对方体型半径,不减自己半径
/// </summary>
// 该方法仅主角可以使用检查两个非主角应该都是用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;
}
}
/// <summary>
/// 技能状态是否允许移动
/// </summary>
// 技能衔接移动的条件由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;
}
}
/// <summary>
/// 技能状态是否允许角色进行普攻
/// </summary>
public bool SkillAllowSimpleAttack
{
get
{
if (IsPredictSkill)
if (PredictSkillBase.IsMove > 0)
return false;
else
return Time.time > SkillToSimpleDelay(PredictSkill, PredictSkillBase) + PredictSkillTime;
return true;
}
}
/// <summary>
/// 技能状态是否允许角色释放技能
/// </summary>
public bool SkillAllowSkill
{
get
{
if (IsPredictSkill)
return Time.time > SkillToSkillDelay(PredictSkill, PredictSkillBase) + PredictSkillTime;
return true;
}
}
#endregion
}
/// <summary>
/// 技能是否可以释放检查结果
/// </summary>
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 // 其他情况
}
/// <summary>
/// 技能目标选择顺序
/// </summary>
public enum TargetSelectOrder
{
Distance, // 最小距离优先
Angle, // 最小角度优先
HealthRatio, // 生命百分比最低优先
Health, // 生命绝对值最低优先
}
}