--(AI结构: 不同的ai(一堆AIID) --包含多个-> 单个ai(确定的AIID) --包含多个-> 优先级决策组(decisionGroup,具有priority) --包含多个-> 决策(decision)) --AiObject可以合并多个ai(多个AIID),也可以只有1个ai(单个AIID) --AIBaseComponent同一时间只会跑1个ai,可以切换不同ai --同一时间,只会运行1个decision local AIBaseComponent = class("AIBaseComponent") --加载需要的模块 local AiObject = require "Battle/Logic/Entity/Component/AI/AiObject" local AiData = require("Battle/Logic/AI/Data/AiData") local LuaConst=LuaConst function AIBaseComponent:initialize(entity) self.entity = entity --当前所处的State self.CurStateType = LuaConst.StateType.None --进入common状态时 ,被某buffid驱动 self.CommonStateByBuff = nil --当前所处的Decision self.CurDecisionID = nil --当前所处的Decision优先级 self.CurDecisionPriority = -1 --可以打断当前Decision的最低优先级 self.CurMinInterruptPriority = -1 --当前Decision开始的帧数 self.CurDecisionStartFrame = 0 --AI是否激活 self.IsAIActive = false ----------------------------------------- --存储该Entity的AI self.CharacterId2AiObjectDic = {} self.Aiid2AiObjectDic = {} --当前使用的AI self.CurAiObject = nil --当前的入口Decision数据,是最低优先级(目前都是0)的第1个Decision self.CurEntryData = nil --当前所处的State的引用 self.CurStateNode = nil --当前Decision数据 self.CurDecisionData = nil --当前Decision节点 self.CurDecisionNode = nil --决策记录数据字典(白板数据) self.DecisionRecordDic = {} --key为DecisionGruop的ID。value为record字典 --DecisionGroup进入次数计数(每次切换AI都会清空) self.DecisionGroupEnterCount = 0 --这里没法初始化AiUpdateBatchIndex,因为这里entityId还没有被赋值 self.AiUpdateBatchIndex = 0 self.EntityRecord = {} self.ForbidAiSkillType2DurationDic = {} --key是AiSkillType,value是持续时间 self.AISkillJoystickPos = FP.newV2(0, 0) --AI模拟输入的双摇杆位置 self.CalledSkillJoystickEndPos = nil --FP.newV2(0, 0) --通过技能摇杆设置的召唤物的终点位置 local characterData = entity._logicInitData._characterData self:SetCurrentAIData(entity, characterData) end function AIBaseComponent:OnEntityInitialized(entity) if entity._logicInitData.EntityID and entity._logicInitData.EntityID > 0 then self.AiUpdateBatchIndex = entity._logicInitData.EntityID % self:GetAiUpdateInterval() end end --返回ai的刷新间隔,是帧数。1代表每帧都刷新,5代表5帧刷新一次 function AIBaseComponent:GetAiUpdateInterval() if self.IsNotSlowAI then return 1 else return LuaConst.AIHandleTick end end function AIBaseComponent:SetRunToDynamicPos(pos) self.runToDynamicPos = pos end function AIBaseComponent:SetRunToAndInteractEntity(entity) self.runToAndInteractEntity = entity end --[[ 设置当前Decision --]] function AIBaseComponent:SetCurDecision(id) self.CurDecisionID = id self.CurDecisionStartFrame = LogicUpdateManager._clientHandleFrame self:UpdateDecisionCache() end --清理当前状态 --这个方法会整合多个ai GetMergedAiObjectByCharacterData会整合character的AIID的ai,以及一些默认Ai到一个AiObject function AIBaseComponent:SetCurrentAIData(entity, characterData) if not characterData then return end local characterID = characterData.ID self.IsNotSlowAI = characterData.IsNotSlowAI local aiObject = self.CharacterId2AiObjectDic[characterID] if not aiObject then aiObject = self:GetMergedAiObjectByCharacterData(entity, characterData) self.CharacterId2AiObjectDic[characterID] = aiObject self.Aiid2AiObjectDic[aiObject.aiid] = aiObject end self.updateTimeElaMs = self:GetAiUpdateInterval() * LuaConst.TimeSpanMs self:SetAiObject(aiObject) end function AIBaseComponent:SetAiObject(aiObject) local oldAiObject = self.CurAiObject self.CurAiObject = aiObject self.Aiid2AiObjectDic[aiObject.aiid] = aiObject self.CurEntryData = aiObject.PriorityDecisionGroupList[1].DecisionDataList[1] self:OnAiChanged(oldAiObject,self.CurAiObject) end --基于已初始化的AI做AI切换 function AIBaseComponent:ChangeAiByAIID(aiid) if self.CurAiObject then if self.CurAiObject.isSingle then --初始为single的AI,以后也只能切换为single的AI self:SetSingleCurrentAiByID(aiid) else local aiObject = self:GetAiObjectFromPool(aiid) if aiObject == nil then aiObject = AiObject:new(aiid) aiObject.CharacterID = self.CurAiObject.ID aiObject.CharacterType = self.CurAiObject.CharacterType aiObject.NeedSyncInfo = self.CurAiObject.NeedSyncInfo aiObject.PatrolState = self.CurAiObject.PatrolState aiObject.AwsOpenAI = self.CurAiObject.AwsOpenAI self:SetCommonAiData(aiObject) end self:SetAiObject(aiObject) end else logging.error(LTAG.ERROR, "HYW : 当前没有初始化AI,无法改变AI") end end function AIBaseComponent:SetLevelMessageID(id) self.LevelMessageID = id end function AIBaseComponent:ReSetLevelMessageID() if self.LevelMessageID then self.LevelMessageID = nil end end --使用V2的Reset方法设置 function AIBaseComponent:SetAISkillJoystickPos(pos) self.AISkillJoystickPos:Reset(pos) end function AIBaseComponent:SetCalledSkillJoystickEndPos(pos) self.CalledSkillJoystickEndPos = pos end ----------------------------------------------------------------------------------------- --[[ 初始化 会整合character的AIID的ai,以及一些默认Ai到一个AiObject --]] function AIBaseComponent:GetMergedAiObjectByCharacterData(entity, characterData) local aiObject = self:GetAiObjectFromPool(characterData.AIID) if aiObject == nil then aiObject = AiObject:new(characterData.AIID) aiObject.CharacterID = characterData.ID aiObject.CharacterType = entity._logicInitData.CharacterType aiObject.NeedSyncInfo = entity._logicInitData.NeedSyncInfo aiObject.PatrolState = characterData.PatrolState aiObject.AwsOpenAI = characterData.AwsOpenAI aiObject.OpenTargetFindAI = characterData.TargetFindAI self:SetCommonAiData(aiObject) end return aiObject end --设置常规AI的数据,可能会做一些统一的ai处理,包括合并一些通用ai等 function AIBaseComponent:SetCommonAiData(aiObject) if aiObject.CharacterType == LuaConst.CharacterType.NPC then if aiObject.PatrolState then --local selfData = MonsterAIDecisionData[LuaConst.NpcPatrolAIID] local selfData = AiData[LuaConst.NpcPatrolAIID] aiObject:AppendAIData(selfData) else --local selfData = MonsterAIDecisionData[LuaConst.IdleAIID] local selfData = AiData[LuaConst.IdleAIID] aiObject:AppendAIData(selfData) end else --local selfData = MonsterAIDecisionData[aiObject.aiid] local selfData = AiData[aiObject.aiid] if selfData then aiObject:AppendAIData(selfData) else logging.error(LTAG.ERROR, "HYW : 找不到AI : ", aiObject.aiid, " 请联系战斗策划 characterID : " .. tostring(self.entity:GetCharacterID())) end if not aiObject.NeedSyncInfo then --非玩家 if not aiObject.AwsOpenAI then --不跳过通用AI aiObject:AppendAIData(AiData[LuaConst.CommonAIID]) --设置通用AI end if aiObject.OpenTargetFindAI then aiObject:AppendAIData(AiData[LuaConst.TargetFindAI]) --设置 目标寻找AI end --local routData = MonsterAIDecisionData[LuaConst.RoutAIID] local routData = AiData[LuaConst.RoutAIID] aiObject:AppendAIData(routData) --叠加 end end end --[[ 更新缓存 - Decision相关 --]] function AIBaseComponent:UpdateDecisionCache() --self.CurDecisionData = DecisionData[self.CurDecisionID] self.CurDecisionData = DecisionManager.GetDecisionData(self.CurDecisionID) if self.CurDecisionData then self.CurDecisionNode = DecisionManager.GetDecision(self.CurDecisionData.Type) else self.CurDecisionNode = nil end end --[[ 根据MonsterAIDecisionID 切换AI --]] --这个方法只会设置1个aiid的ai,不会整合多个ai function AIBaseComponent:SetSingleCurrentAiByID(aiid) if not aiid then return end if self.CurAiObject == nil or self.CurAiObject.aiid ~= aiid then local aiObject = self:GetAiObjectFromPool(aiid) if aiObject == nil then aiObject = AiObject:new(aiid) aiObject.isSingle = true aiObject:AppendAIData(AiData[aiid]) end self:SetAiObject(aiObject) end end function AIBaseComponent:GetAiObjectFromPool(aiid) return self.Aiid2AiObjectDic[aiid] end --每次刷新AI后调用 function AIBaseComponent:AfterAiUpdate() self:UpdateForbidAiSkill(self.updateTimeElaMs) end function AIBaseComponent:UpdateForbidAiSkill(timeElaMs) for aiSkillType,duration in pairs(self.ForbidAiSkillType2DurationDic) do -- pairs safe 纯数据刷新,对顺序不敏感 if duration > FP.Const.Zero then duration:Sub(timeElaMs) end end end function AIBaseComponent:OnAiChanged(oldAiObject,newAiObject) if oldAiObject then oldAiObject:Clear() end self.DecisionGroupEnterCount = 0 self:RunEntryDecisionGroup(true) end function AIBaseComponent:RunEntryDecisionGroup(dontEnter) self.CurDecisionPriority = 0 self.CurMinInterruptPriority = 0 if self.CurEntryData then self:SetNextDecision(self.CurEntryData,false,dontEnter) end end function AIBaseComponent:SetNextDecision(decisionData,exitLastDecision,dontEnter) --退出上一个Decision if exitLastDecision and self.CurDecisionNode then self.CurDecisionNode:DecisionOut(self.entity,self.CurDecisionData) end --设置新的Decision self:SetCurDecision(decisionData.ID) --进入新的Decision if not dontEnter then self:DecisionIn() end end --duration必须传FP function AIBaseComponent:ForbidSkillByAiSkillType(skillType, duration) local oldDuration = self.ForbidAiSkillType2DurationDic[skillType] or FP.Const.Zero if duration > oldDuration then self.ForbidAiSkillType2DurationDic[skillType] = duration end end function AIBaseComponent:IsForidSkill(skillData) return self.ForbidAiSkillType2DurationDic[skillData.AiSkillType] and self.ForbidAiSkillType2DurationDic[skillData.AiSkillType] > FP.Const.Zero end --是否正在溃散 function AIBaseComponent:IsRouting() return self.isRouting end function AIBaseComponent:SetIsRouting(value) self.isRouting = value end function AIBaseComponent:SetIsBreakByInput(value) self.isBreakByInput = value end function AIBaseComponent:StartTimer(key,mode) self.CurAiObject:StartTimer(key,mode) end function AIBaseComponent:StopTimer(key) self.CurAiObject:StopTimer(key) end function AIBaseComponent:GetTimerElapsed(key) return self.CurAiObject:GetTimerElapsed(key) end function AIBaseComponent:HasTimer(key) return self.CurAiObject:HasTimer(key) end function AIBaseComponent:GetDecisionGroupByPriority(priority) --new↓ 充分考虑了combine AI的情况 for i,v in ipairs(self.CurAiObject.PriorityDecisionGroupList) do if v.Data.Priority == priority then return v end end --new↑ --return self.CurAiObject.PriorityDecisionGroupList[priority] --old 没有考虑combine AI的情况 end function AIBaseComponent:SetCurDecisionGroup(priorityDecisionGroup) self.PriorityDecisionGroup = priorityDecisionGroup self.CurDecisionPriority = self.PriorityDecisionGroup.Data.Priority self.CurMinInterruptPriority = self.PriorityDecisionGroup.Data.MinInterruptPriority self.PriorityDecisionGroup.EnterCount = self.PriorityDecisionGroup.EnterCount + 1 end function AIBaseComponent:DecisionIn() if self.CurDecisionNode then self.CurDecisionNode:DecisionIn(self.entity, self.CurDecisionData) --else --正常现象,比如AI被打断了(SystemManager.OnConvey等会打断AI) end end --单个ai内共享的临时数据存放点(AI结构: 不同的ai(一堆AIID) --包含多个-> 单个ai(确定的AIID) --包含多个-> 单个condition节点(decisionGroup) --包含多个-> 单个decision(decisionData)) --开放给Ai配置,Decision节点可以Set,条件节点可以Get function AIBaseComponent:SetAiRecord(key,value) self.CurAiObject:SetAiRecord(key,value) end function AIBaseComponent:GetAIID() local aiid = -1 if self.CurAiObject then aiid = self.CurAiObject.aiid end return aiid end function AIBaseComponent:GetAiRecord(key) return self.CurAiObject:GetAiRecord(key) end function AIBaseComponent:GetAiRecordDic() return self.CurAiObject.aiRecordDic end -- 跨单位的Record,Decision可以用这个来进行跨单位通信 function AIBaseComponent:SetGlobalRecord(key, value) DecisionManager.globalRecord[key] = value end function AIBaseComponent:GetGlobalRecord(key, value) return DecisionManager.globalRecord[key] end --跨AiObject的Record,开放给Ai配置,Decision节点可以Set,条件节点可以Get --开放给策划配置的record的key都是整数 --另外Decision的持久化Record也会放在这里,key是string,是Deicision的ID,是类似"1136_2_0"这种带下划线的多段数字组合字符串 --※Decision持久化Record(DecisionEntityRecord)的概念 :区别于DecisionRecord,DecisionRecord通常会在DecisionOut的时候被清空。而DecisionEntityRecord则不会。 function AIBaseComponent:SetEntityRecord(key,value) self.EntityRecord[key] = value end function AIBaseComponent:GetEntityRecord(key) return self.EntityRecord[key] end --未完成HYW decisionRecord得移动到aiObject里去 --decision临时数据存放点,只有程序用,不会开放给策划配置 function AIBaseComponent:SetDecisionRecord(decisionData,key,record) local recordDic = self:GetDecisionRecordDic(decisionData) recordDic[key] = record end --根据deicionData获取AI动态记录的数据(白板数据) function AIBaseComponent:GetDecisionRecordDic(decisionData) local recordDic = self.DecisionRecordDic[decisionData.ID] if not recordDic then recordDic = {} self.DecisionRecordDic[decisionData.ID] = recordDic end return recordDic end function AIBaseComponent:GetDecisionRecord(decisionData,key) return self:GetDecisionRecordDic(decisionData)[key] end function AIBaseComponent:ClearDecisionRecord(decisionData) self.DecisionRecordDic[decisionData.ID] = nil end function AIBaseComponent:ClearAllDecisionRecord() self.DecisionRecordDic = {} end function AIBaseComponent:OnStopAutoFight() if self.CurAiObject then self.CurAiObject:Clear() end end function AIBaseComponent:Dispose() self.IsAIActive = false self:ClearAllDecisionRecord() end return AIBaseComponent