LD = require("design.LevelDesignLibrary")
timers = require("level.timer")
monitors = require("level.MonitorLibrary")
local thisObj, thisLevel, hitCooldown, hitEvents, cooldownEvents, hitCallback, cooldownCallback, hitMonitor, soundState
local SOUND_STATE_IDLE = 0
local temp_UseAlfheimLighting
local enabled = true
local destroyed = false
local onGlobalCooldown = false
local onCooldown = false
local suctionRadiusThrow, suctionRadiusRecall
local hasThrowableTarget = false
local fx_Break = "Tendril_breakFX"
local fx_Retract = "Tendril_RetractFX"
local fx_Idle = "Tendril_IdleFX"
local idleFX
local fx_BrokenIdle = "Tendril_Broken_IdleFX"
local brokenIdleFX_R, brokenIdleFX_L
local fx_Regrow = "Tendril_RegrowFX"
local fx_Reconnect = "Tendril_ReconnectFX"
local fx_Destroyed = "Tendril_DestroyedFX"
local fx_AxeHitBranch = "Tendril_Axe_Hit_BranchFX"
function OnScriptLoaded(level, obj)
  game.SubObject.Sleep(obj)
  thisObj = obj
  thisLevel = level
  hitCooldown = obj:GetLuaTableAttribute("RegrowthCooldown")
  temp_UseAlfheimLighting = obj:GetLuaTableAttribute("temp_UseAlfheimLighting")
  hitEvents = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("Event_OnHit"))
  cooldownEvents = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("Event_CooldownElapsed"))
  suctionRadiusThrow = obj:FindLuaTableAttribute("suctionRadiusThrow") or 0.9
  suctionRadiusRecall = obj:FindLuaTableAttribute("suctionRadiusRecall") or 1.1
  idleFX = obj:FindSingleGOByName("Tendril_IdleFX_Ref")
  local nonTargetCollisionObj = obj:FindSingleGOByName("Collision_NonEmbedCallback")
  nonTargetCollisionObj.LuaObjectScript.RegisterOnHitByWeapon(TendrilHitNonEmbedCallback)
end
function OnFirstStart(level, obj)
  soundState = SOUND_STATE_IDLE
  if temp_UseAlfheimLighting == false then
    thisObj:HideLights()
  end
end
function OnStart(level, obj)
  if not destroyed then
    thisObj:StartAnim("envTendrilClosedIdle")
    local randomStartFrame = math.random(0, thisObj.AnimLengthFrames)
    thisObj:JumpAnimToFrame(randomStartFrame)
    thisObj:PlayAnimCycle(RandomRange(0.25, 0.6))
    SpawnFX(fx_Idle)
    if enabled then
      ResetState()
      SoundInit()
    end
  end
end
function OnHitByWeapon(level, obj, weapon, weaponOwnerCreature)
  if enabled then
    HitTendril()
  end
end
function GetAxePosition()
  local player = game.Player.FindPlayer()
  if player ~= nil then
    local axe = player.Axe
    if axe ~= nil then
      return axe.WorldPosition
    end
  end
end
function Enable()
  enabled = true
end
function Disable()
  enabled = false
end
function GetDestroyed()
  return destroyed
end
function AddAxeTarget_Tendril()
  if hasThrowableTarget == false then
    hasThrowableTarget = true
    thisObj:AddThrowableTarget({
      JointName = "AxeJoint",
      AttachName = "ATT_Axe",
      OutwardRadius = suctionRadiusThrow,
      ReturnRadius = suctionRadiusRecall,
      ClampRadius = 0.7
    })
    thisObj:AddAimAssist({
      Joint = "AxeJoint",
      Angle = 15,
      MaxRadius = 2.5,
      TargetRadius = 1,
      ZoomSnapScreenAngle = 15,
      ZoomSnapRadius = 2.5,
      ZoomSnapTargetRadius = 1,
      AimSphereScreenAngle = 12,
      AimSphereRadius = 1.4,
      MaxDistance = -1
    })
  end
