local LD = require("design.LevelDesignLibrary")
local timer = require("level.timer")
local thisObj, thisLevel, enabled, active, intiallyAllowInteractZones, player, distanceToBase
local propRegistered = false
local failedEffectOn = false
local validDropPosition, subFX, nestedFX, pulseFX, fullyLitMesh
local crystalActive = false
local light, wasAborted
local safeHiddenOver = false
local interactEvents, heroPuppeteer, crystalBases, closestBase, playerJointIndex, playerJointFacing, objectJointLocation, frontInteractZone, horizontalLeftZone, horizontalRightZone, horizontalLeftTriggered, horizontalRightTriggered, startsSocketed, startsHorizontal
local pickedUpHorizontally = false
local attachedToPlayer = false
local socketed = false
local currentState, carryActiveIconTable, carryStaticIconTable
local combatTagsActive = false
local lockedOnceLit
function OnScriptLoaded(level, obj)
  thisObj = obj
  thisLevel = level
  player = game.Player.FindPlayer()
  thisObj:AddMarker("SonTarget")
  thisObj:AddMarkerPointTest("CarryCrystalTop", obj:GetJointIndex("synchJoint"))
  thisObj:HidePhysics()
  intiallyAllowInteractZones = obj:FindLuaTableAttribute("startEnabled")
  startsHorizontal = obj:GetLuaTableAttribute("StartsHorizontal")
  startsSocketed = obj:GetLuaTableAttribute("StartsSocketed")
  lockedOnceLit = obj:FindLuaTableAttribute("lockedOnceLit")
  SoundInit()
  interactEvents = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("Event_OnInteract"))
  frontInteractZone = LD.CreateInteractZone_Carryable_360(thisObj, "InteractZoneJoint")
  frontInteractZone:SetPromptJoint("promptJoint")
  frontInteractZone:SetDebugOffset(1, 0.7, 0)
  frontInteractZone:SetRequiresPlayerGrounded(true)
  frontInteractZone:Disable()
  if frontInteractZone.SetPlayerMaxYDifference then
    frontInteractZone:SetPlayerMaxYDifference(0.1)
  end
  carryActiveIconTable = {
    normal = "WORLD_INTERACT_CARRY",
    unavailable = "WORLD_INTERACT_UNAVAILABLE",
    locked = "WORLD_INTERACT_LOCKED",
    hint = "WORLD_INTERACT_HINT"
  }
  carryStaticIconTable = {
    normal = "WORLD_INTERACT",
    unavailable = "WORLD_INTERACT_UNAVAILABLE",
    locked = "WORLD_INTERACT_LOCKED",
    hint = "WORLD_INTERACT_HINT"
  }
  pulseFX = obj:FindSingleGOByName("bifrost_crystal_temporal_pulse")
  pulseFX:Hide()
  light = obj:FindSingleGOByName("Light")
  fullyLitMesh = obj:FindSingleGOByName("FullyLitMesh")
  subFX = obj:FindSingleGOByName("bifrost_crystal_temporal_active")
  subFX:Hide()
  nestedFX = subFX:FindSingleGOByName("vis_part1")
  game.SubObject.SetUpdateDisableDistance(thisObj, 10)
end
function OnUseWorld(level, obj)
  if frontInteractZone:PlayerCanInteract() then
    player:RequestInteract(thisObj, frontInteractZone)
  end
  if horizontalLeftZone and horizontalLeftZone:PlayerCanInteract() then
    player:RequestInteract(thisObj, horizontalLeftZone)
    horizontalLeftTriggered = true
  end
  if horizontalRightZone and horizontalRightZone:PlayerCanInteract() then
    player:RequestInteract(thisObj, horizontalRightZone)
    horizontalRightTriggered = true
  end
end
function OnFirstStart(level, obj)
  if startsSocketed then
    enabled = true
    socketed = true
    SetCrystalObjectStates(1)
  else
    EnableLos()
    enabled = false
    socketed = false
    SetCrystalObjectStates(0)
  end
  if intiallyAllowInteractZones then
    if startsHorizontal and not pickedUpHorizontally then
      CreateHorizontalZones()
    else
      DisableHorizontalZones()
    end
  end
