local statemachine = require("ai.statemachine")
local helper = require("son.helper")
local poilib = require("behavior.poi")
local followpointcloud = require("ai.followpointcloud")
local theaterofoperations = require("ai.theaterofoperations")
local DL = require("design.DesignerLibrary")
local positioning = require("behavior.positioning")
local FWP = require("behavior.followWayPoints")
local navCurve = require("behavior.navCurve")
local locomotion = require("creature.locomotion")
local thunk = require("core.thunk")
MotionBrain = statemachine.StateMachine.New("MotionBrain")
local TargetMotionLogic = MotionBrain:State("TargetMotionLogic")
local POI_Nav = MotionBrain:State("POI_Nav")
local CombatNav = MotionBrain:State("CombatNav")
local CombatBossNav = MotionBrain:State("CombatBossNav")
local CombatPOINav = MotionBrain:State("CombatPOINav")
local SoloCombatNav = MotionBrain:State("SoloCombatNav")
local SplineNav = MotionBrain:State("SplineNav")
local FollowPlayerNav = MotionBrain:State("FollowPlayerNav")
local ManualStrafeSpline = MotionBrain:State("ManualStrafeSpline")
local CustomNav = MotionBrain:State("CustomNav")
local StandGround = MotionBrain:State("StandGround")
local ForceTheaterOperation = MotionBrain:State("ForceTheaterOperation")
statemachine.AddTags(ForceTheaterOperation, "SuppressAutonomousShots")
local TheaterStayOnPathTimer = MotionBrain:Timer("TheaterStayOnPathTimer") .. {period = 150}
local InVehicle = MotionBrain:State("InVehicle")
local ForcedPathNav = MotionBrain:State("ForcedPathNav")
local newPositioningInputs = {
  CurrentZoneSet = "SonPosition",
  Constants = {OccupancyRadius = 1.5},
  SeparationConstraints = {
    NonAggressive = {
      Mass = 10000,
      {
        Constraint = "RadialDistance",
        Value = 3
      }
    },
    Aggressive = {
      Mass = 10000,
      {
        Constraint = "RadialDistance",
        Value = 3
      }
    }
  },
  Zones = {
    SonPosition = {
      NonAggressive = {
        {
          Constraints = {
            {
              Constraint = "Distance",
              Min = 0,
              Max = 3,
              SonOnly = true
            }
          }
        }
      },
      Aggressive = {
        {
          Constraints = {
            {
              Constraint = "Distance",
              Min = 0,
              Max = 3,
              SonOnly = true
            }
          }
        }
      }
    }
  }
}
function MotionBrain:SelectNextState(ai, global, constants)
  TheaterResetPath(ai, global, constants)
  if CombatPOINav:IsAvailable(ai, global, constants) then
    return CombatPOINav
  end
  local curve = ai:GetNavCurve()
  if global.POIInfo.useThisPOI and ai.OwnedPOI then
    local POIEnding = ai.OwnedPOI and ai.OwnedPOI:GetStageName() == "ApproachOrMovingBreakOut"
    if global.POIInfo.approach and not POIEnding then
      return POI_Nav
    end
    if global.POIInfo.useThisPOI ~= nil and global.POIInfo.useThisPOI.Type ~= nil and (global.POIInfo.useThisPOI.Type == "KeepEvaluatingPath" or global.POIInfo.useThisPOI.Type == "GoToPoint" or global.POIInfo.useThisPOI.Type == "ObserveAndWait" or global.POIInfo.useThisPOI.Type == "ForcedGoToPoint" or global.POIInfo.useThisPOI.Type == "DispellDoor" or POIEnding or global.POIInfo.useThisPOI:FindLuaTableAttribute("UseSteadyOnExit") == false) then
      if curve then
        return SplineNav
      else
        return FollowPlayerNav
      end
    end
    return POI_Nav
  end
  if ai:IsInVehicle() then
    return InVehicle
  end
  if SoloCombatNav:IsAvailable(ai, global, constants) then
    return SoloCombatNav
  end
  if StandGround:IsAvailable(ai, global, constants) then
    return StandGround
  end
  if ForceTheaterOperation:IsAvailable(ai, global, constants) then
    return ForceTheaterOperation
  end
  if global.bInCombat and constants.inBossFight and ai:IsAvailableForCombat() then
    return CombatBossNav
  end
  if global.bInCombat and ai:IsAvailableForCombat() then
    return CombatNav
  end
  if ForcedPathNav:IsAvailable(ai, global, constants) then
    return ForcedPathNav
  end
  if curve then
    return SplineNav
  end
  if CustomNav:IsAvailable(ai, global, constants) then
    return CustomNav
  end
  return FollowPlayerNav
end
function BackpedalTest(ai)
  if _G.constants.awarenessState ~= "Backpedal" then
    return false
  end
  if ai:IsInNavigationMove() and not ai:HasMarker("CombatBackpedal") then
    ai:TriggerMoveEvent("kLEBackpedal")
  end
  local target = _G.constants.GO_dodgeObject
  if target == nil then
    return
  end
  local targetPosition = target:GetWorldPosition()
  local targetFacing = (ai:GetWorldPosition() - target:GetWorldPosition()):Normalized()
  local aiPosition = ai:GetWorldPosition()
  local destination = targetPosition + targetFacing * 10
  local facing = ai:GetWorldForward()
  if _G.global.InCombat_backpedalPosSet == false then
    _G.global.backPedalDestination = destination
    _G.global.InCombat_backpedalPosSet = true
  end
  local locomotionInfo = ai:GetLocomotionInfo()
  if locomotionInfo.PathDirection then
    facing = -locomotionInfo.PathDirection
  end
  if locomotionInfo.PathLength <= 1 then
    _G.global.nearDestination = true
  else
    _G.global.nearDestination = false
  end
  locomotion.SetActuator(ai, {
    Destination = _G.global.backPedalDestination,
    Facing = facing,
    Strafe = true,
    Speed = 4
  })
  return true
end
function DisableLeashing()
  print("Leashing is disabled!")
  _G.global.leadTheWayParams.EnableLeashing = false
end
function EnableLeashing()
  print("Leashing is enabled!")
  _G.global.leadTheWayParams.EnableLeashing = true
end
function WaitGateEnter()
  print("Entered wait gate")
  local sonbb = game.AI.FindSon():GetPrivateBlackboard()
  sonbb:Set("inWaitGate", true)
end
function WaitGateExit()
  print("Exited wait gate")
  local sonbb = game.AI.FindSon():GetPrivateBlackboard()
  sonbb:Set("inWaitGate", false)
end
function SplineNav:Enter(ai, global, constants)
  constants.splineNav = true
  ai:SetDecelerationOverride(2.75)
end
function SplineNav:Update(ai, global, constants)
  local curve = ai:GetNavCurve()
  navCurve.LeadTheWay(ai, curve, global.leadTheWayParams)
end
function SplineNav:Exit(ai, global, constants)
  ai:ClearMaxSpeedOverride()
  ai:ClearDecelerationOverride()
  constants.splineNav = nil
end
function SubmitNavCurve(ai, data)
  locomotion.SubmitNavCurve(_G.constants, data)
end
function InVehicle:Update(ai, global, constants)
end
function InVehicle:IsAvailable(ai, global, constants)
  return ai:IsInVehicle()
end
function InVehicle:Exit(ai, global, constants)
end
function InVehicle:OnBrainInit(ai, global, constants)
end
function FollowPlayerNav:Update(ai, global, constants)
  if constants.motionType == "UseFollowPoint" then
    local finalPos = global.Player:GetWorldPosition()
    local totalDistance = finalPos:Distance(ai:GetWorldPosition())
    local playerSpeed = global.Player:GetVelocity():Length()
    local speed = 6
    if 15 < totalDistance or 4 < playerSpeed then
      speed = 6
    elseif 10 < totalDistance or 3 < playerSpeed then
      speed = 4
    elseif 3 < totalDistance or 2.5 < playerSpeed then
      speed = 4
    else
      speed = 3
    end
    if (ai.WorldPosition - finalPos):Length() > 1 then
      locomotion.SetActuator(ai, {
        Destination = finalPos,
        Facing = ai:GetWorldForward(),
        Speed = speed,
        Strafe = false
      })
    end
  elseif constants.motionType == "Idle" then
    locomotion.SetActuator(ai, {
      Destination = ai.WorldPosition,
      Facing = ai:GetWorldForward(),
      Speed = 0
    })
  elseif constants.motionType == "GoToPoint" and constants.moveToPointActuator ~= nil then
    locomotion.SetActuator(ai, {
      Destination = constants.moveToPointActuator.Destination,
      Facing = ai:GetWorldForward(),
      Speed = constants.moveToPointActuator.Speed,
      StopDistance = constants.moveToPointActuator.StopDistance,
      StartDistance = constants.moveToPointActuator.StartDistance,
      AllowCloseRangePathfind = constants.moveToPointActuator.UseCloseRangeApproach
    })
  end
