local profile = require("core.profile")
local color = require("core.color")
local DEG = 180 / math.pi
local PI2 = math.pi * 2
local PI4 = math.pi * 4
local fmod = math.fmod
local atan2 = math.atan2
local NormalizeAngle = function(a)
  local b = fmod(PI4 + a, PI2)
  return b
end
local GetWorldJointPositionByName = function(ai, jointName)
  local jointIndex = ai:FindJointIndex(jointName)
  if jointIndex ~= nil then
    return ai:GetWorldJointPosition(jointIndex)
  end
  return nil
end
local FrontAngleFromPoint = function(from, location, direction)
  local fwd
  if direction ~= nil then
    fwd = direction
  else
    fwd = from:GetWorldForward()
  end
  local to = (location - from:GetWorldPosition()):Normalized()
  local afwd = atan2(fwd.z, fwd.x)
  local ato = atan2(to.z, to.x)
  local angle = NormalizeAngle(ato - afwd) * DEG
  if 180 < angle then
    angle = angle - 360
  end
  return angle
end
local FrontAngle = function(from, target)
  local angle = FrontAngleFromPoint(from, target.WorldPosition)
  return angle
end
local GetAngleFromVectors = function(vector1, vector2)
  local afwd = atan2(vector1.z, vector1.x)
  local ato = atan2(vector2.z, vector2.x)
  local angle = NormalizeAngle(ato - afwd) * DEG
  if 180 < angle then
    angle = angle - 360
  end
  return angle
end
local GetAngleFromVectorsYZ = function(vector1, vector2)
  local afwd = atan2(vector1.z, vector1.y)
  local ato = atan2(vector2.z, vector2.y)
  local angle = NormalizeAngle(ato - afwd) * DEG
  if 180 < angle then
    angle = angle - 360
  end
  return angle
end
local HashCreatureID = function(base, id)
  if game.Creature.HashCreatureID then
    return game.Creature.HashCreatureID(id)
  end
  return base:LookupConstant(id)
end
local ReturnPositionRelativeToObject = function(angle, rightorleft)
  local selfPositionRelativeToObject
  if angle <= 45 then
    selfPositionRelativeToObject = "objBack"
  elseif 135 <= angle then
    selfPositionRelativeToObject = "objFront"
  elseif rightorleft < 0 then
    selfPositionRelativeToObject = "objRight"
  else
    selfPositionRelativeToObject = "objLeft"
  end
  return selfPositionRelativeToObject
end
local GetObjectARelativePositionToObjectB = function(objecta, objectb)
  local selfPosition = objecta:GetWorldPosition()
  local objPos = objectb:GetWorldPosition()
  local objFacing = objectb:GetWorldForward()
  local relativeposition = objPos - selfPosition
  local normalizedRelativePos = relativeposition:Normalized()
  local rotationrelation = math.acos(normalizedRelativePos:Dot(objFacing)) * 57.296
  local right = objFacing:Cross(engine.Vector.New(0, 1, 0))
  local rightorleft = right:Dot(relativeposition)
  return ReturnPositionRelativeToObject(rotationrelation, rightorleft)
end
local GetObjectARelativeAngleToObjectB = function(objecta, objectb)
  local selfPosition = objecta:GetWorldPosition()
  local objPos = objectb:GetWorldPosition()
  local objFacing = objectb:GetWorldForward()
  local relativeposition = objPos - selfPosition
  local normalizedRelativePos = relativeposition:Normalized()
  local rotationrelation = math.deg(math.acos(normalizedRelativePos:Dot(objFacing)))
  local right = objFacing:Cross(engine.Vector.New(0, 1, 0))
  local rightorleft = right:Dot(relativeposition)
  rotationrelation = 180 - rotationrelation
  if 0 < rightorleft then
    rotationrelation = -rotationrelation
  end
  return rotationrelation
end
local DebugPrint = function(ai, text)
  if ai:DebugIsSelectedCreature() then
    print(tostring(text))
  end
end
local switch = function(c)
  local swtbl = {
    casevar = c,
    caseof = function(self, code)
      local f
      if self.casevar then
        f = code[self.casevar] or code.default
      else
        f = code.missing or code.default
      end
      if f then
        if type(f) == "function" then
          return f(self.casevar, self)
        else
          error("case " .. tostring(self.casevar) .. " not a function")
        end
      end
    end
  }
  return swtbl
end
local Reticle_GetCreature = function()
  local reticleTarget = game.Player.FindPlayer().ReticleTargetCreature
  if reticleTarget ~= nil then
    return reticleTarget, reticleTarget.WorldPosition
  else
    return nil, game.Camera.GetOrbitPosition() + game.Camera.GetOrbitForward() * 50
  end