end
function RemoveAxeTarget_Tendril()
  if hasThrowableTarget then
    hasThrowableTarget = false
    thisObj:RemoveThrowableTarget({JointName = "AxeJoint"})
    thisObj:RemoveAimAssist({
      Joint = "AxeJoint",
      Angle = 15,
      MaxRadius = 2.5
    })
  end
end
function SpawnFX(fxName, joints)
  if fxName == fx_BrokenIdle then
    if brokenIdleFX_R == nil then
      brokenIdleFX_R = game.FX.Spawn(fxName, nil, {
        AutoDelete = true,
        GameObject = thisObj,
        Joint = joints[1]
      })
      brokenIdleFX_R:SetWorldPosition(thisObj:GetWorldJointPosition(thisObj:GetJointIndex(joints[1])))
    else
      brokenIdleFX_R:ShowParticleEmitter()
    end
    if brokenIdleFX_L == nil then
      brokenIdleFX_L = game.FX.Spawn(fxName, nil, {
        AutoDelete = true,
        GameObject = thisObj,
        Joint = joints[2]
      })
      brokenIdleFX_L:SetWorldPosition(thisObj:GetWorldJointPosition(thisObj:GetJointIndex(joints[2])))
    else
      brokenIdleFX_L:ShowParticleEmitter()
    end
  elseif fxName == fx_Idle then
    LD.ShowFX(idleFX)
  else
    for _, j in pairs(joints) do
      local newFX = game.FX.Spawn(fxName, nil, {
        AutoDelete = true,
        GameObject = thisObj,
        Joint = j
      })
      newFX:SetWorldPosition(thisObj:GetWorldJointPosition(thisObj:GetJointIndex(j)))
    end
  end
end
function SpawnFX_AtLocation(fxName, location)
  local newFX = game.FX.Spawn(fxName, nil, {AutoDelete = true})
  newFX:SetWorldPosition(location)
end
function StopFX(fxName)
  if fxName == fx_BrokenIdle then
    if brokenIdleFX_R ~= nil then
      brokenIdleFX_R:HideParticleEmitter()
    end
    if brokenIdleFX_L ~= nil then
      brokenIdleFX_L:HideParticleEmitter()
    end
  elseif fxName == fx_Idle then
    LD.HideFX(idleFX)
  end
end
function HitTendril()
  if HitTendril_NoCooldown() == true then
    LD.CallFunctionAfterDelay(RegrowthCooldownElapsed, hitCooldown)
  end
end
function HitStop()
  if hitMonitor == nil then
    hitMonitor = monitors.CreateAnimFrameMonitor(thisObj)
    hitMonitor:OnFrameForward(1, PauseTendrilAnim)
  else
    hitMonitor:Start()
  end
end
function PauseTendrilAnim()
  if hitMonitor ~= nil then
    hitMonitor:Stop()
  end
  thisObj:SetAnimRate(0)
  LD.CallFunctionAfterDelay(function()
    thisObj:SetAnimRate(0.7)
  end, 0.125)
end
function SpawnHitFX()
  SpawnFX(fx_Break, {"AxeJoint"})
  SpawnFX(fx_Retract, {
    "JOLeftTendril2Arm2_9",
    "JORightTendril1Arm2_9"
  })
end
function RandomRange(min, max)
  return math.random(min * 1000, max * 1000) / 1000
end
function StartIdleAnim()
  thisObj:ClearAllAnimCallbacks()
  if onCooldown then
    if not destroyed then
      thisObj:StartAnim("envTendrilOpenIdle")
      SpawnFX(fx_BrokenIdle, {
        "JORightTendril_Base",
        "JOLeftTendril_Base"
      })
      thisObj:PlayAnimCycle(RandomRange(0.25, 0.6))
    end
  else
    soundState = SOUND_STATE_IDLE
    thisObj:StartAnim("envTendrilClosedIdle")
    thisObj:PlayAnimCycle(RandomRange(0.25, 0.6))
  end