end
function FollowPlayerNav:Enter(ai, global, constants)
  self.desiredDirectionDelta = 0
end
function FollowPlayerNav:Exit(ai, global, constants)
end
function FollowPlayerNav:OnBrainInit(ai, global, constants)
end
function OnSetDirection(go, direction)
  if not _G.global.warpRot then
    _G.global.warpRot = game.AI.FindSon():GetAnimDriver("WarpROT")
  end
  _G.global.warpRot.ValueVec = direction
end
function OnSetTargetDirection(go, direction)
  if not _G.global.warpTargetRot then
    _G.global.warpTargetRot = game.AI.FindSon():GetAnimDriver("WarpTargetRot")
  end
  _G.global.warpTargetRot.ValueVec = direction
end
function OnSetPosition(go, position)
  if not _G.global.warpPos then
    _G.global.warpPos = game.AI.FindSon():GetAnimDriver("WarpPOS")
  end
  _G.global.warpPos.ValueVec = position
end
function OnSetWarpPosition(go, position)
  if not _G.global.warpPos then
    _G.global.warpPos = game.AI.FindSon():GetAnimDriver("WarpTargetPOS")
  end
  _G.global.warpPos.ValueVec = position
end
function CallTurnEventsCombatTest(ai, global, constants, position, movename)
  if (ai.WorldPosition - position):Length() > 0.5 then
    local desiredDirectionDelta = DL.FrontAngleFromPoint(ai, position)
    if movename == "MOV_CombatStand" or movename == "MOV_CombatRunStart" then
      if -60 <= desiredDirectionDelta and desiredDirectionDelta <= 60 then
        ai:TriggerMoveEvent("kLERunStartTurn0")
      elseif 60 < desiredDirectionDelta and desiredDirectionDelta <= 180 then
        ai:TriggerMoveEvent("kLERunStartTurnR")
      elseif desiredDirectionDelta < -60 and -180 <= desiredDirectionDelta then
        ai:TriggerMoveEvent("kLERunStartTurnL")
      end
    elseif movename == "MOV_CombatRun" then
      if -90 <= desiredDirectionDelta and desiredDirectionDelta <= -40 then
        ai:TriggerMoveEvent("kLERunPlantTurn90L")
      elseif 40 < desiredDirectionDelta and desiredDirectionDelta <= 90 then
        ai:TriggerMoveEvent("kLERunPlantTurn90R")
      elseif desiredDirectionDelta < -90 and -180 <= desiredDirectionDelta then
        ai:TriggerMoveEvent("kLERunPlantTurn180L")
      elseif desiredDirectionDelta < 180 and 90 < desiredDirectionDelta then
        ai:TriggerMoveEvent("kLERunPlantTurn180R")
      end
    elseif movename == "MOV_CombatRunStop" then
      if -60 <= desiredDirectionDelta and desiredDirectionDelta <= 60 then
        ai:TriggerMoveEvent("kLERunStopTurn0")
      elseif 60 < desiredDirectionDelta and desiredDirectionDelta <= 180 then
        ai:TriggerMoveEvent("kLERunStopTurnR")
      elseif desiredDirectionDelta < -60 and -180 <= desiredDirectionDelta then
        ai:TriggerMoveEvent("kLERunStopTurnL")
      end
    end
  elseif global.combatTarget ~= nil then
    local desiredDirectionDelta = DL.FrontAngleFromPoint(ai, global.combatTarget.WorldPosition)
    if movename == "MOV_CombatStand" then
      if -90 <= desiredDirectionDelta and desiredDirectionDelta <= -40 then
        ai:TriggerMoveEvent("kLEIdleTurn90L")
      elseif 40 < desiredDirectionDelta and desiredDirectionDelta <= 90 then
        ai:TriggerMoveEvent("kLEIdleTurn90R")
      elseif desiredDirectionDelta < -90 and -180 <= desiredDirectionDelta then
        ai:TriggerMoveEvent("kLEIdleTurn180L")
      elseif desiredDirectionDelta < 180 and 90 < desiredDirectionDelta then
        ai:TriggerMoveEvent("kLEIdleTurn180R")
      end
    end
  end
end
function TheaterStayOnPathTimer:OnTimerComplete(timer, ai, global, constants)
  self.timerRunning = false
end
function TheaterStayOnPathTimer:StartNewPath(newPath)
  local timer = self:GetTimer()
  self.timerRunning = true
  self.forcedPath = newPath
  self.thisTimer = timer
  timer:Restart()
end
function TheaterStayOnPathTimer.Events:ResetTheaterPath(timer, ai, global, constants)
  if _G.constants.resetPathDelay <= 10 then
    return
  end
  _G.constants.resetPathDelay = 0
  local tags = statemachine.ActiveTags()
  if _G.constants.awarenessState == _G.constants.SurvivalReposition then
    CombatNav.theaterOfOperationLocations:SetTweak("TheaterOO_SurvivalReposition")
  end
  if not _G.global.selfAI:HasMarker("DoNotChangePath") and not tags.TagDoNotChangePath then
    self.timerRunning = false
    self.forcedPath = nil
    _G.global.nearDestination = false
    timer:Stop()
  end
  _G.constants.delayTheater = true
end
function TheaterResetPath(ai, global, constants)
  if _G.constants.resetTheaterPath == false then
    return
  end
  local tags = statemachine.ActiveTags()
  if _G.constants.awarenessState == _G.constants.SurvivalReposition then
    CombatNav.theaterOfOperationLocations:SetTweak("TheaterOO_SurvivalReposition")
  end
  if not _G.global.selfAI:HasMarker("DoNotChangePath") and not tags.TagDoNotChangePath then
    TheaterStayOnPathTimer.timerRunning = false
    TheaterStayOnPathTimer.forcedPath = nil
    _G.global.nearDestination = false
    if TheaterStayOnPathTimer.thisTimer ~= nil then
      TheaterStayOnPathTimer.thisTimer:Stop()
    end
  end
  _G.constants.delayTheater = true
  _G.constants.resetTheaterPath = false
end
function GetTheaterOfOperationsPath(theaterResult)
  local forcedPath
  if TheaterStayOnPathTimer.timerRunning then
    forcedPath = TheaterStayOnPathTimer.forcedPath
  elseif theaterResult.Path then
    if TheaterStayOnPathTimer.forcedPath then
      local delta = theaterResult.Path:GetLastPoint() - TheaterStayOnPathTimer.forcedPath:GetLastPoint()
      if delta:Length() < 0.5 then
        forcedPath = TheaterStayOnPathTimer.forcedPath
      end
    end
    if not forcedPath then
      forcedPath = theaterResult.Path
      TheaterStayOnPathTimer:StartNewPath(forcedPath)
    end
  end
  return forcedPath
end
function ForceTheaterOperation:Update(ai, global, constants)
  self.ForceDuration = self.ForceDuration - ai:GetFrameTime()
  self.theaterOfOperationLocations:SetTweak(self.ForceTheaterOperationName)
  positioning.SubmitInputs(ai, newPositioningInputs)
  local nextPosition
  local speed = 6
  local strafe = false
  local theaterResult = self.theaterOfOperationLocations:GetResult()
  local forcedPath
  forcedPath = theaterResult.Path
  local nextIndex = forcedPath and forcedPath:GetNextIndexAfterPosition(ai.WorldPosition)
  nextPosition = nextIndex and forcedPath:GetPoint(nextIndex) or theaterResult.Position
  local facing = GetCombatFacing(ai, global, constants)
  if nextPosition ~= nil then
    facing = (nextPosition - ai.WorldPosition):Normalized()
    OnSetDirection(ai, nextPosition - ai.WorldPosition)
    if global.combatTarget ~= nil then
      OnSetTargetDirection(ai, global.combatTarget.WorldPosition - ai.WorldPosition)
    elseif global.rayCastHitLocation ~= nil then
      OnSetTargetDirection(ai, global.rayCastHitLocation - ai.WorldPosition)
    end
    if ai:IsPlayingMove("MOV_CombatStand") then
      CallTurnEventsCombatTest(ai, global, constants, nextPosition, "MOV_CombatStand")
    elseif ai:IsPlayingMove("MOV_CombatRun") then
      CallTurnEventsCombatTest(ai, global, constants, nextPosition, "MOV_CombatRun")
    elseif ai:IsPlayingMove("MOV_CombatRunStop") then
      if global.combatTarget ~= nil then
        facing = (global.combatTarget.WorldPosition - ai.WorldPosition):Normalized()
        CallTurnEventsCombatTest(ai, global, constants, global.combatTarget.WorldPosition, "MOV_CombatRunStop")
      else
        CallTurnEventsCombatTest(ai, global, constants, nextPosition, "MOV_CombatRunStop")
      end
    end
  end
  locomotion.SetActuator(ai, {
    Destination = forcedPath or nextPosition,
    Facing = facing,
    Strafe = strafe,
    Speed = speed
  })