end
function OnStart()
  if closestBase == nil then
    crystalBases = game.World.FindGameObjectsByMarker("CarryCrystalBase")
    GetAndSetClosestCrystalBase()
  end
  if intiallyAllowInteractZones then
    if startsHorizontal and not pickedUpHorizontally then
      CreateHorizontalZones()
    else
      DisableHorizontalZones()
    end
  end
end
function OnUpdate(level, obj)
  CheckForHitReact()
  if game.Combat.GetCombatStatus and not attachedToPlayer and game.Combat.GetCombatStatus() and not combatTagsActive then
    SetCombatTags()
  end
end
function SetCombatTags()
  combatTagsActive = true
  if frontInteractZone then
    frontInteractZone:SetTags("NotInCombat")
  end
  if horizontalLeftZone then
    horizontalLeftZone:SetTags("NotInCombat")
  end
  if horizontalRightZone then
    horizontalRightZone:SetTags("NotInCombat")
  end
end
function ClearCombatTags()
  combatTagsActive = false
  if frontInteractZone then
    frontInteractZone:ClearTags("NotInCombat")
  end
  if horizontalLeftZone then
    horizontalLeftZone:ClearTags("NotInCombat")
  end
  if horizontalRightZone then
    horizontalRightZone:ClearTags("NotInCombat")
  end
end
function SetCarryDisallowedTags()
  if frontInteractZone then
    frontInteractZone:SetTags("SonNotOnBridge")
  end
  if horizontalLeftZone then
    horizontalLeftZone:SetTags("SonNotOnBridge")
  end
  if horizontalRightZone then
    horizontalRightZone:SetTags("SonNotOnBridge")
  end
  game.Interact.DisableTags("SonNotOnBridge")
end
function ClearCarryDisallowedTags()
  game.Interact.EnableTags("SonNotOnBridge")
  if frontInteractZone then
    frontInteractZone:ClearTags("SonNotOnBridge")
  end
  if horizontalLeftZone then
    horizontalLeftZone:ClearTags("SonNotOnBridge")
  end
  if horizontalRightZone then
    horizontalRightZone:ClearTags("SonNotOnBridge")
  end
end
function CreateHorizontalZones()
  if horizontalLeftZone == nil then
    horizontalLeftZone = LD.CreateInteractZone_Standard_180(thisObj, "InteractZoneJointL")
    horizontalLeftZone:SetOnScreenPercentWeight(0)
    horizontalLeftZone:SetPromptJoint("promptJointHorz")
  end
  if horizontalRightZone == nil then
    horizontalRightZone = LD.CreateInteractZone_Standard_180(thisObj, "InteractZoneJointR")
    horizontalRightZone:SetOnScreenPercentWeight(0)
    horizontalRightZone:SetPromptJoint("promptJointHorz")
  end
  if not socketed then
    frontInteractZone:Disable()
  end
end
function SetActiveIconTable()
  frontInteractZone:SetPromptIconSet(carryActiveIconTable)
end
function SetCarryStaticTable()
  frontInteractZone:SetPromptIconSet(carryStaticIconTable)
end
function DisableHorizontalZones()
  if horizontalLeftZone then
    horizontalLeftZone:Disable()
  end
  if horizontalRightZone then
    horizontalRightZone:Disable()
  end
  if frontInteractZone then
    frontInteractZone:Enable()
  end
end
function ActivatePulse()
  local objPos = thisObj:GetWorldPosition()
  local pulseFX = game.FX.Spawn("bifrost_crystal_temporal_pulse", thisLevel, {AutoDelete = true})
  pulseFX:SetWorldPosition(objPos)
  local concussionParams = {
    Tweak = "CNC_Light_Crystal_Pulse",
    WorldLocation = objPos,
    GameObject = thisObj,
    EnemyId = game.AI.FindSon():GetID()
  }
  game.Combat.PlayConcussion(concussionParams)
end
function PlayFailedEffectAlt()
  local failFX = game.FX.Spawn("bifrost_temporal_fail", thisLevel, {
    GameObject = thisObj,
    Joint = "fxJoint",
    AutoDelete = true
  })