end
local GetClosestOnScreenEnemy = function()
  local player = game.Player.FindPlayer()
  local creaturesAroundPlayer = player:FindEnemies(30)
  for _, thisCreature in ipairs(creaturesAroundPlayer) do
    if thisCreature:GetAI():CheckDecision("tweak_Decision_OnCamera") and not thisCreature:HasMarker("DoNotTarget") and thisCreature:GetHitPoints() > 0 then
      return thisCreature
    end
  end
  return nil
end
local GetAllEnemiesOnScreen = function(onScreen)
  local player = game.Player.FindPlayer()
  local creaturesOnScreen = {}
  local creaturesAroundPlayer = player:FindEnemies(60)
  for _, thisCreature in ipairs(creaturesAroundPlayer) do
    if thisCreature:GetAI():CheckDecision("tweak_Decision_OnCamera") then
      if onScreen then
        table.insert(creaturesOnScreen, thisCreature)
      end
    elseif not onScreen then
      table.insert(creaturesOnScreen, thisCreature)
    end
  end
  return creaturesOnScreen
end
local PositionVisibleOnCamera = function(position, screenPercentage)
  return screenPercentage < game.Camera.GetViewPenetration(position, 0, 0.16)
end
local TargetObstructed = function(ai, thisEnemy)
  local raycastOrigin = ai.WorldPosition + engine.Vector.New(0, 1, 0)
  local collisionParams = {
    SourceGameObject = ai,
    EntityType = game.CollisionType.New("kAI", "kEnvironment")
  }
  local hit = game.World.RaycastCollision(raycastOrigin, thisEnemy.WorldPosition + engine.Vector.New(0, 1, 0), collisionParams)
  if hit ~= nil and hit.GameObject ~= thisEnemy then
    return true
  else
    return false
  end
end
local LinesIntersect = function(x1, y1, x2, y2, x3, y3, x4, y4)
  local d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
  local Ua_n = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
  local Ub_n = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
  if d == 0 then
    return false
  end
  local Ua = Ua_n / d
  local Ub = Ub_n / d
  if 0 <= Ua and Ua <= 1 and 0 <= Ub and Ub <= 1 then
    return true
  end
  return false
end
local FindClosestLivingEnemy = function(ai, radius)
  local enemies = ai:FindEnemies(radius)
  for i = #enemies, 1, -1 do
    if enemies[i]:GetCreature():GetHitPoints() <= 0 then
      table.remove(enemies, i)
    end
  end
  local thisEnemy
  if enemies ~= nil and 0 < #enemies then
    thisEnemy = enemies[1]
  end
  return thisEnemy
end
local FindLivingEnemies = function(ai, radius, sort_closest)
  local enemies = ai:FindEnemies(radius)
  for i = #enemies, 1, -1 do
    if enemies[i]:GetCreature():GetHitPoints() <= 0 then
      table.remove(enemies, i)
    end
  end
  return enemies
end
local GetAngleBetweenVector = function(vector1, vector2)
  return math.deg(math.atan2(vector1.x * vector2.z - vector1.z * vector2.x, vector1.x * vector2.x + vector1.z * vector2.z))
end
local RotationSpeedAugmentLogic = function(ai, global, motionParams, finalPosition)
  return nil
end
local ReturnIdealSpeed = function(ai, destination)
  local player = game.Player.FindPlayer()
  local playerSpeed = player:GetVelocity():Length()
  local distanceToDestination = game.AIUtil.Distance(ai, destination)
  local playerSpeedCategory
  if playerSpeed <= 0.1 then
    playerSpeedCategory = "Idle"
  elseif playerSpeed < 1.5 then
    playerSpeedCategory = "Walk"
  elseif playerSpeed < 2.5 then
    playerSpeedCategory = "Jog"
  elseif playerSpeed < 4.5 then
    playerSpeedCategory = "Run"
  else
    playerSpeedCategory = "Sprint"
  end
  if distanceToDestination < 0.25 then
    if playerSpeedCategory == "Idle" then
      return 0
    else
      return playerSpeed * 0.85
    end
  elseif distanceToDestination < 8 then
    if playerSpeedCategory == "Idle" then
      return 1.3
    else
      return playerSpeed
    end
  elseif distanceToDestination < 15 then
    if playerSpeedCategory == "Idle" then
      return 2.5
    else
      return playerSpeed * 1.2
    end
  elseif playerSpeedCategory == "Idle" then
    return 4
  else
    return playerSpeed * 1.5
  end