end
function ForceTheaterOperation:Enter(ai, global, constants)
  self.theaterOfOperationLocations = theaterofoperations.TheaterOfOperationsRequester.New(ai, self.ForceTheaterOperationName)
  self.theaterOfOperationLocations:Start()
  self.theaterOfOperationLocations:SetTarget(global.Player)
end
function ForceTheaterOperation:Exit(ai, global, constants)
  self.theaterOfOperationLocations:Stop()
end
function ForceTheaterOperation:OnBrainInit(ai, global, constants)
  self.ForceTheaterOperationName = "TheaterOO_ForceBehindKratos"
  self.ForceDuration = 0
  thunk.Install("OnForceTheater", function(go, duration, theaterName)
    self.ForceDuration = duration
    global.ForceTheaterOperationName = theaterName
  end)
end
function ForceTheaterOperation:IsAvailable(ai, global, constants)
  return self.ForceDuration > 0 and global.bInCombat
end
local LAST_HIGHEST_RANK_DUDE
local LAST_DUDE_TIMER = 0
function CombatNav:Update(ai, global, constants)
  if constants.awarenessState == _G.constants.SurvivalReposition or constants.awarenessState == _G.constants.EvaluateSurvivalReposition or constants.awarenessState == "CautiousReposition" or game.Wallets.GetResourceValue("HERO", "Dummy_DisableAutonomous") > 0 then
    self.theaterOfOperationLocations:SetTarget(global.Player)
  else
    self.theaterOfOperationLocations:SetTarget(ai)
  end
  local sonStayBehind = ai:CheckDecision("tweak_Decision_LevelVariable_SetSonBehind")
  if sonStayBehind == true then
    self.theaterOfOperationLocations:SetTweak("TheaterOO_StayClose")
  elseif constants.awarenessState == _G.constants.SurvivalReposition or constants.awarenessState == _G.constants.EvaluateSurvivalReposition or constants.awarenessState == "CautiousReposition" then
    self.theaterOfOperationLocations:SetTweak("TheaterOO_SurvivalReposition")
  elseif constants.lockDownPursue == true then
    self.theaterOfOperationLocations:SetTweak("TheaterOO_FightingEnemy")
  elseif global.bGoToTarget == true then
    self.theaterOfOperationLocations:SetTweak("TheaterOO_GoToTarget")
  elseif constants.urgentMovement then
    if constants.fightPositionCurrent ~= nil then
      self.theaterOfOperationLocations:SetTweak("TheaterOO_FlankEnemy")
    else
      self.theaterOfOperationLocations:SetTweak("TheaterOO_StayRightSide")
    end
  elseif global.fireAtAllTargets == true then
    self.theaterOfOperationLocations:SetTweak("TheaterOO_StayRightSide")
  elseif global.sTargetMode == "AcquireKratosTarget" then
    self.theaterOfOperationLocations:SetTweak("TheaterOO_StayClose")
  else
    self.theaterOfOperationLocations:SetTweak("TheaterOO_StayRightSide")
  end
  local canFlank = false
  local fightPosResult
  local largeCreature = helper.LargeCreatureExists(ai, global, constants, 40)
  local doFirstTrollBehavior = largeCreature ~= nil
  if doFirstTrollBehavior then
    local cinValue = game.Level.GetVariable("CompletedCineNumber")
    if 50 <= cinValue or cinValue <= 78 then
      doFirstTrollBehavior = ai:HasMarker("FirstTrollBehavior")
    else
      doFirstTrollBehavior = false
    end
  end
  if doFirstTrollBehavior then
    local creaturePosition = largeCreature.WorldPosition
    local creatureForward = largeCreature:GetWorldForward()
    local playerPosition = game.Camera.GetOrbitPosition()
    local playerForward = game.Camera.GetOrbitForward()
    local playerToCreature = (creaturePosition - playerPosition):Normalized()
    local playerToScreenRight = playerToCreature:RotateXZ(90)
    local aiPosition = ai.WorldPosition
    local playerToAI = (aiPosition - playerPosition):Normalized()
    local isRight = 0 < playerToAI:Dot(playerToScreenRight)
    local Zones = {}
    local screenLeft = {
      MinimumAvailableRadius = 1,
      HackForSonResolveToCenter = true,
      Constraints = {
        [1] = {
          Position = creaturePosition,
          Forward = -playerToCreature,
          Constraint = "Distance",
          Min = 4,
          Max = 12
        },
        [2] = {
          Position = creaturePosition,
          Forward = playerToCreature,
          Constraint = "Angle",
          Min = -165,
          Max = -35
        }
      }
    }
    local screenRight = {
      MinimumAvailableRadius = 1,
      HackForSonResolveToCenter = true,
      Constraints = {
        [1] = {
          Position = creaturePosition,
          Forward = -playerToCreature,
          Constraint = "Distance",
          Min = 4,
          Max = 12
        },
        [2] = {
          Position = creaturePosition,
          Forward = playerToCreature,
          Constraint = "Angle",
          Min = 35,
          Max = 165
        }
      }
    }
    Zones = {
      [1] = isRight and screenRight or screenLeft,
      [2] = isRight and screenLeft or screenRight,
      [3] = {
        HackForSonResolveToCenter = false,
        Constraints = {
          [1] = {
            Position = playerPosition,
            Forward = playerForward,
            Constraint = "Distance",
            Min = 5,
            Max = 13
          },
          [2] = {
            Position = playerPosition,
            Forward = playerForward,
            Constraint = "Angle",
            Min = -180,
            Max = 180
          }
        }
      }
    }
    ai:SetFightKnowledgeInputs({
      OccupancyRadius = constants.fightKnowledgeInputs.OccupancyRadius,
      OccupancyPriority = constants.fightKnowledgeInputs.OccupancyPriority,
      GeneratePosition = true,
      Zones = Zones
    })
    fightPosResult = positioning.GetResult(ai)
    if fightPosResult ~= nil then
      if constants.fightPositionCurrent == nil then
        constants.fightPositionCurrent = fightPosResult.Position
      end
      constants.fightPositionNew = fightPosResult.Position
      if gVFSDebug.value then
        engine.DrawFillSphere(constants.fightPositionCurrent + engine.Vector.New(0, 1, 0), 0.25, 16711935)
        engine.DrawFillSphere(constants.fightPositionNew, 0.25, 16777215)
      end
    end
  elseif constants.awarenessState == _G.constants.SurvivalReposition or constants.awarenessState == _G.constants.EvaluateSurvivalReposition then
    positioning.SubmitInputs(ai, newPositioningInputs)
  elseif largeCreature ~= nil then
    local creaturePosition = largeCreature.WorldPosition
    local creatureForward = largeCreature:GetWorldForward()
    local playerPosition = game.Camera.GetOrbitPosition()
    local playerForward = game.Camera.GetOrbitForward()
    local playerToCreature = (creaturePosition - playerPosition):Normalized()
    local playerToScreenRight = playerToCreature:RotateXZ(90)
    local aiPosition = ai.WorldPosition
    local playerToAI = (aiPosition - playerPosition):Normalized()
    local isRight = 0 < playerToAI:Dot(playerToScreenRight)
    local engaged = (creaturePosition - playerPosition):Length() < 14
    local Zones = {}
    if engaged then
      local screenLeft = {
        MinimumAvailableRadius = 2,
        HackForSonResolveToCenter = true,
        Constraints = {
          [1] = {
            Position = creaturePosition,
            Forward = -playerToCreature,
            Constraint = "Distance",
            Min = 5,
            Max = 8
          },
          [2] = {
            Position = creaturePosition,
            Forward = playerToCreature,
            Constraint = "Angle",
            Min = -135,
            Max = -65
          }
        }
      }
      local screenRight = {
        MinimumAvailableRadius = 2,
        HackForSonResolveToCenter = true,
        Constraints = {
          [1] = {
            Position = creaturePosition,
            Forward = -playerToCreature,
            Constraint = "Distance",
            Min = 5,
            Max = 8
          },
          [2] = {
            Position = creaturePosition,
            Forward = playerToCreature,
            Constraint = "Angle",
            Min = 65,
            Max = 135
          }
        }
      }
      Zones = {
        [1] = isRight and screenRight or screenLeft,
        [2] = isRight and screenLeft or screenRight,
        [3] = {
          HackForSonResolveToCenter = false,
          Constraints = {
            [1] = {
              Position = playerPosition,
              Forward = playerForward,
              Constraint = "Distance",
              Min = 5,
              Max = 13
            },
            [2] = {
              Position = playerPosition,
              Forward = playerForward,
              Constraint = "Angle",
              Min = -180,
              Max = 180
            }
          }
        }
      }
    else
      Zones = {
        [1] = {
          HackForSonResolveToCenter = false,
          Constraints = {
            [1] = {
              Position = playerPosition,
              Forward = -playerForward,
              Constraint = "Distance",
              Min = 3,
              Max = 7
            },
            [2] = {
              Position = playerPosition,
              Forward = -playerForward,
              Constraint = "Angle",
              Min = -135,
              Max = 135
            }
          }
        },
        [2] = {
          HackForSonResolveToCenter = false,
          Constraints = {
            [1] = {
              Position = playerPosition,
              Forward = -playerForward,
              Constraint = "Distance",
              Min = 5,
              Max = 12
            },
            [2] = {
              Position = playerPosition,
              Forward = -playerForward,
              Constraint = "Angle",
              Min = -180,
              Max = 180
            }
          }
        }
      }
    end
    ai:SetFightKnowledgeInputs({
      OccupancyRadius = constants.fightKnowledgeInputs.OccupancyRadius,
      OccupancyPriority = constants.fightKnowledgeInputs.OccupancyPriority,
      GeneratePosition = true,
      Zones = Zones
    })
    fightPosResult = positioning.GetResult(ai)
    if fightPosResult ~= nil then
      if constants.fightPositionCurrent == nil then
        constants.fightPositionCurrent = fightPosResult.Position
      end
      constants.fightPositionNew = fightPosResult.Position
      if gVFSDebug.value then
        engine.DrawFillSphere(constants.fightPositionCurrent + engine.Vector.New(0, 1, 0), 0.25, 16711935)
        engine.DrawFillSphere(constants.fightPositionNew, 0.25, 16777215)
      end
    end
  elseif constants.inLockDown then
    local sonTarget = ai:GetTargetCreature()
    local minDist = 2
    local maxDist = 6
    if constants.lockDownRanged == true then
      minDist = 4
      maxDist = 8
    end
    if sonTarget ~= nil then
      local enemyPos = sonTarget.WorldPosition
      local forward = (game.Player.FindPlayer().WorldPosition - sonTarget.WorldPosition):Normalized()
      local Zones = {
        [1] = {
          MinimumAvailableRadius = 1.25,
          Constraints = {
            [1] = {
              Position = enemyPos,
              Forward = forward,
              Constraint = "Distance",
              Min = minDist,
              Max = maxDist
            },
            [2] = {
              Position = enemyPos,
              Forward = forward,
              Constraint = "Angle",
              Min = -60,
              Max = 60
            }
          }
        },
        [2] = {
          MinimumAvailableRadius = 2,
          Constraints = {
            [1] = {
              Position = enemyPos,
              Forward = forward,
              Constraint = "Distance",
              Min = minDist,
              Max = maxDist
            },
            [2] = {
              Position = enemyPos,
              Forward = forward,
              Constraint = "Angle",
              Min = -120,
              Max = 120
            }
          }
        },
        [3] = {
          Constraints = {
            [1] = {
              Position = enemyPos,
              Forward = forward,
              Constraint = "Distance",
              Min = 2,
              Max = 10
            },
            [2] = {
              Position = enemyPos,
              Forward = forward,
              Constraint = "Angle",
              Min = -180,
              Max = 180
            }
          }
        }
      }
      ai:SetFightKnowledgeInputs({
        OccupancyRadius = constants.fightKnowledgeInputs.OccupancyRadius,
        OccupancyPriority = constants.fightKnowledgeInputs.OccupancyPriority,
        GeneratePosition = true,
        Zones = Zones
      })
      fightPosResult = positioning.GetResult(ai)
      if fightPosResult ~= nil then
        if constants.fightPositionCurrent == nil then
          constants.fightPositionCurrent = fightPosResult.Position
        end
        constants.fightPositionNew = fightPosResult.Position
        if gVFSDebug.value then
          engine.DrawFillSphere(constants.fightPositionCurrent + engine.Vector.New(0, 1, 0), 0.25, 16711935)
          engine.DrawFillSphere(constants.fightPositionNew, 0.25, 16777215)
        end
      end
    else
      constants.fightPositionCurrent = nil
      constants.fightPositionNew = nil
      positioning.SubmitInputs(ai, newPositioningInputs)
    end
  elseif constants.urgentMovement and constants.awarenessState ~= _G.constants.SurvivalReposition and constants.awarenessState ~= _G.constants.EvaluateSurvivalReposition and game.Wallets.GetResourceValue("HERO", "Dummy_DisableAutonomous") <= 0 then
    local player = game.Player.FindPlayer()
    local playerPosition = player:GetWorldPosition()
    local playerForward = player:GetWorldForward()
    local highestRankDude = helper.GetHighestRankingChar(ai, global)
    local enemyInFront = false
    local enemyID
    if highestRankDude ~= nil then
      if LAST_HIGHEST_RANK_DUDE == nil then
        LAST_HIGHEST_RANK_DUDE = highestRankDude
      end
      if LAST_HIGHEST_RANK_DUDE ~= highestRankDude and game.AIUtil.IntersectPointCone(LAST_HIGHEST_RANK_DUDE.WorldPosition, player.WorldPosition, global.Player:GetWorldForward(), 90, 6) == false then
        LAST_DUDE_TIMER = LAST_DUDE_TIMER + ai:GetFrameTime()
        if 0.15 < LAST_DUDE_TIMER then
          LAST_DUDE_TIMER = 0
          LAST_HIGHEST_RANK_DUDE = highestRankDude
        else
          highestRankDude = LAST_HIGHEST_RANK_DUDE
        end
      end
    end
    if LAST_HIGHEST_RANK_DUDE ~= nil then
      enemyID = DL.ReturnStringID(LAST_HIGHEST_RANK_DUDE)
      local highRankCreature = ai:GetNearestCreatureWithActionRank(tweaks.eActionRank.kARHigh)
      if highRankCreature == LAST_HIGHEST_RANK_DUDE then
        playerForward = (LAST_HIGHEST_RANK_DUDE:GetWorldPosition() - (game.Camera.GetOrbitPosition() - game.Camera.GetOrbitForward() * 5)):Normalized()
      else
        playerForward = (LAST_HIGHEST_RANK_DUDE:GetWorldPosition() - playerPosition):Normalized()
      end
      if helper.IsMountable(enemyID) then
        if LAST_HIGHEST_RANK_DUDE:PickupIsAcquired("DraugrPowerArmsL") or LAST_HIGHEST_RANK_DUDE:PickupIsAcquired("Fanatic_HelwalkerArcher") then
          canFlank = false
          self.theaterOfOperationLocations:SetTweak("TheaterOO_StayClose")
          self.theaterOfOperationLocations:SetTarget(global.Player)
        else
          canFlank = true
        end
      else
        canFlank = false
        self.theaterOfOperationLocations:SetTweak("TheaterOO_StayClose")
        self.theaterOfOperationLocations:SetTarget(global.Player)
      end
      local minDist = 2.25
      local maxDist = 6.75
      local minAngle = -30
      local maxAngle = 85
      local cameraFoward = game.Camera.GetOrbitForward()
      cameraFoward.y = 0
      enemyInFront = game.AIUtil.IntersectPointCone(LAST_HIGHEST_RANK_DUDE.WorldPosition, player.WorldPosition, cameraFoward, 90, 6)
      local position = LAST_HIGHEST_RANK_DUDE.WorldPosition
      local Zones
      if enemyInFront then
        minDist = 2.25
        maxDist = 5.5
        playerForward = cameraFoward
        position = global.Player.WorldPosition + playerForward * 3
      end
      if constants.aggressiveFlank then
        minDist = 2.25
        maxDist = 5.25
        minAngle = -20
        maxAngle = 30
      end
      if global.Player:HasMarker("LastEnemyCS") then
        playerForward = game.Camera.GetOrbitForward()
        playerForward.y = 0
        minDist = 4
        maxDist = 7
      end
      if canFlank then
        Zones = {
          [1] = {
            MinimumAvailableRadius = 1,
            HackForSonResolveToCenter = true,
            Constraints = {
              [1] = {
                Position = position,
                Forward = playerForward,
                Constraint = "Distance",
                Min = minDist,
                Max = maxDist
              },
              [2] = {
                Position = position,
                Forward = playerForward,
                Constraint = "Angle",
                Min = minAngle,
                Max = maxAngle
              }
            }
          },
          [2] = {
            MinimumAvailableRadius = 1,
            HackForSonResolveToCenter = true,
            Constraints = {
              [1] = {
                Position = position,
                Forward = playerForward,
                Constraint = "Distance",
                Min = 3,
                Max = 6
              },
              [2] = {
                Position = position,
                Forward = playerForward,
                Constraint = "Angle",
                Min = -35,
                Max = 75
              }
            }
          },
          [3] = {
            HackForSonResolveToCenter = false,
            Constraints = {
              [1] = {
                Position = position,
                Forward = playerForward,
                Constraint = "Distance",
                Min = 5,
                Max = 10
              },
              [2] = {
                Position = position,
                Forward = playerForward,
                Constraint = "Angle",
                Min = -180,
                Max = 180
              }
            }
          }
        }
      else
        local creaturePosition = LAST_HIGHEST_RANK_DUDE.WorldPosition
        local playerToCreature = (creaturePosition - playerPosition):Normalized()
        local playerToScreenRight = playerToCreature:RotateXZ(90)
        local aiPosition = ai.WorldPosition
        local playerToAI = (aiPosition - playerPosition):Normalized()
        local isRight = 0 < playerToAI:Dot(playerToScreenRight)
        local screenLeft = {
          MinimumAvailableRadius = 2,
          HackForSonResolveToCenter = true,
          Constraints = {
            [1] = {
              Position = playerPosition,
              Forward = -playerToCreature,
              Constraint = "Distance",
              Min = 4.5,
              Max = 9.5
            },
            [2] = {
              Position = playerPosition,
              Forward = playerToCreature,
              Constraint = "Angle",
              Min = -90,
              Max = -30
            }
          }
        }
        local screenRight = {
          MinimumAvailableRadius = 2,
          HackForSonResolveToCenter = true,
          Constraints = {
            [1] = {
              Position = playerPosition,
              Forward = -playerToCreature,
              Constraint = "Distance",
              Min = 4.5,
              Max = 9.5
            },
            [2] = {
              Position = playerPosition,
              Forward = playerToCreature,
              Constraint = "Angle",
              Min = 30,
              Max = 90
            }
          }
        }
        Zones = {
          [1] = isRight and screenRight or screenLeft,
          [2] = isRight and screenLeft or screenRight,
          [3] = {
            HackForSonResolveToCenter = false,
            Constraints = {
              [1] = {
                Position = playerPosition,
                Forward = playerForward,
                Constraint = "Distance",
                Min = 5,
                Max = 13
              },
              [2] = {
                Position = playerPosition,
                Forward = playerForward,
                Constraint = "Angle",
                Min = -180,
                Max = 180
              }
            }
          }
        }
      end
      ai:SetFightKnowledgeInputs({
        OccupancyRadius = constants.fightKnowledgeInputs.OccupancyRadius,
        OccupancyPriority = constants.fightKnowledgeInputs.OccupancyPriority,
        GeneratePosition = true,
        Zones = Zones
      })
      fightPosResult = positioning.GetResult(ai)
      if fightPosResult ~= nil then
        if constants.fightPositionCurrent == nil then
          constants.fightPositionCurrent = fightPosResult.Position
        end
        constants.fightPositionNew = fightPosResult.Position
        if gVFSDebug.value then
          engine.DrawFillSphere(constants.fightPositionCurrent + engine.Vector.New(0, 1, 0), 0.25, 16711935)
          engine.DrawFillSphere(constants.fightPositionNew, 0.25, 16777215)
        end
      end
    else
      constants.fightPositionCurrent = nil
      constants.fightPositionNew = nil
      positioning.SubmitInputs(ai, newPositioningInputs)
    end
  else
    positioning.SubmitInputs(ai, newPositioningInputs)
  end
  local nextPosition
  local speed = 4
  local strafe = false
  local goalloc
  if global.bGoToTarget == true and global.FollowUpTarget ~= nil then
    goalloc = global.FollowUpTarget.WorldPosition
  elseif fightPosResult ~= nil and constants.fightPositionCurrent ~= nil then
    goalloc = constants.fightPositionCurrent
  end
  local bb = ai:GetPrivateBlackboard()
  if bb ~= nil then
    bb:Set("IsInGoToTarget", global.bGoToTarget)
  end
  if goalloc ~= nil then
    local playerToGoalLoc = (goalloc - global.Player.WorldPosition):Length()
    if playerToGoalLoc < 16 or global.emotionState.value == 3 then
      self.theaterOfOperationLocations:SetGoal(goalloc)
    elseif global.emotionState.value ~= 3 then
      self.theaterOfOperationLocations:SetTarget(global.Player)
      self.theaterOfOperationLocations:SetTweak("TheaterOO_StayClose")
    end
  end
  local theaterResult = self.theaterOfOperationLocations:GetResult()
  global.theaterValues.CurrentValue = theaterResult.CurrentValue
  global.theaterValues.FinalValue = theaterResult.FinalValue
  global.theaterValues.FinalPosition = theaterResult.FinalPosition
  if constants.fightPositionCurrent == nil then
    constants.fightPositionCurrent = global.theaterValues.FinalPosition
    constants.fightPositionNew = global.theaterValues.FinalPosition
  end
  if gVFSDebug.value and theaterResult.FinalPosition ~= nil then
    engine.DrawFillSphere(theaterResult.FinalPosition + engine.Vector.New(0, 2, 0), 0.25, 65535)
  end
  local forcedPath
  if global.bGoToTarget == false and (constants.fightPositionCurrent ~= nil and theaterResult.FinalPosition ~= nil and 3 > (theaterResult.FinalPosition - constants.fightPositionCurrent):Length() and canFlank or canFlank == false) then
    local totalDistance = theaterResult.FinalPosition and theaterResult.FinalPosition:Distance(ai:GetWorldPosition()) or 0
    forcedPath = GetTheaterOfOperationsPath(theaterResult)
    local nextIndex = forcedPath and forcedPath:GetNextIndexAfterPosition(ai.WorldPosition)
    nextPosition = nextIndex and forcedPath:GetPoint(nextIndex) or theaterResult.Position
    if gVFSDebug.value and nextPosition ~= nil then
      engine.DrawFillSphere(nextPosition + engine.Vector.New(0, 2, 0), 0.15, 16711680, 1)
      engine.DrawLine(nextPosition, nextPosition + engine.Vector.New(0, 2, 0), 16711680, 1)
    end
    speed = 4
    strafe = false
  elseif global.bGoToTarget == true then
    local totalDistance = theaterResult.FinalPosition and theaterResult.FinalPosition:Distance(ai:GetWorldPosition()) or 0
    forcedPath = GetTheaterOfOperationsPath(theaterResult)
    local nextIndex = forcedPath and forcedPath:GetNextIndexAfterPosition(ai.WorldPosition)
    nextPosition = nextIndex and forcedPath:GetPoint(nextIndex) or theaterResult.Position
    if gVFSDebug.value and nextPosition ~= nil then
      engine.DrawFillSphere(nextPosition + engine.Vector.New(0, 2, 0), 0.15, 16711680, 1)
      engine.DrawLine(nextPosition, nextPosition + engine.Vector.New(0, 2, 0), 16711680, 1)
    end
    speed = 6
    strafe = false
  end
  local facing = GetCombatFacing(ai, global, constants)
  speed, strafe = HandleFireAllTargets(ai, global, constants, speed, strafe)
  UpdateGlobalSpeed(global, speed)
  if nextPosition ~= nil then
    facing = (nextPosition - ai.WorldPosition):Normalized()
    OnSetDirection(ai, nextPosition - ai.WorldPosition)
    if global.combatTarget ~= nil then
      OnSetTargetDirection(ai, global.combatTarget.WorldPosition - ai.WorldPosition)
    elseif global.rayCastHitLocation ~= nil then
      OnSetTargetDirection(ai, global.rayCastHitLocation - ai.WorldPosition)
    end
  end
  global.nextPosition = nextPosition
  local locomotionInfo = ai:GetLocomotionInfo()
  if locomotionInfo.PathLength <= 1.5 then
    global.nearDestination = true
  else
    global.nearDestination = false
  end
  strafe = constants.strafe
  if global.combatnav_StayPut and global.bGoToTarget == false then
    speed = 0
    global.nearDestination = true
  elseif constants.awarenessState == _G.constants.SurvivalReposition or constants.awarenessState == _G.constants.EvaluateSurvivalReposition then
    constants.strafe = false
    speed = 4.5
  elseif constants.boostSpeed then
    speed = 6
  elseif constants.urgentMovement and not constants.onCamera then
    speed = 12
  elseif constants.urgentMovement then
    speed = 4.6
  end
  if global.bGoToTarget == true then
    strafe = false
  end
  if constants.strafe then
    if not constants.onCamera then
      speed = 12
    else
      speed = 2.5
    end
  end
  if constants.motionType == "UseFollowPoint_GetInFront" then
    if BackpedalTest(ai) then
      return
    end
    local finalPos = global.followPoints.intent_state.pos
    if finalPos == nil then
      finalPos = global.Player:GetWorldPosition()
    end
    local speed = 1.3
    global.followPoints:SetTweak("PointCloudParameters_FrontLead")
    global.followPlayerParams.Speed = speed
    followpointcloud.Follow(ai, global.followPoints, global.followPlayerParams)
  elseif constants.motionType == "Idle" then
    locomotion.SetActuator(ai, {
      Destination = ai.WorldPosition,
      Facing = ai:GetWorldForward(),
      Speed = 0,
      Strafe = strafe
    })
  elseif constants.motionType == "GoToPoint" then
    if constants.moveToPointActuator ~= nil then
      locomotion.SetActuator(ai, {
        Destination = constants.moveToPointActuator.Destination,
        Facing = ai:GetWorldForward(),
        Speed = constants.moveToPointActuator.Speed,
        StopDistance = constants.moveToPointActuator.StopDistance,
        StartDistance = constants.moveToPointActuator.StartDistance
      })
    end
  elseif constants.doNotUsePath and global.theaterValues.FinalPosition ~= nil then
    locomotion.SetActuator(ai, {
      Destination = global.theaterValues.FinalPosition,
      Facing = facing,
      Strafe = true,
      Speed = speed,
      StraightPathAngle = 50
    })
  else
    locomotion.SetActuator(ai, {
      Destination = forcedPath or nextPosition or global.theaterValues.FinalPosition,
      Facing = facing,
      Strafe = constants.strafe,
      Speed = speed,
      StraightPathAngle = 50
    })
  end