end
function CheckPickUpAnimType()
  if thisObj.RegisterAsProp ~= nil then
    thisObj:RegisterAsProp("CarryBifrostLightCrystal")
    propRegistered = true
  end
  if socketed then
    StartPickupCarryDetach()
    DisableLos()
  elseif not socketed and horizontalLeftTriggered then
    startsHorizontal = false
    pickedUpHorizontally = true
    StartPickupCarryHorizontalLeft()
  elseif not socketed and horizontalRightTriggered then
    startsHorizontal = false
    pickedUpHorizontally = true
    StartPickupCarryHorizontalRight()
  else
    StartPickupCarry()
  end
  crystalBases = game.World.FindGameObjectsByMarker("CarryCrystalBase")
  local closest = FindClosestCrystalBase(crystalBases)
  if closest ~= nil then
    closest:CallScript("PlayerPickedUpNearbyCrystal")
  end
end
function StartCheckPointAttach()
  if thisObj.RegisterAsProp ~= nil then
    thisObj:RegisterAsProp("CarryBifrostLightCrystal")
    propRegistered = true
  end
  local playerBB = player:GetPrivateBlackboard()
  playerBB:Set("CrystalCarry", true)
  frontInteractZone:SetPlayerFrontAngleWeight(0)
  frontInteractZone:SetInteractFrontAngleWeight(100)
  game.Interact.DisableTags("CarryActive")
  heroPuppeteer = game.Puppeteer.NewForce(thisObj, "Carry Instant ", player)
  heroPuppeteer:WeaponEquip({weaponMode = "Bare"})
  heroPuppeteer:StartMove("MOV_CarryStandInstant")
  heroPuppeteer:OnComplete(ClearCheckpointAttach)
  player:CallScript("SetCurrentCarryObject", thisObj)
end
function ClearCheckpointAttach()
  if heroPuppeteer ~= nil then
    heroPuppeteer:Clear()
    heroPuppeteer:DetachPuppet()
    heroPuppeteer = nil
  end
end
function OnPickedUp()
  HideObjectCollision()
  SetActiveIconTable()
  game.Interact.DisableTags("CarryActive")
  player:CallScript("SetCurrentCarryObject", thisObj)
  frontInteractZone:SetOnScreenPercentWeight(0)
  frontInteractZone:SetPlayerFrontAngleWeight(0)
  frontInteractZone:SetInteractFrontAngleWeight(100)
  if closestBase ~= nil then
    closestBase:CallScript("Enable")
  end
end
function OnPutDown()
  game.Interact.EnableTags("CarryActive")
  attachedToPlayer = false
  timer.StartLevelTimer(0.5, EnableLos)
  SetCombatTags()
  player:CallScript("ClearCurrentCarryObject")
  if game.Combat.GetCombatStatus() == false then
    ShowObjectCollision()
  end
  SetCarryStaticTable()
  EnableInteract()
  print("ENABLING INTERACT FROM PUT DOWN")
  PropDisable()
  frontInteractZone:SetPlayerFrontAngleWeight(1)
  frontInteractZone:SetInteractFrontAngleWeight(0)
end
function PropDisable()
  if propRegistered then
    thisObj:PropDisable()
  end
end
function PropEnable()
  if propRegistered then
    thisObj:PropEnable()
  end
end
function DisableInteract()
  if frontInteractZone then
    frontInteractZone:Disable()
  end
  if horizontalLeftZone then
    horizontalLeftZone:Disable()
  end
  if horizontalRightZone then
    horizontalRightZone:Disable()
  end
end
function EnableInteract()
  intiallyAllowInteractZones = true
  if startsHorizontal then
    CreateHorizontalZones()
  elseif frontInteractZone then
    frontInteractZone:Enable()
  end
end
function Lock()
  if frontInteractZone then
    frontInteractZone:Lock()
  end
  if horizontalLeftZone then
    horizontalLeftZone:Lock()
  end
  if horizontalRightZone then
    horizontalRightZone:Lock()
  end
end
function Unlock()
  if frontInteractZone then
    frontInteractZone:Unlock()
  end
  if horizontalLeftZone then
    horizontalLeftZone:Unlock()
  end
  if horizontalRightZone then
    horizontalRightZone:Unlock()
  end