end
local GetChild = function(level, groupName, childName)
  local gameObj = level:FindSingleGameObject(groupName)
  local myChild = gameObj:FindSingleGOByName(childName)
  return myChild
end
local GetChildPosition = function(level, groupName, childName)
  local gameObj = level:FindSingleGameObject(groupName)
  local myChild = gameObj:FindSingleGOByName(childName)
  return myChild:GetPosition(myChild)
end
local GetChildRotation = function(level, groupName, childName)
  local gameObj = level:FindSingleGameObject(groupName)
  local myChild = gameObj:FindSingleGOByName(childName)
  return myChild:GetWorldJointForward(myChild)
end
local GetJointPosition = function(level, groupName, jointName)
  local gameObj = level:FindSingleGameObject(groupName)
  local jIndex = gameObj:GetJointIndex(jointName)
  return gameObj:GetWorldJointPosition(jIndex)
end
local GetJointRotation = function(level, groupName, jointName)
  local gameObj = level:FindSingleGameObject(groupName)
  local jIndex = gameObj:GetJointIndex(jointName)
  return gameObj:GetWorldJointForward(jIndex)
end
local Puppet
local MoveEntityComplete = function(pup)
  pup:Clear()
end
local MoveEntity = function(movingEntity, level, locationsGroup, locationsIndex, travelTime)
  local locations = level:FindSingleGameObject(locationsGroup)
  local jointIndex = locations:GetJointIndex(locationsIndex)
  Puppet = game.Puppeteer.NewForce(locations, "Puppet", movingEntity)
  Puppet:Clear()
  Puppet:Align({
    pos = locations:GetWorldJointPosition(jointIndex),
    facing = locations:GetWorldJointForward(jointIndex),
    aligntime = travelTime
  })
  Puppet:OnComplete(MoveEntityComplete)
end
local g_contextCache = {}
local LookupContext = function(contextName)
  local ctx = g_contextCache[contextName]
  if not ctx then
    ctx = game.LargeInteger.HashString(contextName)
    g_contextCache[contextName] = ctx
  end
  return ctx
end
local CheckCreatureContext = function(contextValue, contextName)
  local ctx = g_contextCache[contextName]
  if ctx then
    return ctx == contextValue
  else
    return contextValue == LookupContext(contextName)
  end
end
local SendAggroEvent = function(player, creature)
  local playerPos = player:GetWorldPosition()
  local creaturePos = creature:GetWorldPosition()
  local creatureFacing = creature:GetWorldForward()
  local relativeposition = creaturePos - playerPos
  local normalizedRelativePos = relativeposition:Normalized()
  local rotationrelation = math.acos(normalizedRelativePos:Dot(creatureFacing)) * 57.296
  local right = creatureFacing:Cross(engine.Vector.New(0, 1, 0))
  local rightorleft = right:Dot(relativeposition)
  if rotationrelation <= 90 then
    if rightorleft < 0 then
      creature:TriggerMoveEvent("kLEAggroBR")
      creature:TriggerMoveEvent("kLEAggroB")
    else
      creature:TriggerMoveEvent("kLEAggroBL")
      creature:TriggerMoveEvent("kLEAggroB")
    end
  else
    creature:TriggerMoveEvent("kLEAggroF")
  end
end
local BroadcastAggro = function(ai, global, constants)
  DebugPrint(ai, "Alerting nearby creatures to aggro Kratos")
  ai:SendBroadcastReaction({
    FilterData = {
      FilterRadius = global.awarenessBroadcastRange or 40
    },
    BroadcastContext = "GRUNT_CRY"
  })
end
local BroadcastAggroSecondary = function(ai, global, constants)
  DebugPrint(ai, "Alerting nearby creatures to aggro Kratos")
  ai:SendBroadcastReaction({
    FilterData = {
      FilterRadius = global.awarenessBroadcastRange / 2 or 15
    },
    BroadcastContext = "GRUNT_CRY"
  })