end
function CombatNav:Enter(ai, global, constants)
  self.theaterOfOperationLocations:Start()
  self.theaterOfOperationLocations:SetTarget(global.Player)
  statemachine.DispatchGlobalEvent("ResetTheaterPath")
end
function CombatNav:Exit(ai, global, constants)
  self.theaterOfOperationLocations:Stop()
end
function CombatNav:OnBrainInit(ai, global, constants)
  self.theaterOfOperationLocations = theaterofoperations.TheaterOfOperationsRequester.New(ai, "TheaterOO_StayRightSide")
  global.theaterValues = {}
  global.theaterValues.CurrentValue = 0
  global.theaterValues.FinalValue = 0
  constants.strafe = false
  constants.fightPosDataDefault = {}
  constants.fightPosDataDefault.OccupancyRadius = 1.1
  constants.fightPosDataDefault.OccupancyPriority = 1000
  constants.fightPosDataDefault.AggressiveMass = 200
  constants.fightPosDataDefault.NonAggressiveMass = 200
  constants.fightPosDataDefault.AggressiveMinDistance = 5
  constants.fightPosDataDefault.AggressiveMaxDistance = 10
  constants.fightPosDataDefault.NonAggressiveMinDistance = 5
  constants.fightPosDataDefault.NonAggressiveMaxDistance = 10
  constants.fightKnowledgeInputs = positioning.CreateStandardPositioningInputs(constants.fightPosDataDefault)
  constants.fightPositionCurrent = nil
  constants.fightPositionNew = nil