end
function SetCurrentBaseOnCrystal(level, obj, baseObject)
  print("From kratos", level, obj, baseObject)
  if baseObject ~= nil then
    closestBase = baseObject
  end
end
function CheckForHitReact()
  if player:PickupIsActive("CarryObjectClear") and attachedToPlayer then
    CheckHitPosition()
  elseif player:HasMarker("CarryHitReactFront") and attachedToPlayer then
    SnapCarryToGround()
  elseif player:HasMarker("CarryHitReactBack") and attachedToPlayer then
    SnapCarryToGround()
  end
end
function CheckHitPosition()
  local jointIndex = player:GetJointIndex("zeroJoint")
  validDropPosition = player:GetWorldJointPosition(jointIndex)
  print("FORCE DROPPING CARRY!!", validDropPosition)
  player:PickupRelinquish("CarryObjectClear")
end
function SnapCarryToGround()
  thisObj:HideCollision()
  local playerBB = player:GetPrivateBlackboard()
  playerBB:Set("CrystalCarry", false)
  game.Interact.EnableTags("CarryActive")
  thisObj:SetWorldPosition(validDropPosition)
  attachedToPlayer = false
  PropDisable()
  DisableInteract()
  SetCombatTags()
  if not wasAborted then
    LD.CallFunctionAfterDelay(OnPutDown, 3)
  end
  game.SubObject.SetUpdateDisableDistance(thisObj, 10)
  crystalBases = game.World.FindGameObjectsByMarker("CarryCrystalBase")
  local closest = FindClosestCrystalBase(crystalBases)
  if closest ~= nil then
    closest:CallScript("PlayerPutDownNearbyCrystal")
  end
end
function SetSocketed(newState)
  enabled = newState
  socketed = newState
  if intiallyAllowInteractZones or socketed then
    EnableInteract()
  end
  if socketed and frontInteractZone then
    DisableLos()
  else
    EnableLos()
  end
end
function EnableLos()
  if not socketed and frontInteractZone then
    frontInteractZone:SetEnableSphereTest(true)
  end
end
function DisableLos()
  frontInteractZone:SetEnableSphereTest(false)
end
function Enable()
  enabled = true
  if frontInteractZone ~= nil then
    frontInteractZone:Enable()
  end
end
function Disable()
  enabled = false
  if not socketed and frontInteractZone ~= nil then
    frontInteractZone:Disable()
  end
end
function SafeHide()
  thisObj:Hide()
  thisObj:HideCollision()
  DisableInteract()
  if pulseFX then
    pulseFX:Hide()
  end
end
function SafeShow()
  if not safeHiddenOver then
    safeHiddenOver = true
    EnableInteract()
    thisObj:Show()
    thisObj:ShowCollision()
    if fullyLitMesh then
      fullyLitMesh:Hide()
      fullyLitMesh:PauseAnim()
    end
    if light then
      light:Hide()
    end
    if pulseFX then
      pulseFX:Hide()
    end
  end
end
function GetAndSetClosestCrystalBase()
  closestBase = crystalBases[1]
  for i = 1, #crystalBases do
    if game.AIUtil.Distance(thisObj, crystalBases[i]) < game.AIUtil.Distance(thisObj, closestBase) then
      closestBase = crystalBases[i]
    end
  end
  return closestBase
end
function FindClosestCrystalBase(bases)
  local closest = bases[1]
  local myPos = thisObj:GetWorldJointPosition(thisObj:GetJointIndex("zeroJoint"))
  for i = 1, #bases do
    if game.AIUtil.Distance(myPos, bases[i]) < game.AIUtil.Distance(myPos, closest) then
      closest = bases[i]
    end
  end
  return closest
end
function CheckBaseDistance()
  distanceToBase = game.AIUtil.Distance(player, closestBase)
  if distanceToBase <= 3 then
    return true
  end
end
function HideObjectCollision()
  thisObj:HideCollision()
end
function ShowObjectCollision()
  thisObj:ShowCollision()
end
function IsAttachedToPlayer()
  return attachedToPlayer