end
function StartIdleAnim_ThrowMiss()
  soundState = SOUND_STATE_IDLE
  thisObj:StartAnim("envTendrilClosedIdle")
  thisObj:PlayAnimCycle(RandomRange(0.25, 0.6))
end
function HitTendril_NoCooldown()
  if enabled and onCooldown == false then
    onCooldown = true
    RemoveAxeTarget_Tendril()
    SpawnFX(fx_Break, {"AxeJoint"})
    SpawnFX(fx_Retract, {
      "JOLeftTendril2Arm2_9",
      "JORightTendril1Arm2_9"
    })
    StopFX(fx_Idle)
    thisObj:HideLights()
    thisObj:HideCollision()
    thisObj:FindSingleGOByName("Collision_NonEmbedCallback"):HideCollision()
    thisObj:StartAnim("envTendrilHit")
    thisObj:SetAnimRate(0.5)
    thisObj:OnAnimationDone(thisObj, "StartIdleAnim", {Force = true})
    FireHitCallback(thisObj)
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, hitEvents, "TendrilHit")
    soundState = nil
    AudioTendrilHit()
    HitStop()
    return true
  end
  return false
end
function RegrowthCooldownElapsed()
  if destroyed == false and onGlobalCooldown == false then
    CooldownElapsed()
    AudioTendrilRegrowth()
  end
end
function ForceRegrow()
  if onCooldown then
    RegrowthCooldownElapsed()
  end
end
function CooldownElapsed()
  onCooldown = false
  SpawnFX(fx_Regrow, {
    "JORightTendril_Base",
    "JOLeftTendril_Base"
  })
  SpawnFX(fx_Reconnect, {"AxeJoint"})
  LD.CallFunctionAfterDelay(function()
    if not onCooldown then
      SpawnFX(fx_Idle)
    end
  end, 1.5)
  StopFX(fx_BrokenIdle)
  FireCooldownCallback()
  thisObj:ShowCollision()
  thisObj:FindSingleGOByName("Collision_NonEmbedCallback"):ShowCollision()
  thisObj:StartAnim("envTendrilRegrow")
  thisObj:OnAnimationDone(thisObj, "StartIdleAnim", {Force = true})
  if temp_UseAlfheimLighting then
    thisObj:ShowLights()
  end
  AddAxeTarget_Tendril()
  AudioTendrilConnect()
  LD.ExecuteCallbacksForEvent(thisLevel, thisObj, cooldownEvents, "Tendril Cooldown Elapsed")
end
function ResetState()
  destroyed = false
  onCooldown = false
  thisObj:ClearAllAnimCallbacks()
  AddAxeTarget_Tendril()
  if temp_UseAlfheimLighting then
    thisObj:ShowLights()
  end
  thisObj:ShowCollision()
  thisObj:FindSingleGOByName("Collision_NonEmbedCallback"):ShowCollision()
  if soundState ~= SOUND_STATE_IDLE then
    thisObj:StartAnim("envTendrilClosedIdle")
    thisObj:PlayAnimCycle(RandomRange(0.25, 0.6))
  end
end
function TendrilHitNonEmbedCallback()
  SpawnFX_AtLocation(fx_AxeHitBranch, GetAxePosition())
  thisObj:StartAnim("envTendrilClosedHitMiss")
  thisObj:OnAnimationDone(thisObj, "StartIdleAnim_ThrowMiss", {Force = true})
  AudioTendrilDeflect()
end
function TendrilDestroyed()
  destroyed = true
  StopFX(fx_BrokenIdle)
  StopFX(fx_Idle)
  AudioTendrilCollapse()
  RemoveAxeTarget_Tendril()
  if hitMonitor ~= nil then
    hitMonitor:Terminate()
  end