end
local LuaHook_Evade_CollisionCheckShared = function(ai, data, distanceAmount, checkDiagonal)
  local sphere_radius = 1
  local jointIndex = ai:GetJointIndex("zeroJoint")
  local zeroJointFacingFwd = ai:GetWorldJointForward(jointIndex)
  local zeroJointFacingLeft = ai:GetWorldJointLeft(jointIndex)
  local offset = engine.Vector.New(0, 1.5, 0)
  local offsetZeroJoint = ai.WorldPosition + offset
  local toBack = zeroJointFacingFwd * -distanceAmount
  local offsetBack = offsetZeroJoint + toBack
  offsetBack.y = offsetZeroJoint.y
  local hitBack = game.World.SpherecastCollision(offsetZeroJoint, offsetBack, sphere_radius, {SourceGameObject = ai, FindAnything = true})
  local toLeftBack = zeroJointFacingLeft * distanceAmount + zeroJointFacingFwd * -distanceAmount
  local offsetLeftBack = offsetZeroJoint + toLeftBack
  offsetLeftBack.y = offsetZeroJoint.y
  local hitLeftBack = game.World.SpherecastCollision(offsetZeroJoint, offsetLeftBack, sphere_radius, {SourceGameObject = ai, FindAnything = true})
  local toRight = zeroJointFacingLeft * -distanceAmount
  local offsetRight = offsetZeroJoint + toRight
  offsetRight.y = offsetZeroJoint.y
  local hitRight = game.World.SpherecastCollision(offsetZeroJoint, offsetRight, sphere_radius, {SourceGameObject = ai, FindAnything = true})
  local toRightBack = zeroJointFacingLeft * -distanceAmount + zeroJointFacingFwd * -distanceAmount
  local offsetRightBack = offsetZeroJoint + toRightBack
  offsetRightBack.y = offsetZeroJoint.y
  local hitRightBack = game.World.SpherecastCollision(offsetZeroJoint, offsetRightBack, sphere_radius, {SourceGameObject = ai, FindAnything = true})
  local toLeft = zeroJointFacingLeft * distanceAmount
  local offsetLeft = offsetZeroJoint + toLeft
  offsetLeft.y = offsetZeroJoint.y
  local hitLeft = game.World.SpherecastCollision(offsetZeroJoint, offsetLeft, sphere_radius, {SourceGameObject = ai, FindAnything = true})
  if ai:DebugIsSelectedCreature() then
    engine.DrawLine(offsetZeroJoint, offsetBack, 8388736)
    engine.DrawLine(offsetZeroJoint, offsetLeft, 8388736)
    engine.DrawLine(offsetZeroJoint, offsetRight, 8388736)
  end
  local hitsFailed = {}
  if not hitBack then
    DebugPrint(ai, "EVADE BACK")
    table.insert(hitsFailed, "EvadeBack")
  end
  if checkDiagonal == true then
    if not hitRightBack then
      DebugPrint(ai, "EVADE RIGHT-BACK")
      table.insert(hitsFailed, "EvadeRightBack")
    end
    if not hitLeftBack then
      DebugPrint(ai, "EVADE Left-BACK")
      table.insert(hitsFailed, "EvadeLeftBack")
    end
  end
  if not hitRight then
    DebugPrint(ai, "EVADE RIGHT")
    table.insert(hitsFailed, "EvadeRight")
  end
  if not hitLeft then
    DebugPrint(ai, "EVADE LEFT")
    table.insert(hitsFailed, "EvadeLeft")
  end
  if #hitsFailed == 0 then
    return data:FindOutcomeBranchesEntry("EvadeFailed")
  else
    local rand = math.random(1, #hitsFailed)
    return data:FindOutcomeBranchesEntry(hitsFailed[rand])
  end
end
local damageCounter = 0
local delayRecoveryCounter = 0
local ComboBreakerDecayAbsolute = function(ai, decayAmount)
  damageCounter = damageCounter - decayAmount
  if damageCounter < 0 then
    damageCounter = 0
  end
end
local ComboBreakerDecay = function(ai, decayPerSecond)
  if 9999 <= decayPerSecond then
    damageCounter = 0
    delayRecoveryCounter = 0
  end
  if 0 < delayRecoveryCounter then
    delayRecoveryCounter = delayRecoveryCounter - ai:GetFrameTime() * 1
    if delayRecoveryCounter < 0 then
      delayRecoveryCounter = 0
    end
  elseif 0 < damageCounter then
    ComboBreakerDecayAbsolute(ai, ai:GetFrameTime() * decayPerSecond)
  end
end
local ComboBreakerCheck = function(ai, damage, maxThreshold, resetPercent, breakEvent)
  damageCounter = damageCounter + damage
  if maxThreshold <= damageCounter then
    if breakEvent ~= nil then
      ai:TriggerMoveEvent(breakEvent)
    end
    damageCounter = maxThreshold * resetPercent
    return true
  else
    delayRecoveryCounter = 5
  end
  return false
end
local ComboBreakerGetDebugString = function()
  local debugStr = "Combo Break Counter - " .. string.format("%.0f", damageCounter)
  return debugStr
end
local GetMarkerPosition = function(level, markerName)
  local gameObj = level:FindSingleGameObject(markerName)
  return gameObj.WorldPosition
end
local GetMarkerRotation = function(level, markerName)
  local gameObj = level:FindSingleGameObject(markerName)
  return gameObj:GetWorldForward()
end
local HideLevelObject = function(level, objectName)
  local gameObj = level:FindSingleGameObject(objectName)
  gameObj:Hide()
  gameObj:HideCollision()
end
local ShowLevelObject = function(level, objectName)
  local gameObj = level:FindSingleGameObject(objectName)
  gameObj:Show()
  gameObj:ShowCollision()
end
local SetMotionWarpDrivers = function(level, ai, translationDriver, rotationDriver, markerName)
  local gameObj = level:FindSingleGameObject(markerName)
  if rotationDriver ~= nil then
    local warpRot = ai:GetAnimDriver(rotationDriver)
    if warpRot then
      warpRot.ValueVec = gameObj:GetWorldForward()
    end
  end
  if translationDriver ~= nil then
    local warpLoc = ai:GetAnimDriver(translationDriver)
    if warpLoc then
      warpLoc.ValueVec = gameObj.WorldPosition
    end
  end
end
local GetDistance = function(pt1, pt2)
  return game.AIUtil.Distance(pt1, pt2)
end
local GetDistanceBetweenTwoObjects = function(go1, go2)
  return game.AIUtil.Distance(go1, go2)
end
local WarpPlayer = function(level, markerName)
  game.Player.FindPlayer():Warp(GetMarkerPosition(level, markerName), GetMarkerRotation(level, markerName))
end
local WarpCreature = function(creature, level, markerName)
  creature:Warp(GetMarkerPosition(level, markerName), GetMarkerRotation(level, markerName))
end
local WarpGameObject = function(go, level, markerName)
  go:SetWorldPosition(GetMarkerPosition(level, markerName))
  go:SetWorldFacing(GetMarkerRotation(level, markerName))
end
local DrawTextOnPlayer = function(text)
  engine.DrawTextInWorld(game.Player.FindPlayer():GetWorldPosition() + engine.Vector.New(0, 2, 0), text, 16776960)
end
local PlayObjectAnimation = function(level, objectName)
  local animatingObject = level:FindSingleGameObject(objectName)
  animatingObject:Show()
  animatingObject:JumpAnimToFrame(0)
  animatingObject:PlayAnimToEnd()
end
local ResetObjectAnimation = function(level, objectName)
  local animatingObject = level:FindSingleGameObject(objectName)
  animatingObject:Show()
  animatingObject:JumpAnimToFrame(0)
  animatingObject:PlayAnimToPercent(0)
end
local FinishObjectAnimation = function(level, objectName)
  local animatingObject = level:FindSingleGameObject(objectName)
  animatingObject:Show()
  animatingObject:JumpAnimToFrame(animatingObject.AnimLengthFrames)
  animatingObject:PauseAnim()
end
local PlayObjectAnimationChild = function(level, objectName)
  local animatingObjects = level:FindSingleGameObject(objectName).Children
  for _, child in pairs(animatingObjects) do
    child:Show()
    child:JumpAnimToFrame(0)
    child:PlayAnimToEnd()
  end
end
local PlayObjectAnimationChildFrameRange = function(level, objectName, startFrame, endFrame)
  local animatingObjects = level:FindSingleGameObject(objectName).Children
  for _, child in pairs(animatingObjects) do
    child:Show()
    child:JumpAnimToFrame(startFrame)
    if startFrame < endFrame then
      child:PlayAnimToFrame(endFrame)
    elseif endFrame == -1 then
      child:PlayAnimToEnd()
    else
      child:PlayAnimToFrame(startFrame)
    end
  end
end
local ResetObjectAnimationChild = function(level, objectName)
  local animatingObjects = level:FindSingleGameObject(objectName).Children
  for _, child in pairs(animatingObjects) do
    child:Show()
    child:JumpAnimToFrame(0)
    child:PlayAnimToPercent(0)
  end
end
local FinishObjectAnimationChild = function(level, objectName)
  local animatingObjects = level:FindSingleGameObject(objectName).Children
  for _, child in pairs(animatingObjects) do
    child:Show()
    pcall(function()
      child:JumpAnimToFrame(child.AnimLengthFrames)
    end)
    child:PauseAnim()
  end
end
local ReturnClassType = function(thisClassValue)
  if thisClassValue == 1 then
    return "eFodder"
  elseif thisClassValue == 2 then
    return "dGrunts"
  elseif thisClassValue == 3 then
    return "cMajorGrunts"
  elseif thisClassValue == 4 then
    return "bMiniBosses"
  elseif thisClassValue == 5 then
    return "aBosses"
  else
    return "none"
  end
end
local ReturnClassValue = function(thisClassType)
  if thisClassType == "eFodder" then
    return 1
  elseif thisClassType == "dGrunts" then
    return 2
  elseif thisClassType == "cMajorGrunts" then
    return 3
  elseif thisClassType == "bMiniBosses" then
    return 4
  elseif thisClassType == "aBosses" then
    return 5
  else
    return 6
  end
end
local ReturnFacing = function(obj)
  if obj == nil then
    return 0
  end
  if obj:FindJointIndex("synchjoint") ~= nil then
    return obj:GetWorldJointForward(obj:GetJointIndex("synchjoint"))
  else
    return obj:GetWorldForward()
  end
end
local ReturnPosition = function(obj)
  if obj == nil then
    return 0
  end
  if obj:FindJointIndex("synchjoint") ~= nil then
    return obj:GetWorldJointPosition(obj:GetJointIndex("synchjoint"))
  else
    return obj:GetWorldPosition()
  end
end
local ReturnStringID = function(creature)
  if creature ~= nil then
    return string.upper(creature:GetName())
  end
  return nil
end
local PositionDistanceDebug = function(creature, debugPosStart, debugPosEnd, debugPosSwitch)
  local player = game.Player.FindPlayer()
  local debugPosDistance, debugPosDistanceXZ, debugPosDistanceY
  if creature:DebugIsSelectedCreature() then
    if player.Pad.TriangleDown and debugPosSwitch then
      debugPosSwitch = false
      debugPosStart = debugPosEnd
      if creature.DebugGetMotionDebuggerRecordLocation then
        debugPosEnd = creature:DebugGetMotionDebuggerRecordLocation()
      else
        debugPosEnd = creature:GetWorldPosition()
      end
    end
    if player.Pad.TriangleDown == false and debugPosSwitch == false then
      debugPosSwitch = true
    end
  else
    debugPosStart = nil
    debugPosEnd = nil
  end
  if debugPosStart ~= nil then
    engine.DrawFillSphere(debugPosStart, 0.1, color.fuchsia, 0.5)
  end
  if debugPosEnd ~= nil then
    engine.DrawFillSphere(debugPosEnd, 0.1, color.violet, 0.5)
  end
  if debugPosStart ~= nil and debugPosEnd ~= nil then
    debugPosDistance = debugPosStart - debugPosEnd
    debugPosDistanceXZ = engine.Vector.New(debugPosDistance.x, 0, debugPosDistance.z)
    debugPosDistanceXZ = string.format("%.3f", debugPosDistanceXZ:Length())
    debugPosDistanceY = engine.Vector.New(0, debugPosDistance.y, 0)
    debugPosDistanceY = string.format("%.3f", debugPosDistanceY:Length())
    engine.DrawLine(debugPosStart, debugPosEnd, color.red, 0.5)
  end
  return {
    debugPosStart = debugPosStart,
    debugPosEnd = debugPosEnd,
    debugPosDistanceXZ = debugPosDistanceXZ,
    debugPosDistanceY = debugPosDistanceY,
    debugPosSwitch = debugPosSwitch
  }
end
local CheckRaycastCollision = function(ai, direction, evadeDistance)
  local dirToCheck
  if direction == "Back" then
    dirToCheck = ai:GetWorldForward()
    dirToCheck = dirToCheck * -evadeDistance
  elseif direction == "Left" then
    dirToCheck = ai:GetWorldLeft()
    dirToCheck = dirToCheck * evadeDistance
  elseif direction == "Right" then
    dirToCheck = ai:GetWorldLeft()
    dirToCheck = dirToCheck * -evadeDistance
  end
  local hit = game.World.RaycastCollision(ai:GetWorldPosition() + engine.Vector.New(0, 0.7, 0), ai:GetWorldPosition() + engine.Vector.New(0, 0.7, 0) + dirToCheck, {
    SourceGameObject = ai,
    EntityType = game.CollisionType.New("kEnvironment"),
    CollidesWith = game.CollisionType.New("kInvisibleBarrier", "kEnvironment")
  })
  if hit == nil then
    return false
  end
  return true
end
local CheckSpherecastCollision = function(ai, direction, evadeDistance)
  local aiPosition = ai:GetWorldPosition() + engine.Vector.New(0, 1.5, 0)
  local sphere_radius = 0.4
  local startOffset = ai:GetWorldForward() * sphere_radius
  local endOffset
  if direction == "Back" then
    endOffset = ai:GetWorldForward()
    endOffset = endOffset * -evadeDistance
  elseif direction == "Left" or direction == "LeftBack" or direction == "LeftFront" then
    endOffset = ai:GetWorldLeft()
    endOffset = endOffset * evadeDistance
    startOffset = startOffset + ai:GetWorldLeft() * sphere_radius
  elseif direction == "Right" or direction == "RightBack" or direction == "RightFront" then
    endOffset = ai:GetWorldLeft()
    endOffset = endOffset * -evadeDistance
    startOffset = startOffset + ai:GetWorldLeft() * -sphere_radius
  end
  if direction == "LeftBack" or direction == "RightBack" then
    endOffset = endOffset + ai:GetWorldForward() * -(evadeDistance / 2)
  elseif direction == "LeftFront" or direction == "RightFront" then
    endOffset = endOffset + ai:GetWorldForward() * (evadeDistance / 2)
  end
  endOffset = endOffset + ai:GetWorldForward() * sphere_radius
  local hit = game.World.SpherecastCollision(aiPosition + startOffset, aiPosition + endOffset, sphere_radius, {
    SourceGameObject = ai,
    EntityType = game.CollisionType.New("kEnvironment"),
    CollidesWith = game.CollisionType.New("kInvisibleBarrier", "kEnvironment")
  })
  if hit == nil then
    return false
  end
  return true
end
local NewGamePlusMuspelheimBoostLimit = function(ai, boostLimit)
  if game.GetNewGamePlus() and ai.GroundLevel ~= nil and string.find(ai.GroundLevel.Name, "Msp100_") ~= nil and ai:PickupIsAcquired("NewGameLevelBoost") and ai:PickupGetStage("NewGameLevelBoost") > 0 then
    local pStage = ai:PickupGetStage("NewGameLevelBoost")
    if boostLimit < pStage then
      pStage = boostLimit
    end
    if 0 <= pStage then
      ai:PickupSetStage("NewGameLevelBoost", pStage)
    end
  end
end
local NewGamePlusLevelBoost = function(ai, pickupName)
  if game.GetNewGamePlus() then
    if ai:PickupIsAcquired(pickupName) == false then
      return
    end
    if ai:PickupIsAcquired("PowerLevelBoost") then
      ai:PickupRelinquish("PowerLevelBoost")
    end
    if ai:PickupIsAcquired("GiveMeGodOfWar_DifficultyDefenseOffset") then
      ai:PickupRelinquish("GiveMeGodOfWar_DifficultyDefenseOffset")
    end
    ai:PickupAcquire("NewGameLevelBoost")
    local curPowerLevel = ai:PickupGetStage(pickupName) + 1
    local difficulty = ai:AttributeGetValue("Difficulty")
    local boostArray = {
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0
    }
    if difficulty == 4 then
      boostArray = {
        6,
        5,
        4,
        4,
        3,
        2,
        1,
        0,
        0
      }
    elseif difficulty == 3 then
      boostArray = {
        5,
        4,
        3,
        2,
        2,
        1,
        1,
        0,
        0
      }
    elseif difficulty == 2 then
      boostArray = {
        4,
        3,
        2,
        1,
        1,
        1,
        1,
        0,
        0
      }
    end
    local groundLevel = ai.GroundLevel
    if groundLevel ~= nil and string.find(groundLevel.Name, "Msp100_") ~= nil and string.find(groundLevel.Name, "Msp100_") ~= nil and 2 <= difficulty then
      if difficulty == 4 then
        boostArray = {
          6,
          5,
          4,
          3,
          2,
          1,
          0,
          0,
          0
        }
      else
        boostArray = {
          5,
          4,
          3,
          2,
          1,
          1,
          0,
          0,
          0
        }
      end
    end
    if 9 <= curPowerLevel then
      ai:PickupSetStage("NewGameLevelBoost", boostArray[9])
    elseif 8 <= curPowerLevel then
      ai:PickupSetStage("NewGameLevelBoost", boostArray[8])
    elseif 7 <= curPowerLevel then
      ai:PickupSetStage("NewGameLevelBoost", boostArray[7])
    elseif 6 <= curPowerLevel then
      ai:PickupSetStage("NewGameLevelBoost", boostArray[6])
    elseif 5 <= curPowerLevel then
      ai:PickupSetStage("NewGameLevelBoost", boostArray[5])
    elseif 4 <= curPowerLevel then
      ai:PickupSetStage("NewGameLevelBoost", boostArray[4])
    elseif 3 <= curPowerLevel then
      ai:PickupSetStage("NewGameLevelBoost", boostArray[3])
    elseif 2 <= curPowerLevel then
      ai:PickupSetStage("NewGameLevelBoost", boostArray[2])
    else
      ai:PickupSetStage("NewGameLevelBoost", boostArray[1])
    end
  end
end
local NewGamePlusLevelBoostFlat = function(ai, boostAmount)
  if game.GetNewGamePlus() then
    if ai:PickupIsAcquired("NewGameLevelBoost") == false then
      ai:PickupAcquire("NewGameLevelBoost")
    end
    ai:PickupSetStage("NewGameLevelBoost", boostAmount)
  end
end
return profile.WrapLibrary({
  FrontAngle = FrontAngle,
  FrontAngleFromPoint = FrontAngleFromPoint,
  HashCreatureID = HashCreatureID,
  GetObjectARelativePositionToObjectB = GetObjectARelativePositionToObjectB,
  ReturnPositionRelativeToObject = ReturnPositionRelativeToObject,
  DebugPrint = DebugPrint,
  switch = switch,
  SendAggroEvent = SendAggroEvent,
  BroadcastAggro = BroadcastAggro,
  BroadcastAggroSecondary = BroadcastAggroSecondary,
  Reticle_GetCreature = Reticle_GetCreature,
  GetClosestOnScreenEnemy = GetClosestOnScreenEnemy,
  GetAllEnemiesOnScreen = GetAllEnemiesOnScreen,
  PositionVisibleOnCamera = PositionVisibleOnCamera,
  TargetObstructed = TargetObstructed,
  LinesIntersect = LinesIntersect,
  FindLivingEnemies = FindLivingEnemies,
  FindClosestLivingEnemy = FindClosestLivingEnemy,
  MoveEntity = MoveEntity,
  GetJointPosition = GetJointPosition,
  GetJointRotation = GetJointRotation,
  GetChild = GetChild,
  GetChildPosition = GetChildPosition,
  GetChildRotation = GetChildRotation,
  RotationSpeedAugmentLogic = RotationSpeedAugmentLogic,
  ReturnIdealSpeed = ReturnIdealSpeed,
  GetAngleBetweenVector = GetAngleBetweenVector,
  GetAngleFromVectorsYZ = GetAngleFromVectorsYZ,
  GetObjectARelativeAngleToObjectB = GetObjectARelativeAngleToObjectB,
  CheckCreatureContext = CheckCreatureContext,
  LookupContext = LookupContext,
  GetWorldJointPositionByName = GetWorldJointPositionByName,
  LuaHook_Evade_CollisionCheckShared = LuaHook_Evade_CollisionCheckShared,
  ComboBreakerDecayAbsolute = ComboBreakerDecayAbsolute,
  ComboBreakerDecay = ComboBreakerDecay,
  ComboBreakerCheck = ComboBreakerCheck,
  ComboBreakerGetDebugString = ComboBreakerGetDebugString,
  SetMotionWarpDrivers = SetMotionWarpDrivers,
  GetDistance = GetDistance,
  GetDistanceBetweenTwoObjects = GetDistanceBetweenTwoObjects,
  GetMarkerRotation = GetMarkerRotation,
  GetMarkerPosition = GetMarkerPosition,
  WarpPlayer = WarpPlayer,
  WarpCreature = WarpCreature,
  WarpGameObject = WarpGameObject,
  DrawTextOnPlayer = DrawTextOnPlayer,
  HideLevelObject = HideLevelObject,
  ShowLevelObject = ShowLevelObject,
  PlayObjectAnimation = PlayObjectAnimation,
  ResetObjectAnimation = ResetObjectAnimation,
  FinishObjectAnimation = FinishObjectAnimation,
  PlayObjectAnimationChild = PlayObjectAnimationChild,
  PlayObjectAnimationChildFrameRange = PlayObjectAnimationChildFrameRange,
  ResetObjectAnimationChild = ResetObjectAnimationChild,
  FinishObjectAnimationChild = FinishObjectAnimationChild,
  GetAngleFromVectors = GetAngleFromVectors,
  ReturnClassType = ReturnClassType,
  ReturnClassValue = ReturnClassValue,
  ReturnFacing = ReturnFacing,
  ReturnPosition = ReturnPosition,
  ReturnStringID = ReturnStringID,
  PositionDistanceDebug = PositionDistanceDebug,
  CheckRaycastCollision = CheckRaycastCollision,
  CheckSpherecastCollision = CheckSpherecastCollision,
  NewGamePlusLevelBoost = NewGamePlusLevelBoost,
  NewGamePlusLevelBoostFlat = NewGamePlusLevelBoostFlat,
  NewGamePlusMuspelheimBoostLimit = NewGamePlusMuspelheimBoostLimit
})