end
function StartPickupCarry()
  local linkJointIndex = thisObj:GetJointIndex("linkJoint")
  LD.SetJointFacingTowardObject(thisObj, player, linkJointIndex, "")
  local jointIndex = thisObj:GetJointIndex("synchJoint")
  LD.SetJointFacingTowardObject(thisObj, player, jointIndex, "Sync")
end
function Sync()
  LD.PlaySingleSynchMove_KratosObject(thisObj, "synchJoint", "Carry Place Enter", "BRA_CarryPlaceEnter", "", frontInteractZone, false, "Bare", {completion_percentage = 1})
end
function StartPickupCarryHorizontalLeft()
  startsHorizontal = false
  horizontalRightTriggered = false
  horizontalLeftTriggered = false
  LD.PlaySingleSynchMove_KratosObject(thisObj, "sychJointGroundL", "Carry Place Horz Enter", "BRA_CarryPlaceHorzEnterL", "", horizontalLeftZone, false, "Bare", {completion_percentage = 1})
end
function StartPickupCarryHorizontalRight()
  startsHorizontal = false
  horizontalRightTriggered = false
  horizontalLeftTriggered = false
  LD.PlaySingleSynchMove_KratosObject(thisObj, "sychJointGroundR", "Carry Place Horz Enter R", "BRA_CarryPlaceHorzEnterR", "", horizontalRightZone, false, "Bare", {completion_percentage = 1})
end
function LuaHook_TurnOnLoS()
  EnableLos()
end
function LuaHook_CarryStarted()
  ClearCombatTags()
  DisableHorizontalZones()
  OnPickedUp()
  PropEnable()
  attachedToPlayer = true
  game.SubObject.Wake(thisObj)
end
function StartPickupCarryDetach()
  local linkJointIndex = thisObj:GetJointIndex("linkJoint")
  LD.SetJointFacingTowardObject(thisObj, player, linkJointIndex, "")
  local jointIndex = thisObj:GetJointIndex("synchJoint")
  LD.SetJointFacingTowardObject(thisObj, player, jointIndex, "SyncDetach")
end
function SyncDetach()
  LD.PlaySingleSynchMove_KratosObject(thisObj, "synchJoint", "Carry Place Enter", "BRA_CarryPlace_DetachEnter", "", frontInteractZone, false, "Bare", {completion_percentage = 1})
  PropDisable()
  nestedFX:Show()
  nestedFX:JumpAnimToFrame(50)
  nestedFX:PlayAnimToFrame(100)
end
function StartCarryExit()
  local playerBB = player:GetPrivateBlackboard()
  playerBB:Set("CrystalCarry", false)
  crystalBases = game.World.FindGameObjectsByMarker("CarryCrystalBase")
  local closest = FindClosestCrystalBase(crystalBases)
  if closest ~= nil then
    closest:CallScript("PlayerPutDownNearbyCrystal")
  end
  local pos, dir = FindValidDropLocation()
  SetMotionWarpDrivers(pos, dir)
  player:TriggerMoveEvent("DropCarryBlock")
  SetCombatTags()
  EnableInteract()
  timer.StartLevelTimer(0.2, OnPutDown)