end
function CombatBossNav:Update(ai, global, constants)
  if constants.awarenessState == _G.constants.SurvivalReposition or constants.awarenessState == _G.constants.EvaluateSurvivalReposition or constants.awarenessState == "CautiousReposition" then
    self.theaterOfOperationLocations:SetTarget(ai)
  else
    self.theaterOfOperationLocations:SetTarget(global.Player)
  end
  local fightPosResult
  if constants.focusDown then
    local sonTarget = ai:GetTargetCreature()
    local minDist = 2
    local maxDist = 6
    if constants.lockDownRanged == true then
      minDist = 4
      maxDist = 8
    end
    if sonTarget ~= nil then
      local enemyPos = sonTarget.WorldPosition
      local forward = (game.Player.FindPlayer().WorldPosition - sonTarget.WorldPosition):Normalized()
      local Zones = {
        [1] = {
          MinimumAvailableRadius = 1.25,
          Constraints = {
            [1] = {
              Position = enemyPos,
              Forward = forward,
              Constraint = "Distance",
              Min = minDist,
              Max = maxDist
            },
            [2] = {
              Position = enemyPos,
              Forward = forward,
              Constraint = "Angle",
              Min = -60,
              Max = 60
            }
          }
        },
        [2] = {
          MinimumAvailableRadius = 2,
          Constraints = {
            [1] = {
              Position = enemyPos,
              Forward = forward,
              Constraint = "Distance",
              Min = minDist,
              Max = maxDist
            },
            [2] = {
              Position = enemyPos,
              Forward = forward,
              Constraint = "Angle",
              Min = -120,
              Max = 120
            }
          }
        },
        [3] = {
          Constraints = {
            [1] = {
              Position = enemyPos,
              Forward = forward,
              Constraint = "Distance",
              Min = 2,
              Max = 10
            },
            [2] = {
              Position = enemyPos,
              Forward = forward,
              Constraint = "Angle",
              Min = -180,
              Max = 180
            }
          }
        }
      }
      ai:SetFightKnowledgeInputs({
        OccupancyRadius = constants.fightKnowledgeInputs.OccupancyRadius,
        OccupancyPriority = constants.fightKnowledgeInputs.OccupancyPriority,
        GeneratePosition = true,
        Zones = Zones
      })
      fightPosResult = positioning.GetResult(ai)
      if fightPosResult ~= nil then
        if constants.fightPositionCurrent == nil then
          constants.fightPositionCurrent = fightPosResult.Position
        end
        constants.fightPositionNew = fightPosResult.Position
        if gVFSDebug.value then
          engine.DrawFillSphere(constants.fightPositionCurrent + engine.Vector.New(0, 1, 0), 0.25, 16711935)
          engine.DrawFillSphere(constants.fightPositionNew, 0.25, 16777215)
        end
      end
    else
      constants.fightPositionCurrent = nil
      constants.fightPositionNew = nil
      positioning.SubmitInputs(ai, newPositioningInputs)
    end
  else
    local player = game.Player.FindPlayer()
    local playerPosition = player:GetWorldPosition()
    local playerForward = player:GetWorldForward()
    local highestRankDude = helper.GetHighestRankingChar(ai, global)
    if highestRankDude ~= nil then
      if LAST_HIGHEST_RANK_DUDE == nil then
        LAST_HIGHEST_RANK_DUDE = highestRankDude
      end
      if LAST_HIGHEST_RANK_DUDE ~= highestRankDude and game.AIUtil.IntersectPointCone(LAST_HIGHEST_RANK_DUDE.WorldPosition, player.WorldPosition, global.Player:GetWorldForward(), 90, 6) == false then
        LAST_DUDE_TIMER = LAST_DUDE_TIMER + ai:GetFrameTime()
        if 0.15 < LAST_DUDE_TIMER then
          LAST_DUDE_TIMER = 0
          LAST_HIGHEST_RANK_DUDE = highestRankDude
        else
          highestRankDude = LAST_HIGHEST_RANK_DUDE
        end
      end
    end
    if LAST_HIGHEST_RANK_DUDE ~= nil then
      local highRankCreature = ai:GetNearestCreatureWithActionRank(tweaks.eActionRank.kARHigh)
      if highRankCreature == LAST_HIGHEST_RANK_DUDE then
        playerForward = (LAST_HIGHEST_RANK_DUDE:GetWorldPosition() - (game.Camera.GetOrbitPosition() - game.Camera.GetOrbitForward() * 5)):Normalized()
      else
        playerForward = (LAST_HIGHEST_RANK_DUDE:GetWorldPosition() - playerPosition):Normalized()
      end
    end
    local minDist = 4.25
    local maxDist = 6.75
    local minAngle = -25
    local maxAngle = 75
    if constants.aggressiveFlank then
      minDist = 4.25
      maxDist = 6.75
      minAngle = -15
      maxAngle = 25
    end
    local Zones = {
      [1] = {
        MinimumAvailableRadius = 1,
        HackForSonResolveToCenter = true,
        Constraints = {
          [1] = {
            Position = LAST_HIGHEST_RANK_DUDE.WorldPosition,
            Forward = playerForward,
            Constraint = "Distance",
            Min = minDist,
            Max = maxDist
          },
          [2] = {
            Position = LAST_HIGHEST_RANK_DUDE.WorldPosition,
            Forward = playerForward,
            Constraint = "Angle",
            Min = minAngle,
            Max = maxAngle
          }
        }
      },
      [2] = {
        MinimumAvailableRadius = 1,
        HackForSonResolveToCenter = true,
        Constraints = {
          [1] = {
            Position = LAST_HIGHEST_RANK_DUDE.WorldPosition,
            Forward = playerForward,
            Constraint = "Distance",
            Min = 4,
            Max = 18
          },
          [2] = {
            Position = LAST_HIGHEST_RANK_DUDE.WorldPosition,
            Forward = playerForward,
            Constraint = "Angle",
            Min = -35,
            Max = 75
          }
        }
      },
      [3] = {
        HackForSonResolveToCenter = false,
        Constraints = {
          [1] = {
            Position = LAST_HIGHEST_RANK_DUDE.WorldPosition,
            Forward = playerForward,
            Constraint = "Distance",
            Min = 5,
            Max = 10
          },
          [2] = {
            Position = LAST_HIGHEST_RANK_DUDE.WorldPosition,
            Forward = playerForward,
            Constraint = "Angle",
            Min = -180,
            Max = 180
          }
        }
      }
    }
    ai:SetFightKnowledgeInputs({
      OccupancyRadius = constants.fightKnowledgeInputs.OccupancyRadius,
      OccupancyPriority = constants.fightKnowledgeInputs.OccupancyPriority,
      GeneratePosition = true,
      Zones = Zones
    })
    fightPosResult = positioning.GetResult(ai)
    if fightPosResult ~= nil then
      if constants.fightPositionCurrent == nil then
        constants.fightPositionCurrent = fightPosResult.Position
      end
      constants.fightPositionNew = fightPosResult.Position
      if gVFSDebug.value then
        engine.DrawFillSphere(constants.fightPositionCurrent + engine.Vector.New(0, 1, 0), 0.25, 16711935)
        engine.DrawFillSphere(constants.fightPositionNew, 0.25, 16777215)
      end
    end
  end
  local nextPosition
  local speed = 4
  local strafe = false
  local goalloc
  if global.bGoToTarget == true and global.FollowUpTarget ~= nil then
    goalloc = global.FollowUpTarget.WorldPosition
  elseif fightPosResult ~= nil and constants.fightPositionCurrent ~= nil then
    goalloc = constants.fightPositionCurrent
  end
  local bb = ai:GetPrivateBlackboard()
  if bb ~= nil then
    bb:Set("IsInGoToTarget", global.bGoToTarget)
  end
  if goalloc ~= nil then
    self.theaterOfOperationLocations:SetGoal(goalloc)
  end
  local theaterResult = self.theaterOfOperationLocations:GetResult()
  global.theaterValues.CurrentValue = theaterResult.CurrentValue
  global.theaterValues.FinalValue = theaterResult.FinalValue
  global.theaterValues.FinalPosition = theaterResult.FinalPosition
  if gVFSDebug.value and theaterResult.FinalPosition ~= nil then
    engine.DrawFillSphere(theaterResult.FinalPosition + engine.Vector.New(0, 2, 0), 0.25, 65535)
  end
  local forcedPath
  if global.bGoToTarget == false and constants.fightPositionCurrent ~= nil and theaterResult.FinalPosition ~= nil and 3 > (theaterResult.FinalPosition - constants.fightPositionCurrent):Length() then
    local totalDistance = theaterResult.FinalPosition and theaterResult.FinalPosition:Distance(ai:GetWorldPosition()) or 0
    forcedPath = GetTheaterOfOperationsPath(theaterResult)
    local nextIndex = forcedPath and forcedPath:GetNextIndexAfterPosition(ai.WorldPosition)
    nextPosition = nextIndex and forcedPath:GetPoint(nextIndex) or theaterResult.Position
    if gVFSDebug.value and nextPosition ~= nil then
      engine.DrawFillSphere(nextPosition + engine.Vector.New(0, 2, 0), 0.15, 16711680, 1)
      engine.DrawLine(nextPosition, nextPosition + engine.Vector.New(0, 2, 0), 16711680, 1)
    end
    speed = 4
    strafe = false
  elseif global.bGoToTarget == true then
    local totalDistance = theaterResult.FinalPosition and theaterResult.FinalPosition:Distance(ai:GetWorldPosition()) or 0
    forcedPath = GetTheaterOfOperationsPath(theaterResult)
    local nextIndex = forcedPath and forcedPath:GetNextIndexAfterPosition(ai.WorldPosition)
    nextPosition = nextIndex and forcedPath:GetPoint(nextIndex) or theaterResult.Position
    if gVFSDebug.value and nextPosition ~= nil then
      engine.DrawFillSphere(nextPosition + engine.Vector.New(0, 2, 0), 0.15, 16711680, 1)
      engine.DrawLine(nextPosition, nextPosition + engine.Vector.New(0, 2, 0), 16711680, 1)
    end
    speed = 6
    strafe = false
  end
  local facing = GetCombatFacing(ai, global, constants)
  speed, strafe = HandleFireAllTargets(ai, global, constants, speed, strafe)
  UpdateGlobalSpeed(global, speed)
  if nextPosition ~= nil then
    facing = (nextPosition - ai.WorldPosition):Normalized()
    OnSetDirection(ai, nextPosition - ai.WorldPosition)
    if global.combatTarget ~= nil then
      OnSetTargetDirection(ai, global.combatTarget.WorldPosition - ai.WorldPosition)
    elseif global.rayCastHitLocation ~= nil then
      OnSetTargetDirection(ai, global.rayCastHitLocation - ai.WorldPosition)
    end
  end
  global.nextPosition = nextPosition
  local locomotionInfo = ai:GetLocomotionInfo()
  if locomotionInfo.PathLength <= 1.5 then
    global.nearDestination = true
  else
    global.nearDestination = false
  end
  strafe = constants.strafe
  if global.combatnav_StayPut and global.bGoToTarget == false then
    speed = 0
    global.nearDestination = true
  elseif constants.awarenessState == _G.constants.SurvivalReposition or constants.awarenessState == _G.constants.EvaluateSurvivalReposition then
    constants.strafe = false
    speed = 4.5
  elseif constants.boostSpeed then
    speed = 6
  elseif constants.urgentMovement and not constants.onCamera then
    speed = 12
  elseif constants.urgentMovement then
    speed = 5
  end
  if global.bGoToTarget == true then
    strafe = false
  end
  if constants.strafe then
    if not constants.onCamera then
      speed = 12
    else
      speed = 3
    end
  end
  locomotion.SetActuator(ai, {
    Destination = forcedPath or nextPosition,
    Facing = facing,
    Strafe = strafe,
    Speed = speed,
    StraightPathAngle = 50
  })