end
function AllTendrilsHit(globalCooldown)
  onGlobalCooldown = true
  SpawnFX(fx_Destroyed, {
    "JORightTendril_Base",
    "JOLeftTendril_Base"
  })
  StopFX(fx_BrokenIdle)
  LD.CallFunctionAfterDelay(function()
    thisObj:SetAnimRate(1)
    thisObj:StartAnim("envTendrilDeath", 1)
    thisObj:PlayAnimToEnd()
    LD.CallFunctionAfterDelay(function()
      SpawnFX(fx_Break, {
        "JORightTendril_Base",
        "JOLeftTendril_Base"
      })
      thisObj:Hide()
    end, 0.5)
  end, 0.5)
  if destroyed then
    thisObj:HideLights()
  end
end
function GlobalRegrowthCooldownElapsed()
  onGlobalCooldown = false
  if destroyed == false then
    CooldownElapsed()
  end
end
function RegisterHitCallback(fn)
  if hitCallback == nil then
    hitCallback = fn
  end
end
function FireHitCallback(obj)
  if hitCallback ~= nil then
    hitCallback(obj)
  end
end
function RegisterCooldownCallback(fn)
  if cooldownCallback == nil then
    cooldownCallback = fn
  end
end
function FireCooldownCallback()
  if cooldownCallback ~= nil then
    cooldownCallback()
  end
end
function OnSaveCheckpoint(level, obj)
  return {
    enabled = enabled,
    destroyed = destroyed,
    soundState = soundState
  }
end
function OnRestoreCheckpoint(level, obj, savedInfo)
  enabled = savedInfo.enabled
  destroyed = savedInfo.destroyed
  soundState = savedInfo.soundState
end
local soundEmitter
local soundEvents = {
  isLooping = false,
  OnOpen = "SND_BRK_Hive_Tendril_Break_Open",
  OnClose = "SND_BRK_Hive_Tendril_Break_Close",
  OnThud = "SND_BRK_Hive_Tendril_Impact_Thud",
  OnCollapse = "SND_BRK_Hive_Tendril_Wall_Collapse",
  IdleLoop = "SND_BRK_Hive_Tendril_Slime_LP",
  OnDeflect = "SND_BRK_Hive_Tendril_Impact_Thud"
}
function SoundInit()
  soundEmitter = thisObj:FindSingleSoundEmitterByName("SNDTendrilAxeTarget")
  PlayIdleSound()
end
function SoundSetup(sounds)
  if sounds ~= nil then
    if sounds.SoundEmitter ~= nil then
      soundEmitter = thisObj:FindSingleSoundEmitterByName(sounds.SoundEmitter)
    end
    for key in pairs(soundEvents) do
      for newKey, newValue in pairs(sounds) do
        if newKey == key and newValue ~= nil and newValue ~= "" then
          soundEvents[key] = newValue
        end
      end
      LD.SoundDebug(tostring(key) .. ": " .. tostring(soundEvents[key]))
    end
  end
end
function AudioTendrilRegrowth()
  LD.PlaySoundRingOut(soundEmitter, soundEvents.OnClose)
end
function AudioTendrilHit()
  LD.PlaySoundRingOut(soundEmitter, soundEvents.OnOpen)
  StopIdleSound()
end
function AudioTendrilConnect()
  LD.PlaySoundRingOut(soundEmitter, soundEvents.OnThud)
  PlayIdleSound()
end
function AudioTendrilCollapse()
  LD.PlaySoundRingOut(soundEmitter, soundEvents.OnCollapse)
end
function PlayIdleSound()
  LD.PlayRestartableSoundLoop(soundEmitter, soundEvents.IdleLoop)
end
function StopIdleSound()
  LD.StopRestartableSoundLoop(soundEmitter, soundEvents.IdleLoop)
end
function AudioTendrilDeflect()
  LD.PlaySound(soundEmitter, soundEvents.OnDeflect)
end