end
function FindValidDropLocation()
  local debug_Duration = 10
  local capsuleLength = 1.5
  local capsuleWidth = 0.125
  local yVecOffset_Upper = engine.Vector.New(0, 0.5, 0)
  local hit_F_startPos = player:GetWorldPosition() + yVecOffset_Upper
  local hit_F_endPos = player:GetWorldPosition() + player:GetWorldForward() * capsuleLength + yVecOffset_Upper
  local hit_F = game.World.SpherecastCollision(hit_F_startPos, hit_F_endPos, capsuleWidth, {
    SourceGameObject = player,
    EntityType = game.CollisionType.New("kEnvironment", "kPlayer", "kRagdoll"),
    CollidesWith = game.CollisionType.New("kEnvironment", "kPlayer", "kThrowable")
  }, true)
  local hit_L_startPos = player:GetWorldPosition() + yVecOffset_Upper
  local hit_L_endPos = player:GetWorldPosition() + (player:GetWorldForward() * 2 + player:GetWorldLeft()):Normalized() * capsuleLength + yVecOffset_Upper
  local hit_L = game.World.SpherecastCollision(hit_L_startPos, hit_L_endPos, capsuleWidth, {
    SourceGameObject = player,
    ExcludeGameObject = thisObj,
    EntityType = game.CollisionType.New("kEnvironment", "kPlayer", "kRagdoll"),
    CollidesWith = game.CollisionType.New("kEnvironment", "kPlayer", "kThrowable")
  }, true)
  local hit_R_startPos = player:GetWorldPosition() + yVecOffset_Upper
  local hit_R_endPos = player:GetWorldPosition() + (player:GetWorldForward() * 2 + player:GetWorldLeft() * -1):Normalized() * capsuleLength + yVecOffset_Upper
  local hit_R = game.World.SpherecastCollision(hit_R_startPos, hit_R_endPos, capsuleWidth, {
    SourceGameObject = player,
    ExcludeGameObject = thisObj,
    EntityType = game.CollisionType.New("kEnvironment", "kPlayer", "kRagdoll"),
    CollidesWith = game.CollisionType.New("kEnvironment", "kPlayer", "kThrowable")
  }, true)
  local warpPosition = player:GetWorldPosition()
  local warpRotation = player:GetWorldForward()
  local dropOffset = 1.6
  if not (not hit_F or hit_R or hit_L) or hit_F and hit_R and hit_L then
    warpPosition = LD.GetPointAlongLine(hit_F.Position, hit_F_startPos, dropOffset)
    warpPosition.y = player:GetWorldPosition().y
  elseif not hit_F and hit_L and hit_R then
    dropOffset = 1.3
    warpPosition = LD.GetPointAlongLine(player:GetWorldPosition() + player:GetWorldForward(), player:GetWorldPosition(), dropOffset)
    warpPosition.y = player:GetWorldPosition().y
  elseif hit_L and not hit_R and not hit_F then
    warpRotation = player:GetWorldForward() - player:GetWorldLeft() / 2
  elseif hit_R and not hit_F and not hit_L then
    warpRotation = player:GetWorldForward() + player:GetWorldLeft() / 2
  elseif hit_F and hit_L and not hit_R then
    dropOffset = 1.3
    warpRotation = player:GetWorldForward() - player:GetWorldLeft() / 2
    warpPosition = LD.GetPointAlongLine(hit_F.Position, hit_F_startPos, dropOffset)
    warpPosition.y = player:GetWorldPosition().y
  elseif hit_F and hit_R and not hit_L then
    dropOffset = 1.3
    warpRotation = player:GetWorldForward() + player:GetWorldLeft() / 2
    warpPosition = LD.GetPointAlongLine(hit_F.Position, hit_F_startPos, dropOffset)
    warpPosition.y = player:GetWorldPosition().y
  end
  if engine.IsDebug() then
    engine.DrawSphere(warpPosition, 0.2, 255, debug_Duration)
    engine.DrawLine(warpPosition, warpPosition + warpRotation * 1, 255, debug_Duration)
    engine.DrawLine(hit_F_startPos, hit_F_endPos, 16711680, debug_Duration)
    if hit_F then
      engine.DrawSphere(hit_F.Position, 0.05, 65280, debug_Duration)
    end
    engine.DrawLine(hit_L_startPos, hit_L_endPos, 16711680, debug_Duration)
    if hit_L then
      engine.DrawSphere(hit_L.Position, 0.05, 65280, debug_Duration)
    end
    engine.DrawLine(hit_R_startPos, hit_R_endPos, 16711680, debug_Duration)
    if hit_R then
      engine.DrawSphere(hit_R.Position, 0.05, 65280, debug_Duration)
    end
  end
  return warpPosition, warpRotation
end
local translationAnimDriver, rotationAnimDriver
function SetMotionWarpDrivers(pos, dir)
  translationAnimDriver = player:GetAnimDriver("WarpTranslation")
  translationAnimDriver.ValueVec = pos
  rotationAnimDriver = player:GetAnimDriver("WarpRotation")
  rotationAnimDriver.ValueVec = dir
