local SkillBaseSystem = require "Battle/Logic/Entity/System/SkillBaseSystem" local SkillMissileSystem = require"Battle/Logic/Entity/System/SkillMissileSystem" local BuffSkillSystem = require"Battle/Logic/Entity/System/BuffSkillSystem" local EventSystem = require('Event/EventSystem') local Decision_Base = class("Decision_Base") local private = {} local abs = math.abs local newV1=FP.newV1 local newV2=FP.newV2 local tonumberV2=FP.tonumberV2 local Const_Vector2_Zero= FP.Const.Vector2_Zero local LessEquals=FP.LessEquals local Math_Abs=FP.Math.Abs local tonumberV1=FP.tonumberV1 local V2_DistanceSqr=FP.V2.DistanceSqr local Const_One=FP.Const.One local Const_Zero = FP.Const.Zero local _tempResultV2 = newV2(0, 0) --子类不需要访问的,搞成local local SMALL_DISTANCE_AXIS_DIFF = newV1(0.35) local RANDOM_WANDER_POS_KEY = "randomWanderPos" --随机巡游位置 local DEFAULT_FIND_PATH_INTERVAL = 2000 --默认寻路间隔 local ON_FOOT_TO_RIDE_INTERVAL_FRAME = math.floor( FP.tonumberV1(DataSheetManager.ConstData:GetDataByID("HorseAutoRideTime_Battle").FloatValue * 20) ) --40 --步行切骑马的间隔帧数 local SPLITOR_SEG = "|" local SPLITOR_COMMA = "," local LuaConst=LuaConst local DODGE_KEYPOS = LuaConst.ESkillKeyPosition.Dodge --[[ 初始化 --]] function Decision_Base:initialize(entity) --类型 self.Type = LuaConst.DecisionType.None --子类需要访问的,搞成成员 self.MIN_USESKILL_DECISION_CD_KEY = "minUseSkillDecisionCD" self.MAX_USESKILL_DECISION_CD_KEY = "maxUseSkillDecisionCD" self.USESKILL_CD_OVER_FRAME = "useSkillCDOverFrame" --使用技能CD结束的帧(可以使用技能的帧) self.RESET_WAITINGSKILL_FRAME_INTERVAL = 60 --间隔多少帧重设一次waitingSkill(用于CommandChaseUseSkill方法) --瞬时 self.IsOnce = false end function Decision_Base:GetSkillCDDataSheet() logging.error(LTAG.ERROR, "HYW : 未实现的方法 : GetSkillCDDataSheet ") end function Decision_Base:InitSkillCDInfo() --一般是各个玩法的机器人AI的initialize方法里需要调这个,用来初始化AI释放技能的间隔档位数据 self.eloSkillCDInfo = {} --不同elo有不同的技能释放decision cd local id = 1 local datasheet = self:GetSkillCDDataSheet() if datasheet then local data = datasheet:GetDataByLevelID(id) local minElo = -9999999 local isSuccess = false while data do local info = {} info.minCd = data.RobertSkillMinCD info.maxCd = data.RobertSkillMaxCD info.warmUpMinCd = data.WarmRobertSkillMinCD info.warmUpMaxCd = data.WarmRobertSkillMaxCD info.dodgeReactionTimeLow = data.DodgeReactionTimeLow info.dodgeReactionTimeHigh = data.DodgeReactionTimeHigh info.dodgeReactionChance = data.DodgeReactionChance info.switchReactionTimeLow = data.SwitchReactionTimeLow info.switchReactionTimeHigh = data.SwitchReactionTimeHigh info.switchReactionChance = data.SwitchReactionChance info.offsetSkillProbability = data.OffsetSkillProbability -- 释放技能时产生随机偏移的概率 info.offsetSkillRange = data.OffsetSkillRange -- 随机偏移的范围 info.minElo = minElo id = id + 1 isSuccess, data = datasheet:TryGetDataByLevelID(id) if isSuccess then minElo = data.LevelScore info.maxElo = data.LevelScore else info.maxElo = 9999999 end table.insert(self.eloSkillCDInfo,info) end end end function Decision_Base:SetSkillCDByElo(entity, decisionRecordDic, isWarmUpRobot) local elo = entity._logicInitData.elo or 0 if LevelManager.CurLevelFunc.GetRobotAIElo then -- 有些玩法需要单独的规则设置elo值 elo = LevelManager.CurLevelFunc:GetRobotAIElo(entity) else --策划需求,如果是机器人,elo要取对面玩家的elo↓ if entity._logicInitData.BattleMemberType == LuaConst.BattleMemberType.Robot_Hero then local campID = entity:GetCampID() for i,_entity in ipairs(LogicUpdateManager.EntityList_Sync) do if _entity:GetCampID() ~= campID then elo = _entity._logicInitData.elo or 0 break end end end --↑ end for i,info in ipairs(self.eloSkillCDInfo) do if info.minElo <= elo and info.maxElo > elo then decisionRecordDic[self.MIN_USESKILL_DECISION_CD_KEY] = isWarmUpRobot and info.warmUpMinCd or info.minCd --warmUpMinCd可能为nil,此时返回info.minCd,这个表达式依然符合预期 decisionRecordDic[self.MAX_USESKILL_DECISION_CD_KEY] = isWarmUpRobot and info.warmUpMaxCd or info.maxCd --warmUpMaxCd可能为nil,此时返回info.maxCd,这个表达式依然符合预期 decisionRecordDic.dodgeReactionTimeLow = info.dodgeReactionTimeLow decisionRecordDic.dodgeReactionTimeHigh = info.dodgeReactionTimeHigh decisionRecordDic.dodgeReactionChance = info.dodgeReactionChance decisionRecordDic.switchReactionTimeLow = info.switchReactionTimeLow decisionRecordDic.switchReactionTimeHigh = info.switchReactionTimeHigh decisionRecordDic.switchReactionChance = info.switchReactionChance decisionRecordDic.offsetSkillProbability = info.offsetSkillProbability -- 释放技能时产生随机偏移的概率 decisionRecordDic.offsetSkillRange = info.offsetSkillRange -- 随机偏移的范围 break end end end function Decision_Base:CommonRefreshConditionalSkillInterval(decisionRecordDic, entity) if decisionRecordDic.__skillMinInterval and decisionRecordDic.__skillMaxInterval then decisionRecordDic.__skillInterval = LogicRandom.GetIntegerValue(entity, decisionRecordDic.__skillMinInterval, decisionRecordDic.__skillMaxInterval, 443543) decisionRecordDic.__skillTimeEla = 0 end end --通用条件技能字符串解析,字符串格式为 技能ID|释放技能条件字串|最小随机间隔,最大随机间隔。其中释放 技能条件字串、最小随机间隔,最大随机间隔 是可选参数可以为空 function Decision_Base:CommonConditionalSkillStringParamParse(decisionData, decisionRecordDic, entity) if decisionData.StringParam1 ~= "" then local splits = string.split(decisionData.StringParam1, SPLITOR_SEG) decisionRecordDic.__skillIndex = tonumber(splits[1]) if splits[2] then decisionRecordDic.__skillConditionTree = ConditionManager.GetConditionDataTree(splits[2]) end if splits[3] then local commaSplits = string.split(splits[3], SPLITOR_COMMA) if #commaSplits >= 2 then decisionRecordDic.__skillMinInterval = tonumber(commaSplits[1]) decisionRecordDic.__skillMaxInterval = tonumber(commaSplits[2]) self:CommonRefreshConditionalSkillInterval(decisionRecordDic, entity) end end end end --[[ 进入该Decision --]] function Decision_Base:DecisionIn(entity, decisionData) local decisionRecordDic = entity.components.aiBase:GetDecisionRecordDic(decisionData) if decisionRecordDic then decisionRecordDic.decisionInFrame = LogicUpdateManager._clientHandleFrame if entity:IsHero() then decisionRecordDic.onFootFrameCount = 0 if LevelPlayerManager.GetPlayerSetting(entity._logicInitData.PlayerIDInRoom ,LuaConst.BattleSettingEnum.AutoRide) then decisionRecordDic.dontAutoRide = false else decisionRecordDic.dontAutoRide = true end end end end --[[ 退出该Decision --]] function Decision_Base:DecisionOut(entity, decisionData) entity.components.aiBase:ClearDecisionRecord(decisionData) end --[[ 能否进入 --]] function Decision_Base:CanEnter(entity, decisionData, command) return true end --[[ 能否退出 --]] function Decision_Base:CanExit(entity, decisionData, command) return true end --[[ 输出指令 --]] function Decision_Base:SetCommand(entity, decisionData, command) return command end function Decision_Base:ShouldBlockAI() return LevelItemManager.IsShowingTimeline() or LevelItemManager.IsShowingDialogue() or LevelItemManager.IsShowingMovie() end --[[ 执行 --]] function Decision_Base:Handle(entity, decisionData, stateType, animName) return LuaConst.StateType.None,nil end --根据CommonConditionalSkillStringParamParse解析出来的信息进行随机间隔的释放技能,必须配置指定格式的StringParam1才能使用这个方法,具体格式见CommonConditionalSkillStringParamParse的说明 -- 注意 :timeEla 是毫秒 function Decision_Base:CommonIntervalUseConditionalSkill(decisionRecordDic, entity, timeEla, command) if decisionRecordDic.__skillIndex then if decisionRecordDic.__skillInterval == nil or decisionRecordDic.__skillTimeEla >= (decisionRecordDic.__skillInterval * 1000) then -- __skillInterval 单位是 秒 if decisionRecordDic.__skillConditionTree == nil or ConditionManager.MeetCondition(entity, decisionRecordDic.__skillConditionTree) then command.SkillIndex = decisionRecordDic.__skillIndex self:CommonRefreshConditionalSkillInterval(decisionRecordDic, entity) end end if decisionRecordDic.__skillTimeEla then -- 单位是ms decisionRecordDic.__skillTimeEla = decisionRecordDic.__skillTimeEla + timeEla -- timeEla 是毫秒 end end end local _tempV1 = FP.newV1(0) local _tempV2 = FP.newV2(0, 0) --间隔interval寻一次路,并RunToPathPoint。单位:毫秒 -- taget:寻路到的单位,优先级比position高 -- position:寻路位置,优先级比target低 -- priorityWall: 不忽略优先级墙,优先级墙是高priority的单位占的格子 -- sideOffset : 横向偏移(指垂直于本体朝着目标位置指过去的向量的方向上的偏移,正数代表右手边,负数代表左手边) function Decision_Base:IntervalFindPath(entity, decisionData, command,interval,target, position, priorityWall, sideOffsetMagnitude) if target then position = target:GetLogicPos() end if interval == nil or interval <= 0 then interval = DEFAULT_FIND_PATH_INTERVAL end if position then position = position:cloneV2() local lastFindPathFrame = entity.components.aiBase:GetDecisionRecord(decisionData,"lastFindPathFrame") or -99999 --每隔interval重新寻一次路 local passFrame = LogicUpdateManager._clientHandleFrame - lastFindPathFrame local passTime = passFrame * LuaConst.TimeSpanMs if passTime >= interval then --#处理召唤物的排布------------------------------ if target and target:IsMasterEntity(entity) then local entityAdoptedIndex = entity.components.base:GetAdoptedIndex() if entityAdoptedIndex then local masterEntity = entity:GetMasterEntity() if not masterEntity then return end local siblingCount = masterEntity.components.base:GetDominatedEntityCount() if siblingCount > 1 then local offsetScale = 0 if siblingCount % 2 == 0 then if entityAdoptedIndex <= siblingCount / 2 then offsetScale = -(siblingCount / 2 - entityAdoptedIndex) - 1 else offsetScale = entityAdoptedIndex - siblingCount / 2 end else offsetScale = entityAdoptedIndex - (siblingCount + 1) / 2 end local offset = FP.newV2(1.5, 0):Mul(offsetScale) position:Add(offset) end end end --处理横向偏移------------------------------ if sideOffsetMagnitude and sideOffsetMagnitude ~= 0 then _tempV2:Reset(position) -- _tempV2是从entity出发,指向目标位置的向量 _tempV2:Sub(entity:GetLogicPos()) local sideOffset = FP.V2.GetRightByForward(_tempV2) --支持横向偏移 sideOffset:Normalize() sideOffset:Mul(sideOffsetMagnitude) position:Add(sideOffset) end ----------------------------------------------- entity.components.aiBase:SetDecisionRecord(decisionData,"lastFindPathFrame",LogicUpdateManager._clientHandleFrame) entity.components.aiBase:SetDecisionRecord(decisionData,"findPathTargetPos",position) SystemManager.FindPath(entity, position, true, priorityWall) end end end function Decision_Base:RunToPathPoint(entity, command, decisionData, forceTargetPos) local pathComponent = entity.components.path local selfPos = entity.components.move.LogicPos local curPathPos = forceTargetPos or pathComponent.Path[pathComponent.CurPathPosIndex] if curPathPos then --if selfPos == curPathPos then --old 没有tolerance,会被Shove引发跑不到点上 _tempResultV2:Reset(selfPos):Sub(curPathPos) local offsetX, offsetZ = tonumberV2(_tempResultV2) if abs(offsetX) + abs(offsetZ) < 0.2 then --new --增加tolerance,且直接用曼哈顿距离判断减少性能损耗 pathComponent.CurPathPosIndex = pathComponent.CurPathPosIndex + 1 curPathPos = pathComponent.Path[pathComponent.CurPathPosIndex] if curPathPos then if not command.TargetPos then command.TargetPos = newV2(0, 0) end command.TargetPos:Reset(curPathPos) command.MoveDir:Reset(curPathPos):Sub(selfPos) command.RotateDir:Reset(command.MoveDir) end else if not command.TargetPos then command.TargetPos = newV2(0, 0) end command.TargetPos:Reset(curPathPos) command.MoveDir:Reset(curPathPos):Sub(selfPos) command.RotateDir:Reset(command.MoveDir) end end if command.MoveDir and command.MoveDir ~= FP.Const.Vector2_Zero then command.MoveDir:Normalize() end self:OnRunToPathPoint(entity, command,decisionData) return command end function Decision_Base:PatrolOnPath(entity, command, isBloc, path) local patrolComponent = entity.components.patrol local posOffset = isBloc and patrolComponent.PosOffset or Const_Vector2_Zero local curPointPos = LevelItemManager.GetPathPoint(path, patrolComponent.CurPatrolPathIndex) if curPointPos then local selfPos = entity.components.move.LogicPos curPointPos:Add(posOffset) if selfPos == curPointPos then if patrolComponent.IsPatrolForward then patrolComponent.CurPatrolPathIndex = patrolComponent.CurPatrolPathIndex + 1 else patrolComponent.CurPatrolPathIndex = patrolComponent.CurPatrolPathIndex - 1 end curPointPos = LevelItemManager.GetPathPoint(path, patrolComponent.CurPatrolPathIndex) if curPointPos then curPointPos:Add(posOffset) command.MoveDir:Reset(curPointPos):Sub(selfPos) command.RotateDir:Reset(command.MoveDir) command.TargetPos = curPointPos:cloneV2() else private:ResetPatrolData(path, entity) command.MoveDir:Reset(entity.components.move.LogicRotateDir) end else command.MoveDir:Reset(curPointPos):Sub(selfPos) command.RotateDir:Reset(command.MoveDir) command.TargetPos = curPointPos:cloneV2() end else private:ResetPatrolData(path, entity) command.MoveDir:Reset(entity.components.move.LogicRotateDir) end return command end function private:ResetPatrolData(path, entity) local patrolComponent = entity.components.patrol local pathType = LevelItemManager.GetPathType(path) if pathType == LuaConst.WalkPathType.Round then if patrolComponent.IsPatrolForward then patrolComponent.IsPatrolForward = false patrolComponent.CurPatrolPathIndex = patrolComponent.CurPatrolPathIndex - 2 else patrolComponent.IsPatrolForward = true patrolComponent.CurPatrolPathIndex = patrolComponent.CurPatrolPathIndex + 2 end elseif pathType == LuaConst.WalkPathType.Loop then patrolComponent.CurPatrolPathIndex = 1 end end function Decision_Base:IsToTargetDisSqrLess(entity, decisionData,disSqr) local comp_Target = entity.components.target local target = comp_Target.Target if target then return LessEquals(comp_Target.ToTargetDisSqr, disSqr) else return true end end --------------------------region 技能释放---------------------------- --[[ 释放技能的指令是否在CD,纯AI层的CD --]] function Decision_Base:IsSkillDecisionInCD(decisionRecordDic) local useSkillCDOverFrame = decisionRecordDic[self.USESKILL_CD_OVER_FRAME] if useSkillCDOverFrame then useSkillCDOverFrame = tonumberV1(useSkillCDOverFrame) else useSkillCDOverFrame = 0 end return LogicUpdateManager._clientHandleFrame < useSkillCDOverFrame end function Decision_Base:HasPlayerInput(command) return command.MoveDir ~= FP.Const.Vector2_Zero or command.SkillIndex ~= 0 end function Decision_Base:GetMinMaxSkillDecisionCD(decisionRecordDic) return decisionRecordDic[self.MIN_USESKILL_DECISION_CD_KEY] or 0,decisionRecordDic[self.MAX_USESKILL_DECISION_CD_KEY] or 0 end function Decision_Base:EnterUseSkillDecisionCD(entity ,decisionRecordDic) local minCd,maxCd = self:GetMinMaxSkillDecisionCD(decisionRecordDic) decisionRecordDic[self.USESKILL_CD_OVER_FRAME] = LogicUpdateManager._clientHandleFrame + LogicRandom.GetRangeValue(entity,minCd,maxCd ,1020) * LuaConst.LogicFrameRate end --selfPos 当前位置 --center wander随机点随机中心 --radius wander随机点随机半径 --changePosPosiblity 重新随机一个点的概率 --随机闲逛,会走走停停。changePosPosiblity是停下来时,再次随机新点的概率 function Decision_Base:CommandRandomWander(entity,aiBaseComponent,decisionData,selfPos,center,radius,command,changePosPosiblity) local randomWanderPos = aiBaseComponent:GetDecisionRecord(decisionData,RANDOM_WANDER_POS_KEY) if randomWanderPos == nil or --如果没有随机走动目标位置,就重新找一个随机走动目标位置 (V2_DistanceSqr(randomWanderPos,selfPos) <= FP.Const.Half --如果离随机走动位置非常近了 or (entity.components.path.Path == nil or #entity.components.path.Path == 0)) --当前的路径为空,也重新寻路 and LogicRandom.GetRandom01(entity , 1004) <= changePosPosiblity --如果离随机走动位置非常近了或者路径为空,就有一定概率重新寻找一个点 then randomWanderPos = LogicRandom.GetRandomPointInCircle(entity,radius - 1 ,1030):Add(center) aiBaseComponent:SetDecisionRecord(decisionData,RANDOM_WANDER_POS_KEY,randomWanderPos) end return self:CommandFindPathAndRunTo(entity,randomWanderPos,command,false,decisionData) end --selfPos 当前位置 --center wander随机点随机中心 --radius wander随机点随机半径 --changePosPosiblity 重新随机一个点的概率 -- 随机闲逛,不会停顿的版本。会一直处于运动中 function Decision_Base:CommandRandomWanderNoStop(entity,aiBaseComponent,decisionData,selfPos,center,radius,command,changePosPosiblity) local randomWanderPos = aiBaseComponent:GetDecisionRecord(decisionData,RANDOM_WANDER_POS_KEY) if randomWanderPos == nil or --如果没有随机走动目标位置,就重新找一个随机走动目标位置 V2_DistanceSqr(randomWanderPos,selfPos) <= FP.Const.Half or LogicRandom.GetRandom01(entity , 1004) <= changePosPosiblity --如果离随机走动位置非常近了,就有一定概率重新寻找一个点 or (entity.components.path.Path == nil or #entity.components.path.Path == 0) --或者当前的路径为空,也重新寻路 then randomWanderPos = LogicRandom.GetRandomPointInCircle(entity,radius - 1 ,1030):Add(center) aiBaseComponent:SetDecisionRecord(decisionData,RANDOM_WANDER_POS_KEY,randomWanderPos) end return self:CommandFindPathAndRunTo(entity,randomWanderPos,command,false,decisionData) end --修饰并返回command为移向targetPos function Decision_Base:CommandFindPathAndRunTo(entity,targetPos,command,forceFindPath,decisionData,interval) local pathComponent = entity.components.path local curPathPos = pathComponent.Path[pathComponent.CurPathPosIndex] if not forceFindPath and interval then self:IntervalFindPath(entity,decisionData,command,interval,nil,targetPos) else if targetPos ~= pathComponent.TargetPos or not curPathPos or forceFindPath then SystemManager.FindPath(entity, targetPos, true) end end return self:CommandRunToPathPoint(entity, command,decisionData) end function Decision_Base:CommandRunToPathPoint(entity, command,decisionData) local pathComponent = entity.components.path local selfPos = entity.components.move.LogicPos local curPathPos = pathComponent.Path[pathComponent.CurPathPosIndex] if curPathPos then local dis = curPathPos - selfPos if Math_Abs(dis.x) < SMALL_DISTANCE_AXIS_DIFF and Math_Abs(dis.z) < SMALL_DISTANCE_AXIS_DIFF then pathComponent.CurPathPosIndex = pathComponent.CurPathPosIndex + 1 else command.MoveDir = curPathPos - selfPos command.RotateDir:Reset(command.MoveDir) end end self:OnRunToPathPoint(entity, command,decisionData) return command end function Decision_Base:OnRunToPathPoint(entity, command,decisionData) if LevelManager.IsNoBattleLevel() then -- 主城类地图中不走这块的代码,自动上马有自己的逻辑控制 return end if entity:IsHero() and entity:CanRideHorse() then local decisionRecordDic = entity.components.aiBase:GetDecisionRecordDic(decisionData) if decisionRecordDic and not decisionRecordDic.dontAutoRide then if not entity.components.buff:CheckHasBuffState(LuaConst.SpecialBuffs.InRiding) then decisionRecordDic.onFootFrameCount = decisionRecordDic.onFootFrameCount or 0 decisionRecordDic.onFootFrameCount = decisionRecordDic.onFootFrameCount + entity.components.aiBase:GetAiUpdateInterval() if decisionRecordDic.onFootFrameCount >= ON_FOOT_TO_RIDE_INTERVAL_FRAME then if command.SpecialCommand == nil or command.SpecialCommand == 0 then command.SpecialCommand = LuaConst.SpecialCommandType.Ride decisionRecordDic.onFootFrameCount = 0 end end else decisionRecordDic.onFootFrameCount = 0 end end end end function Decision_Base:CommandEscapeFromTarget(entity, command, target) command.MoveDir = entity:GetLogicPos() - target:GetLogicPos() command.MoveDir:Normalize() command.RotateDir:Reset(command.MoveDir) end function Decision_Base:CommandEscapeFromPos(entity, command, pos) command.MoveDir = entity:GetLogicPos() - pos command.MoveDir:Normalize() command.RotateDir:Reset(command.MoveDir) end function Decision_Base:CanUseAdjutantSkill(entity, decisionRecordDic) local _, adjutantMagicWeaponActive = LevelPlayerManager.IsEntityMagicWeaponActive(entity) if entity.components.adjutant.AdjutantEntity and adjutantMagicWeaponActive and entity.components.adjutant.AdjutantCD <= 0 and LevelPlayerManager.GetPlayerSetting(entity:GetPlayerID() ,LuaConst.BattleSettingEnum.AutoAdjutantSkill) and entity.components.target.Target and entity.components.buff.CurrentBuffStates:Get(LuaConst.SpecialBuffs.CanSkill) then if entity.components.adjutant.AdjutantSkillCastRadiusSqr then return entity.components.target.ToTargetDisSqr <= entity.components.adjutant.AdjutantSkillCastRadiusSqr else return true end else return false end end function Decision_Base:CanUseDodgeSkill(entity) local skillBase = entity.components.skillBase return self:CanAiUseSkill(entity, skillBase, skillBase:GetSkillDataByKeyPosition(DODGE_KEYPOS)) end function Decision_Base:CanAiUseStratagemSkill(entity) if LevelManager.IsNineVsNineFightLevel() or LevelManager.IsLootMine() or LevelManager.IsAttackCity() then return entity.components.stratagem:CanUseStratagemSkill() and entity.components.buff:HasHardControlBuff() else return false end end --index是键位的index,1=BuffSkill1(法相技能),2=BuffSkill2(神兽技能) 3=BuffSkill3 ... function Decision_Base:CanUseBuffSkill(entity, decisionRecordDic, index) if index == 2 then --神兽技能的情况 --在玩法,只有中军可以释放神兽技 if LevelManager.IsNineVsNineFightLevel() then if LevelPlayerManager.GetHeroArmyPos(entity:GetPlayerID(), entity:GetCharacterID()) ~= 2 then return false end end end local buffSkillUpdate = BuffSkillSystem.GetBuffSkillUpdateByIndex(entity, index) if buffSkillUpdate then local castRadiusSqr = buffSkillUpdate.BuffSkillData.CastRadiusSqr if entity.components.target.ToTargetDisSqr <= castRadiusSqr or castRadiusSqr <= Const_Zero then return BuffSkillSystem.CanUseBuffSkill(entity, BuffSkillSystem.GetBuffSkillKeyPosByIndex(index)) end end return false end function Decision_Base:CommandUseSkill(entity,command,decisionRecordDic) local used = false if self:CanUseAdjutantSkill(entity, decisionRecordDic) then--副将技能 command.SpecialCommand = LuaConst.SpecialCommandType.Adjutant used = true else local skillBaseComp = entity.components.skillBase local curSkill = skillBaseComp.CurSkillData if curSkill and curSkill.Type == LuaConst.SkillType.Serial and curSkill.ParamInt1 >= 1 and curSkill.ParamInt1 <= #skillBaseComp.CurSkillSwitchList then local skillSwitch = skillBaseComp.CurSkillSwitchList[curSkill.ParamInt1] if skillSwitch then command.SkillIndex = SystemManager.GetSkillIndexByKeyPosition(entity,skillSwitch.SwitchPutIn) used = true end else local isSkillDecisionInCD = self:IsSkillDecisionInCD(decisionRecordDic) local isNormalAttack = false for k,skillData in ipairs(skillBaseComp.AutoFightSkillDataList) do isNormalAttack = SkillBaseSystem.IsNormalAttack(skillData) if not isSkillDecisionInCD or isNormalAttack then --非普攻需要走SkillDecision CD,普攻不走CD local ConditionTree = ConditionManager.GetConditionDataTree(skillData.AutoBattleCondition) if self:CanAiUseSkill(entity,skillBaseComp,skillData,ConditionTree) then command.SkillIndex = SystemManager.GetSkillIndexByKeyPosition(entity,skillData.KeyPosition) if skillData.Type ~= LuaConst.SkillType.Serial and not SkillBaseSystem.IsNormalAttack(skillData) then --AI的技能释放指令进CD,纯AI层的(连续技不进、普攻不进) self:EnterUseSkillDecisionCD(entity ,decisionRecordDic) end used = true break end end end end end return command,used end function Decision_Base:CanAiUseSkill(entity,skillBaseComp,skillData,ConditionTree) if not skillData then --如果技能为空 ,则判断不能释放技能 。如:张角变身后要释放闪避技能 return false end local canUse if entity.components.buff.CurrentBuffStates:Get(LuaConst.SpecialBuffs.CanSkill) then canUse = skillBaseComp:CanUseSkill(skillData) and ConditionManager.MeetCondition(entity,ConditionTree) if skillBaseComp:IsMusouSkill(skillData) then canUse = canUse and (not entity:IsHero() or LevelPlayerManager.IsEntityMagicWeaponActive(entity) and LevelManager.CurLevelFunc:IsAutoUseMusou(entity)) elseif skillBaseComp:IsEnegySkill(skillData) then if canUse then local curSkillData = skillBaseComp.CurSkillData if curSkillData then if not skillBaseComp:IsEnegySkill(curSkillData) then canUse = skillBaseComp:IsSkillEnegyFull(skillData) end else canUse = skillBaseComp:IsSkillEnegyFull(skillData) end end end canUse = canUse and (not entity.components.aiBase:IsForidSkill(skillData)) if canUse then --检测SwitchPutOut后要释放的技能是否可以释放 --需要判断该技能键位实际的技能变成了什么(因为有SkillSwitch机制存在,skillData.KeyPosition最终可能释放的是其他技能,比如连续技) local comp_SkillBase = entity.components.skillBase if skillData.ID == skillData.BelongTo then local result = comp_SkillBase.CurUpdateDataGroup[skillData.ID] and comp_SkillBase.CurUpdateDataGroup[skillData.ID].CurSerialNextSkillID if result then local realSkillData = entity:GetSkillData(result) if realSkillData and realSkillData.AutoBattleOrder <= 0 then canUse = false end end end end else canUse = false end return canUse end function Decision_Base:ClearAllWaitingSkillRecord(decisionRecordDic) decisionRecordDic.waitingSkillData = nil decisionRecordDic.pointerUpNextSkillData = nil decisionRecordDic.setWaitingSkillDataFrame = nil end --移动到技能范围内再使用技能 --有target才能成功释放技能 local _tempV1Dis = FP.newV1(0) local _tempV1CastRadiusNum = FP.newV1(0) local _tempSkillJoyOffsetPos = FP.newV2(0,0) function Decision_Base:CommandChaseUseSkill(entity, command, decisionData, decisionRecordDic, shouldWanderWhenInCD) local used = false --decisionRecordDic.waitingSkillData 是预备释放的技能Data if not decisionRecordDic.waitingSkillData then if self:CanUseAdjutantSkill(entity, decisionRecordDic) then--副将技能 decisionRecordDic.lastUsedSkill = decisionRecordDic.adjutantSkill decisionRecordDic.adjutantSkill = true used = true elseif self:CanUseBuffSkill(entity, decisionRecordDic, 1) then decisionRecordDic.useBuffSkillIndex = 1 used = true else --确定要使用哪个技能 local skillBaseComp = entity.components.skillBase local curSkill = skillBaseComp.CurSkillData if curSkill and curSkill.Type == LuaConst.SkillType.Serial and curSkill.ParamInt1 >= 1 and curSkill.ParamInt1 <= #skillBaseComp.CurSkillSwitchList then local skillSwitch = skillBaseComp.CurSkillSwitchList[curSkill.ParamInt1] if skillSwitch then decisionRecordDic.lastUsedSkill = decisionRecordDic.waitingSkillData used = true decisionRecordDic.waitingSkillData = nil command.SkillIndex = SystemManager.GetSkillIndexByKeyPosition(entity,skillSwitch.SwitchPutIn) --连续技不管技能范围 end else local isSkillDecisionInCD = self:IsSkillDecisionInCD(decisionRecordDic) -- ↓ 释放技能行为CD期间 随机乱逛 ↓↓↓↓ if isSkillDecisionInCD and shouldWanderWhenInCD then self:CommandRandomWanderNoStop(entity, entity.components.aiBase, decisionData, entity:GetLogicPos(), entity:GetLogicPos(), 5, command, Const_Zero) return command, false end -- ↑ 随机乱晃 ↑↑↑↑ if curSkill and curSkill.Type == LuaConst.SkillType.Serial and curSkill.ParamInt1 >= 1 and curSkill.AutoNextConditionStr == "pointerUp" then decisionRecordDic.lastUsedSkill = decisionRecordDic.waitingSkillData used = true decisionRecordDic.waitingSkillData = nil local nexSerialSkill = entity:GetSkillData(curSkill.ParamInt1) if nexSerialSkill.Type == LuaConst.SkillType.Serial then command.SkillIndex = entity.components.skillBase:GetSkillIndexBySkillID(curSkill.BelongTo) else command.SkillIndex = entity.components.skillBase:GetSkillIndexBySkillID(curSkill.ParamInt1) end else local isNormalAttack = false for k,skillData in ipairs(skillBaseComp.AutoFightSkillDataList) do isNormalAttack = SkillBaseSystem.IsNormalAttack(skillData) if not isSkillDecisionInCD or isNormalAttack then --非普攻需要走SkillDecision CD,普攻不走CD local ConditionTree = ConditionManager.GetConditionDataTree(skillData.AutoBattleCondition) if self:CanAiUseSkill(entity,skillBaseComp,skillData,ConditionTree) then decisionRecordDic.waitingSkillData = skillData decisionRecordDic.lastUsedSkill = nil if decisionRecordDic.waitingSkillData.AutoNextConditionStr == "pointerUp" then local pointerUpNextSkillData = entity:GetSkillData(decisionRecordDic.waitingSkillData.AutoNextID) if decisionRecordDic.pointerUpNextSkillData ~= pointerUpNextSkillData then decisionRecordDic.pointerUpNextSkillData = pointerUpNextSkillData end end decisionRecordDic.setWaitingSkillDataFrame = LogicUpdateManager._clientHandleFrame break end end end end end end end if decisionRecordDic.waitingSkillData or decisionRecordDic.adjutantSkill or decisionRecordDic.useBuffSkillIndex then if decisionRecordDic.waitingSkillData then _tempV1CastRadiusNum:Reset(decisionRecordDic.waitingSkillData.CastRadius) if not entity:IsHero() then _tempV1CastRadiusNum:Add(entity.components.base:GetBoundingCircleRadius()) end local target = entity.components.target.Target if target then _tempV1CastRadiusNum:Add(target.components.base:GetBoundingCircleRadius()) end _tempV1Dis:Reset(_tempV1CastRadiusNum):Mul(_tempV1CastRadiusNum) if _tempV1CastRadiusNum <= Const_Zero or self:IsToTargetDisSqrLess(entity,decisionData,FP.tonumberV1(_tempV1Dis)) then command.SkillIndex = SystemManager.GetSkillIndexByKeyPosition(entity,decisionRecordDic.waitingSkillData.KeyPosition) if #decisionRecordDic.waitingSkillData.SubSkills > 0 then command.SpecialCommand = LuaConst.SpecialCommandType.SubSkillIndex1 end --↓下面这段代码是为了修复太史慈使用Decision_UseRandomSkill释放蓄力技会出现永久蹲的问题而增加的 --因为太史慈蓄力技第二段的持续时间为无限长,所以需要做特殊处理才能让太史慈顺畅释放技能而不卡住 if entity:IsHero() then --这个IsHero是不必要的,但是下面这段新增代码影响的AI太多了,所以就加一个限制仅为Hero,减少测试范围 local curSkillData = entity.components.skillBase.CurSkillData if curSkillData then if curSkillData.AutoNextConditionStr == "pointerUp" then command.SkillIndex = entity.components.skillBase:GetSkillIndexBySkillID(curSkillData.AutoNextID) command.SpecialCommand = LuaConst.SpecialCommandType.None end end end --↑↑↑↑↑↑↑ if target then local skillJoystickData = entity.components.skillBase:GetSkillJoystickData(decisionRecordDic.waitingSkillData.ID) if skillJoystickData then -- 攻击目标的位置需要随机偏移 if decisionRecordDic.offsetSkillProbability then local offsetSkillProbability = LogicRandom.GetRandom01(entity ,202405160) if LessEquals(offsetSkillProbability, decisionRecordDic.offsetSkillProbability) then LogicRandom.GetRandomPointInCircle(entity, decisionRecordDic.offsetSkillRange, 202405161, _tempSkillJoyOffsetPos) _tempSkillJoyOffsetPos:Add(target:GetLogicPos()):Sub(entity:GetLogicPos()) if skillJoystickData.UEType == LuaConst.ESkillJoystickUEType.JOYSTICK_DIR then _tempSkillJoyOffsetPos:Normalize() end command.SkillJoystickPosX:Reset(_tempSkillJoyOffsetPos.x) command.SkillJoystickPosZ:Reset(_tempSkillJoyOffsetPos.z) end end if SkillMissileSystem.GetJoystickCtrlMissileType(skillJoystickData) then entity.components.aiBase:SetAISkillJoystickPos(target:GetLogicPos():cloneV2()) end end end if decisionRecordDic.waitingSkillData.Type ~= LuaConst.SkillType.Serial and not SkillBaseSystem.IsNormalAttack(decisionRecordDic.waitingSkillData) then --AI的技能释放指令进CD,纯AI层的(连续技不进、普攻不进) self:EnterUseSkillDecisionCD(entity ,decisionRecordDic) end used = true decisionRecordDic.lastUsedSkill = decisionRecordDic.waitingSkillData decisionRecordDic.waitingSkillData = nil end elseif decisionRecordDic.adjutantSkill then used = true decisionRecordDic.lastUsedSkill = decisionRecordDic.adjutantSkill decisionRecordDic.adjutantSkill = nil command.SpecialCommand = LuaConst.SpecialCommandType.Adjutant self:EnterUseSkillDecisionCD(entity ,decisionRecordDic) elseif decisionRecordDic.useBuffSkillIndex then used = true command.SpecialCommand = LuaConst.SpecialCommandType.BuffSkill1 - 1 + decisionRecordDic.useBuffSkillIndex decisionRecordDic.useBuffSkillIndex = nil end if not used then --每隔1000重新寻一次路并跑向目标 if not self:TryUsePointerUpNextSkill(entity, decisionRecordDic, command) then self:IntervalFindPath(entity, decisionData, command, 1000, entity.components.target.Target, nil, false) command = self:RunToPathPoint(entity, command,decisionData) if LogicUpdateManager._clientHandleFrame - decisionRecordDic.setWaitingSkillDataFrame > (decisionRecordDic.resetInterval or self.RESET_WAITINGSKILL_FRAME_INTERVAL) then self:ClearAllWaitingSkillRecord(decisionRecordDic) end end else command.MoveDir:Reset(0, 0) end else command.MoveDir:Reset(0, 0) end return command,used end function Decision_Base:TryUsePointerUpNextSkill(entity, decisionRecordDic, command) if decisionRecordDic.lastUsedSkill and decisionRecordDic.pointerUpNextSkillData and decisionRecordDic.lastUsedSkill.AutoNextID == decisionRecordDic.pointerUpNextSkillData.ID then command.SkillIndex = SystemManager.GetSkillIndexByKeyPosition(entity, decisionRecordDic.pointerUpNextSkillData.KeyPosition) decisionRecordDic.lastUsedSkill = decisionRecordDic.pointerUpNextSkillData decisionRecordDic.pointerUpNextSkillData = nil return true else return false end end -- 尝试释放蓄力技能(太史慈) function Decision_Base:ReleaseHoldingSkill(entity, command) local skillBaseComp = entity.components.skillBase local curSkill = skillBaseComp.CurSkillData if curSkill and curSkill.Type == LuaConst.SkillType.Serial and curSkill.ParamInt1 >= 1 and curSkill.AutoNextConditionStr == "pointerUp" then local nexSerialSkill = entity:GetSkillData(curSkill.ParamInt1) if nexSerialSkill.Type == LuaConst.SkillType.Serial then command.SkillIndex = entity.components.skillBase:GetSkillIndexBySkillID(curSkill.BelongTo) else command.SkillIndex = entity.components.skillBase:GetSkillIndexBySkillID(curSkill.ParamInt1) end end end --------------------------end region --------------------------------- --------------------------region 队列移动↓---------------------------- --设置组员的队列移动信息 --strideColumn, strideRow 单位统一为毫米 --dir可以不为单位向量 function Decision_Base:RefreshPhalanxInfo(entity, dir, strideColumn, strideRow, rotateType, maxColumn) local nextIndex = 1 local groupEntityList = entity.components.group.GroupEntityList local totalMemberCount = #groupEntityList for i,v in ipairs(groupEntityList) do v.components.group:SetPhalanxInfo(nextIndex, dir, entity:GetLogicPos(), totalMemberCount, strideColumn, strideRow, rotateType, maxColumn) nextIndex = nextIndex + 1 end end local DIST_COMPARE_TOLERANCE = 2 local WAIT_POSSIBLITY = FP.Const.Half --默认的超距等待的概率 --strideColumn :列间隔,单位毫米 --strideRow :行间隔,单位毫米 --possibility :超距等待的概率,不传则会用默认的 function Decision_Base:ShouldPhalanxLeaderWait(entity, decisionData, strideColumn, strideRow, maxColumn, possibility) possibility = possibility or WAIT_POSSIBLITY --目前的算法是,如果有组员与队长的距离超过阵型对角线半长*DIST_COMPARE_TOLERANCE local count = #entity.components.group.GroupEntityList local column = math.min(count, maxColumn) if column > 0 then local maxRow = math.floor((count - 1) / maxColumn) + 1 local columnLengthSqr = FP.newV1(column) columnLengthSqr:Sub(1):Mul(strideRow):Div(1000) columnLengthSqr:Mul(columnLengthSqr) local rowLengthSqr = FP.newV1(maxRow) rowLengthSqr:Sub(1):Mul(strideColumn):Div(1000) rowLengthSqr:Mul(rowLengthSqr) local halfDiagnalLengthSqr = columnLengthSqr + rowLengthSqr halfDiagnalLengthSqr:Mul(0.25):Mul(DIST_COMPARE_TOLERANCE) for i,v in ipairs(entity.components.group.GroupEntityList) do if v.components.group.ToLeaderDisSqr and v.components.group.ToLeaderDisSqr > halfDiagnalLengthSqr then return LogicRandom.GetRandom01(entity, 54389843) < possibility end end end return false end --------------------------end region↑--------------------------------- return Decision_Base