package.loaded.Constants = nil; require("Constants") require("Actors/AI/HumanBehaviors") --dofile("Base.rte/Actors/AI/HumanBehaviors.lua") require("Actors/AI/PID") function Create(self) ---------------- AI variables start ---------------- self.lateralMoveState = Actor.LAT_STILL self.proneState = AHuman.NOTPRONE self.jumpState = AHuman.NOTJUMPING self.deviceState = AHuman.STILL self.lastAIMode = Actor.AIMODE_NONE self.teamBlockState = Actor.NOTBLOCKED self.SentryFacing = self.HFlipped self.fire = false self.minBurstTime = math.min(200, self.JetTimeTotal*0.99) self.Ctrl = self:GetController() self.AirTimer = Timer() self.PickUpTimer = Timer() self.ReloadTimer = Timer() self.BlockedTimer = Timer() self.AlarmTimer = Timer() self.AlarmTimer:SetSimTimeLimitMS(500) self.TargetLostTimer = Timer() self.TargetLostTimer:SetSimTimeLimitMS(1500) -- check if this team is controlled by a human local Activ = ActivityMan:GetActivity() for player = Activity.PLAYER_1, Activity.MAXPLAYERCOUNT - 1 do if Activ:PlayerActive(player) and Activ:PlayerHuman(player) then if self.Team == Activ:GetTeamOfPlayer(player) then self.isPlayerOwned = true self.PlayerInterferedTimer = Timer() self.PlayerInterferedTimer:SetSimTimeLimitMS(500) break end end end -- functions that create behaviors. the default behaviors are stored in the HumanBehaviors table. store your custom behaviors in a table to avoid name conflicts between mods. function self:CreateSentryBehavior() if not self.FirearmIsReady and not self.ThrowableIsReady then if not self:EquipFirearm(true) then self:CreateGetWeaponBehavior() end return end self.NextBehavior = coroutine.create(HumanBehaviors.Sentry) -- replace "HumanBehaviors.Sentry" with the function name of your own sentry behavior self.NextCleanup = nil self.NextBehaviorName = "Sentry" end function self:CreatePatrolBehavior() self.NextBehavior = coroutine.create(HumanBehaviors.Patrol) self.NextCleanup = nil self.NextBehaviorName = "Patrol" end function self:CreateGoldDigBehavior() self.NextBehavior = coroutine.create(HumanBehaviors.GoldDig) self.NextCleanup = nil self.NextBehaviorName = "GoldDig" end function self:CreateBrainSearchBehavior() self.NextBehavior = coroutine.create(HumanBehaviors.BrainSearch) self.NextCleanup = nil self.NextBehaviorName = "BrainSearch" end function self:CreateGetToolBehavior() self.NextBehavior = coroutine.create(HumanBehaviors.ToolSearch) self.NextCleanup = nil self.NextBehaviorName = "ToolSearch" end function self:CreateGetWeaponBehavior() self.NextBehavior = coroutine.create(HumanBehaviors.WeaponSearch) self.NextCleanup = nil self.NextBehaviorName = "WeaponSearch" end function self:CreateGoToBehavior() self.NextGoTo = coroutine.create(HumanBehaviors.GoToWpt) self.NextGoToCleanup = function(self) self.lateralMoveState = Actor.LAT_STILL self.deviceState = AHuman.STILL self.crawling = false self.jump = false self.fire = false self:EquipFirearm(true) self:ClearMovePath() end self.NextGoToName = "GoToWpt" end function self:CreateAttackBehavior() if self:EquipFirearm(true) then self.NextBehavior = coroutine.create(HumanBehaviors.ShootTarget) self.NextBehaviorName = "ShootTarget" elseif self:EquipThrowable(true) then self.NextBehavior = coroutine.create(HumanBehaviors.ThrowTarget) self.NextBehaviorName = "ThrowTarget" elseif self:EquipDiggingTool(true) and SceneMan:ShortestDistance(self.Pos, self.Target.Pos, false).Magnitude < 150 then self.NextBehavior = coroutine.create(HumanBehaviors.AttackTarget) self.NextBehaviorName = "AttackTarget" else -- unarmed or far away if self.PickUpTimer:IsPastSimMS(2500) then self.PickUpTimer:Reset() self.NextBehavior = coroutine.create(HumanBehaviors.WeaponSearch) self.NextBehaviorName = "WeaponSearch" self.NextCleanup = nil return else -- there are probably no weapons around here (in the vicinity of an area adjacent to a location) self.NextBehavior = coroutine.create(HumanBehaviors.AttackTarget) self.NextBehaviorName = "AttackTarget" end end self.NextCleanup = function(self) self.fire = false self.canHitTarget = false self.deviceState = AHuman.STILL self.proneState = AHuman.NOTPRONE end end -- force the use of a digger when attacking function self:CreateHtHBehavior() if self.Target and self:EquipDiggingTool(true) then self.NextBehavior = coroutine.create(HumanBehaviors.AttackTarget) self.NextBehaviorName = "AttackTarget" self.NextCleanup = function(self) self.fire = false self.Target = nil self.deviceState = AHuman.STILL self.proneState = AHuman.NOTPRONE end end end function self:CreateSuppressBehavior() if self:EquipFirearm(true) then self.NextBehavior = coroutine.create(HumanBehaviors.ShootArea) self.NextBehaviorName = "ShootArea" else if self.FirearmIsEmpty then self:ReloadFirearm() end return end self.NextCleanup = function(self) self.fire = false self.UnseenTarget = nil self.deviceState = AHuman.STILL self.proneState = AHuman.NOTPRONE end end function self:CreateMoveAroundBehavior() self.NextGoTo = coroutine.create(HumanBehaviors.MoveAroundActor) self.NextGoToCleanup = function(self) self.lateralMoveState = Actor.LAT_STILL self.jump = false end self.NextGoToName = "MoveAroundActor" end -- the controllers self.XposPID = RegulatorPID:New{p=0.04, i=0.00001, d=0.4, filter_leak=0.8, integral_max=100} self.YposPID = RegulatorPID:New{p=0.05, i=0.0001, d=0.2, filter_leak=0.8, integral_max=100} ---------------- AI variables end ---------------- end function UpdateAI(self) if self.isPlayerOwned then if self.PlayerInterferedTimer:IsPastSimTimeLimit() then self.Behavior = nil -- remove the current behavior if self.BehaviorCleanup then self.BehaviorCleanup(self) -- clean up after the current behavior self.BehaviorCleanup = nil end self.GoToBehavior = nil if self.GoToCleanup then self.GoToCleanup(self) self.GoToCleanup = nil end self.Target = nil self.UnseenTarget = nil self.OldTargetPos = nil self.PickupHD = nil self.BlockingActor = nil self.FollowingActor = nil self.fire = false self.canHitTarget = false self.jump = false self.crawling = false self.SentryFacing = self.HFlipped self.deviceState = AHuman.STILL self.lastAIMode = Actor.AIMODE_NONE self.teamBlockState = Actor.NOTBLOCKED if self.EquippedItem then self.PlayerPreferredHD = self.EquippedItem.PresetName else self.PlayerPreferredHD = nil end end self.PlayerInterferedTimer:Reset() end if self.Target and not MovableMan:IsActor(self.Target) then self.Target = nil end if self.UnseenTarget and not MovableMan:IsActor(self.UnseenTarget) then self.UnseenTarget = nil end -- switch to the next behavior, if avaliable if self.NextBehavior then if self.BehaviorCleanup then self.BehaviorCleanup(self) end self.Behavior = self.NextBehavior self.BehaviorCleanup = self.NextCleanup self.BehaviorName = self.NextBehaviorName self.NextBehavior = nil self.NextCleanup = nil self.NextBehaviorName = nil end -- switch to the next GoTo behavior, if avaliable if self.NextGoTo then if self.GoToCleanup then self.GoToCleanup(self) end self.GoToBehavior = self.NextGoTo self.GoToCleanup = self.NextGoToCleanup self.GoToName = self.NextGoToName self.NextGoTo = nil self.NextGoToCleanup = nil self.NextGoToName = nil end -- check if the AI mode has changed or if we need a new behavior if self.AIMode ~= self.lastAIMode or (not self.Behavior and not self.GoToBehavior) then self.Behavior = nil if self.BehaviorCleanup then self.BehaviorCleanup(self) -- stop the current behavior self.BehaviorCleanup = nil end self.GoToBehavior = nil if self.GoToCleanup then self.GoToCleanup(self) self.GoToCleanup = nil end -- select a new behavior based on AI mode if self.AIMode == Actor.AIMODE_GOTO then self:CreateGoToBehavior() elseif self.AIMode == Actor.AIMODE_BRAINHUNT then self:CreateBrainSearchBehavior() elseif self.AIMode == Actor.AIMODE_GOLDDIG then self:CreateGoldDigBehavior() elseif self.AIMode == Actor.AIMODE_PATROL then self:CreatePatrolBehavior() else if self.AIMode ~= self.lastAIMode and self.AIMode == Actor.AIMODE_SENTRY then self.SentryFacing = self.HFlipped -- store the direction in which we should be looking self.SentryPos = Vector(self.Pos.X, self.Pos.Y) -- store the pos on which we should be standing end self:CreateSentryBehavior() end self.lastAIMode = self.AIMode end -- check if the legs reach the ground if self.AirTimer:IsPastSimMS(250) then self.AirTimer:Reset() if -1 < SceneMan:CastObstacleRay(self.Pos, Vector(0, self.Height/4), Vector(), Vector(), self.ID, self.IgnoresWhichTeam, rte.grassID, 3) then self.flying = false else self.flying = true end self:EquipShieldInBGArm() -- try to equip a shield end -- look for targets local FoundMO, HitPoint = HumanBehaviors.LookForTargets(self) if FoundMO then if self.Target and MovableMan:IsActor(self.Target) and FoundMO.ID == self.Target.ID then -- found the same target self.TargetOffset = SceneMan:ShortestDistance(self.Target.Pos, HitPoint, false) self.TargetLostTimer:Reset() self.ReloadTimer:Reset() elseif MovableMan:IsActor(FoundMO) then if FoundMO.Team == self.Team then -- found an ally if self.Target then if SceneMan:ShortestDistance(self.Pos, FoundMO.Pos, false).Magnitude < SceneMan:ShortestDistance(self.Pos, self.Target.Pos, false).Magnitude then self.Target = nil -- stop shooting end elseif FoundMO.ClassName ~= "ADoor" and SceneMan:ShortestDistance(self.Pos, FoundMO.Pos, false).Magnitude < self.Diameter + FoundMO.Diameter then self.BlockingActor = ToActor(FoundMO) -- this actor is blocking our path end else if FoundMO.ClassName == "AHuman" then FoundMO = ToAHuman(FoundMO) elseif FoundMO.ClassName == "ACrab" then FoundMO = ToACrab(FoundMO) elseif FoundMO.ClassName == "ACRocket" then FoundMO = ToACRocket(FoundMO) elseif FoundMO.ClassName == "ACDropShip" then FoundMO = ToACDropShip(FoundMO) elseif FoundMO.ClassName == "ADoor" then FoundMO = ToADoor(FoundMO) elseif FoundMO.ClassName == "Actor" then FoundMO = ToActor(FoundMO) else FoundMO = nil end if FoundMO then self.ReloadTimer:Reset() self.TargetLostTimer:Reset() if self.Target then -- check if this MO sould be targeted instead if HumanBehaviors.CalculateThreatLevel(self, FoundMO) > HumanBehaviors.CalculateThreatLevel(self, self.Target) + 0.2 then self.OldTargetPos = self.Target.Pos self.Target = FoundMO self.TargetOffset = SceneMan:ShortestDistance(self.Target.Pos, HitPoint, false) -- this is the distance vector from the target center to the point we hit with our ray self:CreateAttackBehavior() end else self.OldTargetPos = nil self.Target = FoundMO self.TargetOffset = SceneMan:ShortestDistance(self.Target.Pos, HitPoint, false) -- this is the distance vector from the target center to the point we hit with our ray self:CreateAttackBehavior() end end end end else -- no target found this frame if self.Target and self.TargetLostTimer:IsPastSimTimeLimit() then self.OldTargetPos = self.Target.Pos self.Target = nil -- the target has been out of sight for too long, ignore it end if self.ReloadTimer:IsPastSimMS(8000) then -- check if we need to reload self.ReloadTimer:Reset() if ToAHuman(self).FirearmNeedsReload then self:ReloadFirearm() end end end -- run the move behavior and delete it if it returns true if self.GoToBehavior then local msg, done = coroutine.resume(self.GoToBehavior, self) if not msg then ConsoleMan:PrintString(self.PresetName .. " " .. self.GoToName .. " error:\n" .. done) -- print the error message done = true end if done then self.GoToBehavior = nil if self.GoToCleanup then self.GoToCleanup(self) self.GoToCleanup = nil end end elseif self.flying then -- avoid falling damage if (not self.jump and self.Vel.Y > 9) or (self.jump and self.Vel.Y > 6) then self.jump = true -- try falling straight down if not self.Target then if self.Vel.X > 2 then self.lateralMoveState = Actor.LAT_LEFT elseif self.Vel.X < -2 then self.lateralMoveState = Actor.LAT_RIGHT else self.lateralMoveState = Actor.LAT_STILL end end else self.jump = false self.lateralMoveState = Actor.LAT_STILL end --else --self.jump = false --self.lateralMoveState = Actor.LAT_STILL end -- run the selected behavior and delete it if it returns true if self.Behavior then local msg, done = coroutine.resume(self.Behavior, self) if not msg then ConsoleMan:PrintString(self.PresetName .. " behavior " .. self.BehaviorName .. " error:\n" .. done) -- print the error message done = true end if done then self.Behavior = nil if self.BehaviorCleanup then self.BehaviorCleanup(self) self.BehaviorCleanup = nil end if not self.NextBehavior and not self.PickupHD and self.PickUpTimer:IsPastSimMS(10000) then self.PickUpTimer:Reset() if not self:EquipFirearm(false) then self:CreateGetWeaponBehavior() elseif self.AIMode ~= Actor.AIMODE_SENTRY and not self:EquipDiggingTool(false) then self:CreateGetToolBehavior() end end end end if self.PickupHD then -- there is a HeldDevice we want to pick up if not MovableMan:IsDevice(self.PickupHD) or self.PickupHD.ID ~= self.PickupHD.RootID then self.PickupHD = nil -- the HeldDevice has been destroyed or picked up self:ClearAIWaypoints() if self.PrevAIWaypoint or self.FollowingActor then if self.FollowingActor and MovableMan:IsActor(self.FollowingActor) then -- what if the old destination was a moving actor? self:AddAIMOWaypoint(self.FollowingActor) self:CreateGoToBehavior() -- continue towards our old destination else self.FollowingActor = nil if self.PrevAIWaypoint then self:AddAISceneWaypoint(self.PrevAIWaypoint) self:CreateGoToBehavior() -- continue towards our old destination end end end else if SceneMan:ShortestDistance(self.Pos, self.PickupHD.Pos, false).Magnitude < self.Height then self.Ctrl:SetState(Controller.WEAPON_PICKUP, true) end end end -- react to relevant AlarmEvents if not self.Target and not self.UnseenTarget then if self.AlarmTimer:IsPastSimTimeLimit() and HumanBehaviors.ProcessAlarmEvent(self) then self.AlarmTimer:Reset() elseif self.AlarmPos then -- if alarmed, look at the alarming point until AlarmTimer has expired self.jump = false self.deviceState = AHuman.AIMING self.lateralMoveState = Actor.LAT_STILL -- stop any behavior that can interfere with the aiming self.Behavior = nil if self.BehaviorCleanup then self.BehaviorCleanup(self) self.BehaviorCleanup = nil end -- look in the direction of alarm local aim = self:GetAimAngle(true) local angDiff = aim - SceneMan:ShortestDistance(self.EyePos, self.AlarmPos, false).AbsRadAngle if angDiff > math.pi then angDiff = angDiff - math.pi*2 elseif angDiff < -math.pi then angDiff = angDiff + math.pi*2 end self.Ctrl.AnalogAim = Vector(1,0):RadRotate(aim-angDiff*0.2) -- smooth end end if self.teamBlockState == Actor.IGNORINGBLOCK then if self.BlockedTimer:IsPastSimMS(10000) then self.teamBlockState = Actor.NOTBLOCKED end elseif self.teamBlockState == Actor.BLOCKED then -- we are blocked by a teammate, stop self.lateralMoveState = Actor.LAT_STILL self.jump = false if self.BlockedTimer:IsPastSimMS(20000) then self.BlockedTimer:Reset() self.teamBlockState = Actor.IGNORINGBLOCK end else self.BlockedTimer:Reset() end -- controller states if self.fire then self.Ctrl:SetState(Controller.WEAPON_FIRE, true) end if self.deviceState == AHuman.AIMING then self.Ctrl:SetState(Controller.AIM_SHARP, true) end if self.jump and self.JetTimeLeft > TimerMan.DeltaTimeMS then if self.jumpState == AHuman.PREJUMP then self.jumpState = AHuman.UPJUMP elseif self.jumpState ~= AHuman.UPJUMP then -- the jetpack is off if self.JetTimeLeft >= self.minBurstTime then -- don't start the jetpack unless there is enough fuel to fire a burst that is long enough to be useful self.jumpState = AHuman.PREJUMP else self.jumpState = AHuman.NOTJUMPING end end else self.jumpState = AHuman.NOTJUMPING end if self.jumpState == AHuman.PREJUMP then self.Ctrl:SetState(Controller.BODY_JUMPSTART, true) -- trigger burst elseif self.jumpState == AHuman.UPJUMP then self.Ctrl:SetState(Controller.BODY_JUMP, true) -- trigger normal jetpack emission end if self.proneState == AHuman.GOPRONE then self.proneState = AHuman.PRONE elseif self.proneState == AHuman.PRONE or self.crawling then self.Ctrl:SetState(Controller.BODY_CROUCH, true) end if self.lateralMoveState == Actor.LAT_LEFT then self.Ctrl:SetState(Controller.MOVE_LEFT, true) elseif self.lateralMoveState == Actor.LAT_RIGHT then self.Ctrl:SetState(Controller.MOVE_RIGHT, true) end end