end
function OnHitByArrow(level, go, arrow_owner, arrow_go, arrow_tweak)
  if arrow_go:GetName() == "bifrost_projectile" and enabled then
    if not active then
      SetCrystalObjectStates(2)
      if closestBase ~= nil then
        closestBase:CallScript("FireFirstTimeLit")
      elseif closestBase ~= nil then
        closestBase:CallScript("FireFirstTimeLit")
      end
      PlaySoundActivateCrystal()
    else
      SetCrystalObjectStates(3)
      PlayLightPulseSound()
      ActivatePulse()
    end
  elseif arrow_go:GetName() == "bifrost_projectile" and not enabled then
    PlayFailedEffectAlt()
    ActivatePulse()
    PlayLightPulseSound()
  end
end
function ToolBoxTurnOn()
  SetCrystalObjectStates(2)
end
function ToolBoxTurnOff()
  SetCrystalObjectStates(0)
end
function SetCrystalObjectStates(data)
  currentState = data
  if data == 0 then
    fullyLitMesh:Hide()
    fullyLitMesh:PauseAnim()
    active = false
    light:Hide()
    FireFullyUnlitCallbacks()
    SetSoundBifrostInactive()
  elseif data == 1 then
    fullyLitMesh:Hide()
    fullyLitMesh:PauseAnim()
    light:Hide()
    SetSoundBifrostInactive()
  elseif data == 2 then
    fullyLitMesh:Show()
    nestedFX:JumpAnimToFrame(0)
    nestedFX:PlayAnimToFrame(20)
    timer.StartLevelTimer(0.2, CrystalFXPeaked)
    nestedFX:Show()
    active = true
    light:Show()
    light:PlayAnimCycle()
    light:SetAnimRate(0.1)
    if lockedOnceLit == true then
      Lock()
    end
    FireFullyLitCallbacks()
    print("CRYSTAL GOT LIT!")
    SetSoundBifrostActive()
  elseif data == 3 then
    fullyLitMesh:Show()
    nestedFX:JumpAnimToFrame(0)
    nestedFX:PlayAnimToFrame(20)
    nestedFX:Show()
  else
    fullyLitMesh:Hide()
    fullyLitMesh:PauseAnim()
    SetSoundBifrostInactive()
  end
end
function FireInteractCallbacks()
  if interactEvents ~= nil then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, interactEvents, "InteractEvents")
  end
end
function FireFullyLitCallbacks()
  if closestBase ~= nil then
    closestBase:CallScript("SetCrystalLit")
  end
end
function FireFullyUnlitCallbacks()
  if closestBase ~= nil then
    closestBase:CallScript("SetCrystalUnlit")
  end
end
function CrystalFXPeaked(_, _, crystalFX)
  if nestedFX ~= nil then
    nestedFX:ClearAllAnimCallbacks()
    nestedFX:PlayAnimToFrame(50)
  end
end
function LuaHook_CarryUnsocketed()
  print("JUST GOT UNSOCKETED!!", closestBase)
end
function LuaHook_CarryUnsocketRotate()
  SetSocketed(false)
  if closestBase ~= nil then
    closestBase:CallScript("CarryNewlyUnsocketed")
  end
  closestBase = nil
end
function LuaHook_ShutOffAllFX()
  SetCrystalObjectStates(0)
end
function OnInteractStart(level, obj, creature)
  wasAborted = false
  if not attachedToPlayer then
    CheckPickUpAnimType()
    player:MarkCurrentWeaponMode()
    FireInteractCallbacks()
    HideObjectCollision()
    player:MarkCurrentWeaponMode()
    local playerBB = player:GetPrivateBlackboard()
    playerBB:Set("CrystalCarry", true)
  elseif (not player.IsSafeToDropCarryBlock or player:IsSafeToDropCarryBlock()) and not player:HasMarker("NoDropZone") then
    StartCarryExit()
  else
    player:EndInteract()
  end
end
function OnInteractAbort(level, obj, creature)
  wasAborted = true
  LD.CallFunctionAfterDelay(OnPutDown, 3)
  if player:PickupIsActive("CarryObjectClear") and attachedToPlayer then
    SnapCarryToGround()
  end