end
function CombatBossNav:Enter(ai, global, constants)
  self.theaterOfOperationLocations:Start()
  self.theaterOfOperationLocations:SetTarget(global.Player)
  statemachine.DispatchGlobalEvent("ResetTheaterPath")
end
function CombatBossNav:Exit(ai, global, constants)
  self.theaterOfOperationLocations:Stop()
end
function CombatBossNav:OnBrainInit(ai, global, constants)
  self.theaterOfOperationLocations = theaterofoperations.TheaterOfOperationsRequester.New(ai, "TheaterOO_StayRightSide")
  global.theaterValues = {}
  global.theaterValues.CurrentValue = 0
  global.theaterValues.FinalValue = 0
  constants.strafe = false
  constants.fightPosDataDefault = {}
  constants.fightPosDataDefault.OccupancyRadius = 1.1
  constants.fightPosDataDefault.OccupancyPriority = 1000
  constants.fightPosDataDefault.AggressiveMass = 200
  constants.fightPosDataDefault.NonAggressiveMass = 200
  constants.fightPosDataDefault.AggressiveMinDistance = 5
  constants.fightPosDataDefault.AggressiveMaxDistance = 10
  constants.fightPosDataDefault.NonAggressiveMinDistance = 5
  constants.fightPosDataDefault.NonAggressiveMaxDistance = 10
  constants.fightKnowledgeInputs = positioning.CreateStandardPositioningInputs(constants.fightPosDataDefault)
  constants.fightPositionCurrent = nil
  constants.fightPositionNew = nil
