--PVE(也包括部分PVP)通用自动战斗,支持同步,目前有很多关卡都会用这个。包括主线剧情、组队副本、军团boss、放置爬塔等等关卡都用这个 --除了演武校场、3v3、世界boss以外的所有其他关卡的自动战斗都走这个脚本 --2021年4月16日 local base = require("Battle/Logic/AI/Decision/Decision_Base") local Decision_PveFight = class("Decision_PveFight", base) local Const_MoveBuff = LuaConst.SpecialBuffs.CanMove local BuffSkillSystem = require"Battle/Logic/Entity/System/BuffSkillSystem" local LuaConst = LuaConst local KEY_POS_GODBEAST = LuaConst.ESkillKeyPosition.BuffSkill2 function Decision_PveFight:initialize(entity) base.initialize(self,entity) --类型 self.Type = LuaConst.DecisionType.PveFight --瞬时 self.IsOnce = false self.SearchEnemyDisSqr = FP.newV1(400) end --[[ 进入该Decision --]] function Decision_PveFight:DecisionIn(entity, decisionData) base.DecisionIn(self, entity, decisionData) local aiBaseComponent = entity.components.aiBase local decisionRecordDic = aiBaseComponent:GetDecisionRecordDic(decisionData) if LevelManager.IsSyncLevel() then aiBaseComponent:SetDecisionRecord(decisionData, "isSyncLevel", true) aiBaseComponent:SetDecisionRecord(decisionData, "syncLevelAiDelayFrame", LevelManager.CurLevelFunc:GetHeroAiDelayFrame()) end if LevelManager.IsSyncGuildBossLevel() then self.SearchEnemyDisSqr:Reset(360000) --600米 , 军团boss关,索敌范围设为巨大的值 elseif LevelManager.IsSyncCountryPVPLevel()then self.SearchEnemyDisSqr:Reset(1600) --40米 , 国战pvp,索敌范围设为巨大的值 elseif LevelManager.IsAttackCity() or LevelManager.IsLootMine() or LevelManager.IsNineVsNineFightLevel() then self.SearchEnemyDisSqr:Reset(360000) --600米 , 攻城掠地关,索敌范围设为巨大的值 else self.SearchEnemyDisSqr:Reset(400) --20米,其他关卡,索敌范围20米 end self.shouldTargetTypeSort = true --是否需要在选择目标时排序(比如按照 Boss 先于 精英 先于 小兵 的顺序) if LevelManager.IsSyncPlanesEscort() or LevelManager.IsSyncGuildBossLevel() then self.shouldTargetTypeSort = false --军团boss关卡,不需要在选择目标时排序,因为只有1个目标,这样能节省性能 end decisionRecordDic.stratagemSkillInfo = entity.components.stratagem and entity.components.stratagem.StratagemSkillInfo end --[[ 退出该Decision --]] function Decision_PveFight:DecisionOut(entity, decisionData) entity.components.aiBase:ClearDecisionRecord(decisionData) entity.components.target:SetForceTarget(nil) entity.components.aiBase:SetIsBreakByInput(false) end --[[ 能否进入 --]] function Decision_PveFight:CanEnter(entity, decisionData, command) local autoFightComponent = entity.components.autoFight return autoFightComponent.IsStandaloneAuto or autoFightComponent.IsSyncAuto end --[[ 能否退出 --]] function Decision_PveFight:CanExit(entity, decisionData, command) return not self:IsAuto(entity) end function Decision_PveFight:ShouldBlockAI(decisionRecordDic) --联机玩法本AI延迟SYNC_LEVEL_DELAY帧执行,减轻关卡一开始的性能压力 return base.ShouldBlockAI(self) or (decisionRecordDic.isSyncLevel and LogicUpdateManager._clientHandleFrame < decisionRecordDic.syncLevelAiDelayFrame) end function Decision_PveFight:IsAuto(entity) if LevelManager.IsSyncLevel() then return entity.components.autoFight.IsSyncAuto else return entity.components.autoFight.IsStandaloneAuto end end --返回的是任务路点(missionAim.Data.PathPoints的元素),代表一个区域,而不是一个点。里面存有区域形状信息 function Decision_PveFight:GetCurrentPathPoint(entity,decisionRecordDic,missionAim) if decisionRecordDic.missionAim ~= missionAim then self:RefreshPathPoint(entity,decisionRecordDic,missionAim) else if not decisionRecordDic.pathPoint then self:RefreshPathPoint(entity,decisionRecordDic,missionAim) end end return decisionRecordDic.pathPoint end function Decision_PveFight:RefreshPathPoint(entity,decisionRecordDic,missionAim) if missionAim then if decisionRecordDic.pathPoint ~= missionAim.Data.PathPoints[missionAim.PathIndex] then decisionRecordDic.pathPoint = missionAim.Data.PathPoints[missionAim.PathIndex] end else decisionRecordDic.pathPoint = nil end return decisionRecordDic.pathPoint end function Decision_PveFight:CanPassPathPoint(entity,pathPoint) --判断是否可以通过路点 ①圈里没有敌人 or ②杀够配置的数量( 条件②未完成HYW now now ) if pathPoint then if pathPoint.Radius then return not TargetManager.HasEnemyInCircle(entity, pathPoint.Position, pathPoint.Radius, false) else return not TargetManager.HasEnemyInRect(entity, pathPoint.Position, pathPoint.Length, pathPoint.Width, pathPoint.Direction, false) end else return true end end local PAUSE_FRAME_AFTER_USER_INPUT = 10 --有玩家输入后,该Decision暂停几帧 --[[ 修饰指令(修饰command) --]] function Decision_PveFight:SetCommand(entity, decisionData, command) repeat local aiBaseComponent = entity.components.aiBase local decisionRecordDic = aiBaseComponent:GetDecisionRecordDic(decisionData) if self:ShouldBlockAI(decisionRecordDic) then command.DontHandle = true break end if self:HasPlayerInput(command) then aiBaseComponent:SetIsBreakByInput(true) --如果有玩家输入,记录这个输入的时间,并跳出AI运行 decisionRecordDic.lastUserInputFrame = LogicUpdateManager._clientHandleFrame entity.components.target:SetForceTarget(nil) command.DontHandle = true break else aiBaseComponent:SetIsBreakByInput(false) end if decisionRecordDic.lastUserInputFrame and LogicUpdateManager._clientHandleFrame - decisionRecordDic.lastUserInputFrame < PAUSE_FRAME_AFTER_USER_INPUT / entity.components.aiBase:GetAiUpdateInterval() then --策划需求:如果玩家有输入,就得暂停AI运行一段时间 command.DontHandle = true break end if not self:IsAuto(entity) then entity.components.target:SetForceTarget(nil) command.DontHandle = true break end --↓策划又暂时不要自动放计略技能了, 但是也说了未来可能还要回来,所以这里暂时注掉↓ if decisionRecordDic.stratagemSkillInfo then if self:CanAiUseStratagemSkill(entity) then command.SpecialCommand = LuaConst.SpecialCommandType.Stratagem break end end --↑策划又暂时不要自动放计略技能了, 但是也说了未来可能还要回来,所以这里暂时注掉↑ if LevelManager.IsAdvancedDigNormal() then if LevelManager.CurLevelFunc.TryAutoOpenRewardBox then LevelManager.CurLevelFunc:TryAutoOpenRewardBox(entity, decisionRecordDic) else logging.error(LTAG.ERROR, "HYW : LevelManager.IsAdvancedDigNormal() but no TryAutoOpenRewardBox function, levelID : ", LevelManager.GetCurLevelID()) end end local missionAim = LevelItemManager.Global:GetEntityMissionAim(entity) if missionAim then --pathPoin : 任务路点,不是点,而是一个区域 local pathPoint = self:GetCurrentPathPoint(entity,decisionRecordDic,missionAim) missionAim:GetCurrentPosition(entity:GetLogicPos()) if self:IsEnterPathPoint(entity,pathPoint,missionAim) then if self:CanPassPathPoint(entity,pathPoint) then pathPoint = self:RefreshPathPoint(entity,decisionRecordDic,missionAim) end local target = entity.components.target.Target if not target or not self:IsEnterPathPoint(target,pathPoint,missionAim) or not self:IsMissionAim(target, missionAim) or not target:IsAlive() then self:SearchTargetWithinMissionPoint(entity,missionAim,pathPoint) if not entity.components.target.Target then self:RunToNextPos(entity,pathPoint,command,missionAim,decisionData) end end entity.components.target:SetForceTarget(entity.components.target.Target) if entity.components.target.Target then local isSkillUsed = false command,isSkillUsed = self:CommandChaseUseSkill(entity,command,decisionData,decisionRecordDic) if self:CanUseBuffSkill(entity, decisionRecordDic, 2) then local succeed = BuffSkillSystem.ReleaseBuffSkill(entity, KEY_POS_GODBEAST) --无脑调,如果放不了就不会放 if succeed and LevelManager.IsNineVsNineFightLevel() then SystemManager.SwitchRightGodBeastSkillOfEntity(entity) end end -- if not isSkillUsed then -- self:RunToNextPos(entity,pathPoint,command,missionAim) -- end end else if entity.components.buff:CheckHasBuffState(Const_MoveBuff) then self:RunToNextPos(entity,pathPoint,command,missionAim,decisionData) else local comp_SkillBase = entity.components.skillBase comp_SkillBase:CallAllSkillDecoratorFunc("OnAICantRunToNextPos", command) end self:ReleaseHoldingSkill(entity, command) end else SystemManager.OnSearchTarget(entity, 1, self.SearchEnemyDisSqr, 1, 0, false, true, self.shouldTargetTypeSort) if entity.components.target.Target then entity.components.target:SetForceTarget(entity.components.target.Target) local isSkillUsed = false command,isSkillUsed = self:CommandChaseUseSkill(entity,command,decisionData,decisionRecordDic) if self:CanUseBuffSkill(entity, decisionRecordDic, 2) then local succeed = BuffSkillSystem.ReleaseBuffSkill(entity, KEY_POS_GODBEAST) --无脑调,如果放不了就不会放 if succeed and LevelManager.IsNineVsNineFightLevel() then SystemManager.SwitchRightGodBeastSkillOfEntity(entity) end end else self:ReleaseHoldingSkill(entity, command) end end decisionRecordDic.missionAim = missionAim until true end -- 尝试释放蓄力技能(太史慈) function Decision_PveFight: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 --mission point是一个范围,而不是一个点。 --可能是圆形或者矩形 function Decision_PveFight:SearchTargetWithinMissionPoint(entity,missionAim,pathPoint) local center local radius local width local length local dir if pathPoint then center = pathPoint.Position if pathPoint.Radius then --圆形任务路点 radius = pathPoint.Radius else --矩形任务路点 width = pathPoint.Width length = pathPoint.Length dir = pathPoint.Direction end else center = missionAim.Data.Position if missionAim.Data.Radius then radius = missionAim.Data.Radius else width = missionAim.Data.Width length = missionAim.Data.Length dir = missionAim.Data.Direction end end local aimType = missionAim.Data.Type local missionFind = aimType == LuaConst.LevelConditionType.DeadNumber --优先查找任务目标 if missionFind then local itemList = missionAim.Data.ItemIDList local paramID = missionAim.Data.ParamID if itemList then --这种Aim类型是杀指定ItemID列表里的怪物,可能有多种怪物 for i,itemID in ipairs(itemList) do SystemManager.SearchEnemyMonsterOfLevelItem(entity, itemID, center, radius, length, width, dir) if entity.components.target.Target ~= nil then break end end elseif paramID and paramID > 0 then if radius then SystemManager.SearchEnemyCharAsTargetInCircle(entity, center, radius, missionAim.Data.ParamID, LuaConst.CharacterType.Monster) else SystemManager.SearchEnemyCharAsTargetInRect(entity, center, length, width, dir, missionAim.Data.ParamID, LuaConst.CharacterType.Monster) end end missionFind = entity.components.target.Target ~= nil end if not missionFind then if radius then SystemManager.SearchTargetInCircle(entity, center, radius, false, true, true) else SystemManager.SearchTargetInRect(entity, center, length, width, dir, false, true) end end end function Decision_PveFight:RunToNextPos(entity, pathPoint, command, missionAim, decisionData) if pathPoint then self:CommandFindPathAndRunTo(entity, pathPoint.Position, command, true, decisionData) else if missionAim then local pathComponent = entity.components.path local curPathPos = pathComponent.Path[pathComponent.CurPathPosIndex] if curPathPos == missionAim.Data.Position then self:CommandRunToPathPoint(entity, command, decisionData)--这个会走向最新的目标点 else self:RunToMissionPos(missionAim, entity, command, decisionData) end else self:CommandRunToPathPoint(entity, command, decisionData)--这个会走向最新的目标点 end end end function Decision_PveFight:IsMissionAim(target, missionAim) local isMissionAim = true --默认返回true, 如果当前没有Mission,或者遇到了空数据等异常情况,都返回true local aimData = missionAim.Data if aimData.Type == LuaConst.LevelConditionType.DeadNumber then if aimData.ParamID and aimData.ParamID > 0 then isMissionAim = target:GetCharacterID() == missionAim.Data.ParamID end end return isMissionAim end function Decision_PveFight:IsEnterPathPoint(entity,pathPoint,missionAim) if pathPoint then if pathPoint.Radius then return not FP.V2.IsDistanceGreater(entity:GetLogicPos(), pathPoint.Position, pathPoint.Radius) else return FP.V2.IsInRectRange(entity:GetLogicPos(),pathPoint.Position,pathPoint.Direction,pathPoint.Length,pathPoint.Width) end else if missionAim then if missionAim.Data.Radius then return not FP.V2.IsDistanceGreater(entity:GetLogicPos(), missionAim.Data.Position, missionAim.Data.Radius) else return FP.V2.IsInRectRange(entity:GetLogicPos(),missionAim.Data.Position,missionAim.Data.Direction,missionAim.Data.Length,missionAim.Data.Width) end else return entity.components.path:IsArrived() end end end local _tempPos = FP.newV2(0,0) function Decision_PveFight:RunToMissionPos(missionAim,entity,command,decisionData) local position = missionAim:GetCurrentPosition(entity:GetLogicPos()) if position then _tempPos:Reset(position) --self:CommandFindPathAndRunTo(entity,_tempPos,command,true,decisionData) --old 永远不刷新路径,可能会卡住 --new 0.5秒刷新一次路径 self:IntervalFindPath(entity,decisionData,command,500,nil,_tempPos) self:CommandRunToPathPoint(entity, command,decisionData) end end return Decision_PveFight