end
function OnInteractFinish(level, obj, creature)
end
function OnInteractDone(level, obj, creature)
end
function OnSaveCheckpoint(level, obj)
  local baseLvlName, baseObjName
  if closestBase ~= nil then
    baseLvlName = closestBase.Level.Name
    baseObjName = closestBase:GetName()
  end
  return {
    enabled = enabled,
    intiallyAllowInteractZones = intiallyAllowInteractZones,
    socketed = socketed,
    safeHiddenOver = safeHiddenOver,
    attachedToPlayer = attachedToPlayer,
    pickedUpHorizontally = pickedUpHorizontally,
    startsHorizontal = startsHorizontal,
    currentState = currentState,
    closestBase = closestBase,
    baseLevelName = baseLvlName,
    baseObjectName = baseObjName,
    worldPosition = obj:GetWorldPosition(),
    worldForward = obj:GetWorldForward()
  }
end
function OnRestoreCheckpoint(level, obj, tab)
  safeHiddenOver = tab.safeHiddenOver
  closestBase = tab.closestBase
  enabled = tab.enabled
  intiallyAllowInteractZones = tab.intiallyAllowInteractZones
  socketed = tab.socketed
  SetSocketed(socketed)
  attachedToPlayer = tab.attachedToPlayer
  pickedUpHorizontally = tab.pickedUpHorizontally
  startsHorizontal = tab.startsHorizontal
  currentState = tab.currentState
  SetCrystalObjectStates(currentState)
  if closestBase == nil and tab.baseLevelName ~= nil and tab.baseObjectName ~= nil then
    FindAndRestoreClosestBaseReference(tab.baseLevelName, tab.baseObjectName)
  end
  if tab.startsHorizontal and tab.pickedUpHorizontally or not tab.startsHorizontal then
    obj:SetWorldPosition(tab.worldPosition)
    obj:SetWorldFacing(tab.worldForward)
  end
  if pickedUpHorizontally then
    DisableHorizontalZones()
  end
  if attachedToPlayer then
    StartCheckPointAttach()
  end
end
function FindAndRestoreClosestBaseReference(baseLevelName, baseObjectName)
  baseLevelName = string.gsub(baseLevelName, "WAD_", "")
  local LM = require("level.loadmonitor")
  local load_monitor = LM.CreateLoadMonitor()
  load_monitor:AddCallback({
    Wads = {baseLevelName},
    Functions = {
      function(wads)
        closestBase = FindClosestCrystalBase(wads[baseLevelName]:FindGameObjects(baseObjectName .. "*"))
        SetCrystalObjectStates(currentState)
      end
    }
  })
end
local soundEmitter, soundEmitterName
local SNDBifrostActiveLP = "SND_MAG_Temporal_Light_Crystal_Active_LP"
local SNDBifrostInactiveLP = "SND_MAG_Temporal_Light_Crystal_Inactive_LP"
local SNDBifrostPulse = "SND_MAG_Temporal_Light_Torches_Active_Hit_Sparks"
local SNDBifrostActivation = "SND_MAG_Temporal_Light_Crystal_Active_LP_Start"
function SoundInit()
  soundEmitterName = thisObj:FindLuaTableAttribute("soundEmitterName")
  if soundEmitterName ~= nil and soundEmitterName ~= "" then
    soundEmitter = thisObj:FindSingleSoundEmitterByName(soundEmitterName)
  else
    soundEmitter = thisObj.SoundEmitters[1]
  end
end
function SetSoundBifrostActive()
  LD.StopRestartableSoundLoop(soundEmitter, SNDBifrostInactiveLP)
  LD.PlayRestartableSoundLoop(soundEmitter, SNDBifrostActiveLP)
end
function SetSoundBifrostInactive()
  LD.StopRestartableSoundLoop(soundEmitter, SNDBifrostActiveLP)
  LD.PlayRestartableSoundLoop(soundEmitter, SNDBifrostInactiveLP)
end
function PlayLightPulseSound()
  LD.PlaySound(soundEmitter, SNDBifrostPulse)
end
function PlaySoundActivateCrystal()
  LD.PlaySound(soundEmitter, SNDBifrostActivation)
end