end
function CombatPOINav:Update(ai, global, constants)
  self.theaterOfOperationLocations:SetTweak("TheaterOO_StayRightSide")
  positioning.SubmitInputs(ai, newPositioningInputs)
  local poi = global.POIInfo.useThisPOI
  local curve = ai:GetNavCurve()
  if poi ~= nil then
    local poiInfo = ai:UpdatePOI(poi)
    if poiInfo.Detach then
      if global.POIInfo.useThisPOI ~= nil then
        global.POIInfo.useThisPOI:Disengage(ai)
      end
      global.POIInfo.useThisPOI = nil
      global.POIInfo.state = "done"
    end
    if constants.delayTheater == true then
      constants.delayTheater = false
      return
    end
    local finaldest = ai.WorldPosition
    if poiInfo.Position ~= nil then
      self.theaterOfOperationLocations:SetGoal(poiInfo.Position)
      finaldest = poiInfo.Position
    end
    local theaterResult = self.theaterOfOperationLocations:GetResult()
    global.theaterValues.CurrentValue = theaterResult.CurrentValue
    global.theaterValues.FinalValue = theaterResult.FinalValue
    global.theaterValues.FinalPosition = theaterResult.FinalPosition
    local forcedPath, nextPosition
    local totalDistance = theaterResult.FinalPosition and theaterResult.FinalPosition:Distance(ai:GetWorldPosition()) or 0
    forcedPath = theaterResult.Path
    local nextIndex = forcedPath and forcedPath:GetNextIndexAfterPosition(ai.WorldPosition)
    nextPosition = nextIndex and forcedPath:GetPoint(nextIndex) or theaterResult.Position
    if nextPosition ~= nil and poiInfo.Approach then
      local facing = (nextPosition - ai.WorldPosition):Normalized()
      global.nextPosition = nextPosition
      local locomotionInfo = ai:GetLocomotionInfo()
      if 2 < totalDistance then
        locomotion.SetActuator(ai, {
          Destination = forcedPath or nextPosition,
          Strafe = false,
          Speed = poiInfo.Speed,
          Stop = poiInfo.Stop
        })
      else
        locomotion.SetActuator(ai, {
          Destination = finaldest,
          Strafe = false,
          Speed = poiInfo.Speed,
          Stop = poiInfo.Stop
        })
      end
    else
      locomotion.SetActuator(ai, {
        Destination = ai.WorldPosition,
        Strafe = false,
        Speed = 0,
        Stop = poiInfo.Stop
      })
    end
  end
end
function CombatPOINav:Enter(ai, global, constants)
  self.theaterOfOperationLocations:SetTweak("TheaterOO_StayRightSide")
  self.theaterOfOperationLocations:Start()
  self.theaterOfOperationLocations:SetTarget(ai)
  statemachine.DispatchGlobalEvent("ResetTheaterPath")
  global.offTheaterPath = false
end
function CombatPOINav:Exit(ai, global, constants)
  self.theaterOfOperationLocations:Stop()
  self.bCombatPOINav = false
  global.dispelGoal = nil
end
function CombatPOINav:IsAvailable(ai, global, constants)
  return global.POIInfo.useThisPOI ~= nil and global.bInCombat == true
end
function CombatPOINav:OnBrainInit(ai, global, constants)
  self.theaterOfOperationLocations = theaterofoperations.TheaterOfOperationsRequester.New(ai, "TheaterOO_StayRightSide")
  self.bCombatPOINav = false
  global.theaterValues = {}
  global.theaterValues.CurrentValue = 0
  global.theaterValues.FinalValue = 0
  global.dispelGoal = engine.Vector.New(0, 0, 0)
  global.offTheaterPath = false
end
function LuaHook_SetDispelLocation(ai, location)
  _G.global.dispelGoal = location
end
function LuaHook_DispelNav(ai, dispelnav)
  CombatPOINav.bCombatPOINav = dispelnav
end
function SoloCombatNav:Update(ai, global, constants)
  local motionParams = {}
  if global.combatTarget ~= nil then
    self.followPoints1v1:SetTarget(global.combatTarget)
  else
    self.followPoints1v1:SetTarget(global.Player)
  end
  local followResult
  followResult = self.followPoints1v1:GetResult()
  local finalPosition = followResult and followResult.Position
  local totalDistance = finalPosition and finalPosition:Distance(ai:GetWorldPosition()) or 0
  local speed = 1.9
  local playerSpeed = global.Player:GetLastMovement():Length()
  local isWalkable = true
  if isWalkable then
    if 10 < totalDistance then
      motionParams.ApproachSpeed = 6
    elseif 4 < totalDistance then
      motionParams.ApproachSpeed = 4
    elseif 2 < totalDistance then
      motionParams.ApproachSpeed = 2
    else
      motionParams.ApproachSpeed = playerSpeed
    end
    if 4 < speed then
      motionParams.StopDistance = 1.076
    elseif 2 < speed then
      motionParams.StopDistance = 0.577
    else
      motionParams.StopDistance = 0.484
    end
  else
    motionParams.ApproachSpeed = 0
  end
  motionParams.Strafe = true
  motionParams.Position = finalPosition
  if global.combatTarget then
    motionParams.Facing = (global.combatTarget:GetWorldPosition() - ai:GetWorldPosition()):Normalized()
  end
  CombatMotionParamsSet(ai, global, constants, motionParams)
end
function SoloCombatNav:Enter(ai, global, constants)
  self.followPoints1v1:Start()
end
function SoloCombatNav:Exit(ai, global, constants)
  self.followPoints1v1:Stop()
end
function SoloCombatNav:IsAvailable(ai, global, constants)
  return ai:CheckDecision("tweak_Decision_LevelVariable_SetSonSoloMode") and global.bInCombat
end
function SoloCombatNav:OnBrainInit(ai, global, constants)
  self.followPoints1v1 = followpointcloud.FollowPointRequester.New(ai, "PointCloudParameters_Enemy1vs1")
  if global.combatTarget ~= nil then
    self.followPoints1v1:SetTarget(global.combatTarget)
  else
    self.followPoints1v1:SetTarget(global.Player)
  end
end
function GetCombatFacing(ai, global, constants)
  local facing = ai:GetWorldForward()
  if global.sTargetMode == "SpecialAim" and global.combatTarget == nil then
    if global.rayCastHitLocation ~= nil then
      facing = (global.rayCastHitLocation - ai:GetWorldPosition()):Normalized()
    end
  elseif global.combatTarget ~= nil then
    facing = (global.combatTarget:GetWorldPosition() - ai:GetWorldPosition()):Normalized()
  end
  return facing
end
function HandleFireAllTargets(ai, global, constants, speed, strafe)
  if global.fireAtAllTargets == true then
    strafe = true
    if 4 < speed then
      speed = 4
    end
  end
  return speed, strafe
end
function UpdateGlobalSpeed(global, speed)
  global.navSpeed = speed
  global.approachSpeedLastFrame = speed
end
function CombatMotionParamsSet(ai, global, constants, motionParams)
  motionParams.Facing = GetCombatFacing(ai, global, constants)
  motionParams.Speed, motionParams.Strafe = HandleFireAllTargets(ai, global, constants, motionParams.ApproachSpeed, motionParams.Strafe)
  UpdateGlobalSpeed(global, motionParams.ApproachSpeed)
  ai:SetActuatorMotion(motionParams)
end
function CustomNav:Update(ai, global, constants)
  if global.customIdlePosition ~= nil then
    local motionParams = {}
    motionParams.ApproachSpeed = 1.6
    motionParams.StopDistance = 0.5
    motionParams.Strafe = false
    motionParams.Position = global.customIdlePosition
    ai:SetActuatorMotion(motionParams)
  end
end
function CustomNav:IsAvailable(ai, global, constants)
  local ActiveTags = statemachine.ActiveTags()
  return ActiveTags.CustomNav
end
function CustomNav:Exit(ai, global, constants)
  global.customIdlePosition = nil
end
function CustomNav:OnBrainInit(ai, global, constants)
end
function StandGround:Update(ai, global, constants)
  global.E3_SpecialArrow = true
  local facing = GetCombatFacing(ai, global, constants)
  locomotion.SetActuator(ai, {
    Destination = ai.WorldPosition,
    Facing = facing,
    Strafe = false,
    Speed = 0,
    StopDistance = ai:GetVelocity():Length() > 4.5 and 1 or 0.5,
    StartDistance = 1.5
  })
end
function StandGround:IsAvailable(ai, global, constants)
  return constants.StandGround or constants.awarenessState == _G.constants.Steady or constants.awarenessState == _G.constants.PostPOISteady or global.combatTarget == nil and global.currentState == "InCombat" or global.currentState == "PostCombat"
end
function StandGround:Exit(ai, global, constants)
end
function StandGround:OnBrainInit(ai, global, constants)
  constants.StandGround = false
  global.E3_SpecialArrow = false
end
function ForcedPathNav:IsAvailable(ai, global, constants)
  return constants.motionType == "UseForcedPath"
end
function ForcedPathNav:Enter(ai, global, constants)
  ai:PauseForcedPath(false)
end
function ForcedPathNav:Update(ai, global, constants)
end
function ForcedPathNav:Exit(ai, global, constants)
  ai:PauseForcedPath(true)
end
function MotionBrain:OnBrainInit(ai, global, constants)
  global.navSpeed = 0
  global.heroPreviousPos = nil
  global.followPlayerParams = {
    Speed = 4,
    Strafe = false,
    Facing = nil
  }
  global.leadTheWayParams = {
    Speed = 3,
    LeaderDistance_Wait = 15,
    LeaderDistance_Continue = 10,
    LeaderDistance_Backtrack = 30,
    LeaderDistance_BacktrackDone = 20,
    FollowerDistance_CaughtUp = 5,
    FollowerDistance_Continue = 6,
    Waiting = false,
    Backtracking = false,
    Role = nil,
    TargetCreature = nil,
    TargetCreatureDir = "forward",
    TargetCreatureDirBufferPos = nil,
    TargetCreatureDirBufferRad = 1.5,
    EnableLeashing = true
  }
  global.approachSpeedLastFrame = 0
  global.rotateInPlace = false
end
return MotionBrain
