local LD = require("design.LevelDesignLibrary")
local thisObj, thisLevel, player, son, elevatorObject, elevatorState
local bIsUsingRealmTravelShakeTiming = false
local switch_Obj, switchTop_Obj, switchBottom_Obj, startFrame, topFloorFrame, bottomFloorFrame
local moveUpSpeed = 1
local moveDownSpeed = 1
local callUpSpeed = 1
local callDownSpeed = 1
local enabled = true
local playerActivatedElevator = false
local triggerCinematic = false
local checkpointOnFloor = false
local ignoreNextCheckpointOnFloor = false
local invisSheet, cameraShakeType, rumbleType, son_MoveToPoint
local sonTable = {}
local waitingOnSonCallback, onWaitComplete
local enableSonWarp = true
local onMoveDown, onMoveUp, onReachTop, onReachBottom, fireOnMoveDown, fireOnMoveUp, fireOnReachTop, fireOnReachBottom, checkpointOverrideTopFloor, checkpointOverrideBottomFloor
local debuglog = ""
function OnScriptLoaded(level, obj)
  thisLevel = level
  thisObj = obj
  player = game.Player.FindPlayer()
  son = game.AI.FindSon()
  elevatorObject = obj.Parent.Parent
  checkpointOnFloor = obj:FindLuaTableAttribute("CheckpointGame")
  topFloorFrame = ParseAttribute(obj, "TopFloorFrame")
  bottomFloorFrame = ParseAttribute(obj, "BottomFloorFrame")
  startFrame = ParseAttribute(obj, "StartFrame")
  enabled = obj:FindLuaTableAttribute("StartEnabled")
  cameraShakeType = obj:FindLuaTableAttribute("CameraShake")
  sonTable.moveUp = obj:FindLuaTableAttribute("SonIdles_MovingUp") or false
  sonTable.moveDown = obj:FindLuaTableAttribute("SonIdles_MovingDown") or false
  sonTable.useCollisionCheck = false
  sonTable.availableForCombat = true
  sonTable.overrideCA_Facing = false
  switch_Obj = elevatorObject:FindSingleGOByName(obj:FindLuaTableAttribute("SwitchName"))
  local topCallSwitchName = obj:FindLuaTableAttribute("CallSwitch_Top_Name")
  if topCallSwitchName ~= nil and topCallSwitchName ~= "" then
    SetTopFloorCallSwitch(elevatorObject:FindSingleGOByName(topCallSwitchName, true))
  end
  local bottomCallSwitchName = obj:FindLuaTableAttribute("CallSwitch_Bottom_Name")
  if bottomCallSwitchName ~= nil and bottomCallSwitchName ~= "" then
    SetBottomFloorCallSwitch(elevatorObject:FindSingleGOByName(bottomCallSwitchName, true))
  end
  onMoveDown = obj:FindLuaTableAttribute("OnMoveDown")
  onMoveUp = obj:FindLuaTableAttribute("OnMoveUp")
  onReachBottom = obj:FindLuaTableAttribute("OnReachBottomFloor")
  onReachTop = obj:FindLuaTableAttribute("OnReachTopFloor")
  onMoveDown = LD.ExtractCallbacksForEvent(level, obj, onMoveDown)
  onMoveUp = LD.ExtractCallbacksForEvent(level, obj, onMoveUp)
  onReachBottom = LD.ExtractCallbacksForEvent(level, obj, onReachBottom)
  onReachTop = LD.ExtractCallbacksForEvent(level, obj, onReachTop)
  checkpointOverrideTopFloor = thisObj:FindLuaTableAttribute("TopFloorCheckpointOverride")
  if checkpointOverrideTopFloor ~= nil and checkpointOverrideTopFloor ~= "" then
    checkpointOverrideTopFloor = GameObjects[checkpointOverrideTopFloor]
  end
  checkpointOverrideBottomFloor = thisObj:FindLuaTableAttribute("BottomFloorCheckpointOverride")
  if checkpointOverrideBottomFloor ~= nil and checkpointOverrideBottomFloor ~= "" then
    checkpointOverrideBottomFloor = GameObjects[checkpointOverrideBottomFloor]
  end
  invisSheet = elevatorObject:FindSingleGOByName(obj:FindLuaTableAttribute("ElevatorObject_InvisSheet"))
  if cameraShakeType == "Large" then
    cameraShakeType = "FSE_SHAKE_GENERIC_LARGE"
    rumbleType = "FFB_LARGE"
  elseif cameraShakeType == "Medium" then
    cameraShakeType = "FSE_SHAKE_GENERIC_MEDIUM"
    rumbleType = "FFB_MEDIUM"
  elseif cameraShakeType == "Small" then
    cameraShakeType = "FSE_SHAKE_GENERIC_SMALL"
    rumbleType = "FFB_SMALL"
  end
  son_MoveToPoint = thisObj:FindSingleGOByName("MoveToPoint_Son")
  elevatorState = nil
  game.SubObject.Sleep(obj)
end
function OnFirstPreStart(level, obj)
  if startFrame ~= nil then
    elevatorObject:JumpAnimationToFrame(startFrame)
    elevatorObject:PauseAnimation()
  end
  if elevatorObject.AnimFrame == topFloorFrame then
    elevatorState = "Top"
  elseif elevatorObject.AnimFrame == bottomFloorFrame then
    elevatorState = "Bottom"
  else
    elevatorState = "InBetween"
  end
end
function OnPreStart(level, obj)
  if switch_Obj ~= nil then
    local interactZones = switch_Obj.LuaObjectScript.GetInteractZoneTable()
    for i = 1, #interactZones do
      interactZones[i]:SetXZRange(2)
    end
    switch_Obj.LuaObjectScript.AddSwitchOnOffCallback(WaitForSonWhileOnScreen)
    switch_Obj.LuaObjectScript.AddSwitchedCompleteCallback(PlayerMoveElevator)
  end
  if switchTop_Obj ~= nil and switchTop_Obj ~= "" then
    SetTopFloorCallSwitch(switchTop_Obj)
  else
    switchTop_Obj = nil
  end
  if switchBottom_Obj ~= nil and switchBottom_Obj ~= "" then
    SetBottomFloorCallSwitch(switchBottom_Obj)
  else
    switchBottom_Obj = nil
  end
  if topFloorFrame < bottomFloorFrame then
    moveUpSpeed = -1 * math.abs(obj:FindLuaTableAttribute("AnimSpeedMoveUp"))
    moveDownSpeed = 1 * math.abs(obj:FindLuaTableAttribute("AnimSpeedMoveDown"))
  else
    moveUpSpeed = 1 * math.abs(obj:FindLuaTableAttribute("AnimSpeedMoveUp"))
    moveDownSpeed = -1 * math.abs(obj:FindLuaTableAttribute("AnimSpeedMoveDown"))
  end
  callUpSpeed = moveUpSpeed
  callDownSpeed = moveDownSpeed
  if sonTable.moveUp then
    sonTable.moveUp = thisObj:FindSingleGOByName("Son_Ride_Up")
  else
    sonTable.moveUp = nil
  end
  if sonTable.moveDown then
    sonTable.moveDown = thisObj:FindSingleGOByName("Son_Ride_Down")
  else
    sonTable.moveDown = nil
  end
  if elevatorState == "Top" then
    if invisSheet then
      invisSheet:Hide()
    end
  elseif elevatorState == "Bottom" then
    if invisSheet then
      invisSheet:Hide()
    end
  elseif (elevatorState == "MovingDown" or elevatorState == "MovingUp") and invisSheet then
    invisSheet:Show()
  end
  if enabled == true then
    EnableElevator()
  else
    DisableElevator()
  end
  SoundInit()
end
function OnStart(level, obj)
  if (elevatorState == "Top" or elevatorState == "Bottom") and invisSheet then
    invisSheet:Hide()
  end
end
function OnUpdate(level, obj)
  debuglog = "OnUpdate"
  if not LocationIsOnScreen(son:GetWorldPosition()) or son:IsInsideEntityZone(elevatorObject) or not son:IsInsideEntityZone(elevatorObject) and (elevatorState == "MovingUp" or elevatorState == "MovingDown") then
    game.SubObject.Sleep(obj)
    WarpSon()
    EnableGamePad(true)
    ExecuteOnWaitCompleteCallbacks()
  end
end
function OnSaveCheckpoint(level, obj)
  return {
    state = elevatorState,
    isEnabled = enabled,
    bottomFloorFrame = bottomFloorFrame
  }
end
function OnRestoreCheckpoint(level, obj, savedInfo)
  elevatorState = savedInfo.state
  enabled = savedInfo.isEnabled
  bottomFloorFrame = savedInfo.bottomFloorFrame
end
function IsEnabled()
  return enabled
end
function GetState()
  return elevatorState
end
function GetStartFrame()
  return startFrame
end
function DisableElevator()
  enabled = false
  elevatorObject:ClearAllAnimationCallbacks()
  if switch_Obj ~= nil then
    switch_Obj.LuaObjectScript.Enable()
    switch_Obj.LuaObjectScript.Lock()
  end
  if switchBottom_Obj then
    switchBottom_Obj.LuaObjectScript.Disable(true)
  end
  if switchTop_Obj then
    switchTop_Obj.LuaObjectScript.Disable(true)
  end
end
function EnableElevator()
  enabled = true
  elevatorObject:OnAnimationDone(thisObj, "ResetElevator", {Force = true})
  if switch_Obj ~= nil then
    switch_Obj.LuaObjectScript.Enable()
    switch_Obj.LuaObjectScript.Unlock()
  end
  if switchBottom_Obj and elevatorState == "Top" then
    switchBottom_Obj.LuaObjectScript.Enable()
  end
  if switchTop_Obj and elevatorState == "Bottom" then
    switchTop_Obj.LuaObjectScript.Enable()
  end
end
function EnableCinematicTrigger()
  triggerCinematic = true
  elevatorObject:OnAnimationDone(thisObj, "CineResetElevator", {Force = true})
end
function DisableCinematicTrigger()
  triggerCinematic = false
  elevatorObject:OnAnimationDone(thisObj, "ResetElevator", {Force = true})
end
function UpdateState()
  if elevatorObject.AnimFrame == topFloorFrame then
    elevatorState = "Top"
  elseif elevatorObject.AnimFrame == bottomFloorFrame then
    elevatorState = "Bottom"
  else
    elevatorState = "InBetween"
  end
end
function SetIgnoreNextCheckpointOnFloor()
  ignoreNextCheckpointOnFloor = checkpointOnFloor
end
function SetUseCollisionCheckForWarp(value)
  sonTable.useCollisionCheck = value
end
function EnableContextAction()
  son_MoveToPoint.LuaObjectScript.Enable()
end
function DisableContextAction()
  son_MoveToPoint.LuaObjectScript.Disable()
end
function SetSonAvailableForCombat_WhileRiding(value)
  sonTable.availableForCombat = value
end
function SetOverrideCA_Facing(value)
  sonTable.overrideCA_Facing = value
end
function IsSwitchEnabled()
  if switch_Obj ~= nil then
    return switch_Obj.LuaObjectScript.IsSwitchEnabled()
  end
  return nil
end
function GetElevatorSwitch()
  if switch_Obj then
    return switch_Obj
  end
  return nil
end
function GetTopFloorCallSwitch()
  if switchTop_Obj then
    return switchTop_Obj
  end
  return nil
end
function GetBottomFloorCallSwitch()
  if switchBottom_Obj then
    return switchBottom_Obj
  end
  return nil
end
function SetTopFloorCallSwitch(obj, isBeforeInit)
  if obj then
    switchTop_Obj = obj
    if not isBeforeInit then
      switchTop_Obj.LuaObjectScript.AddSwitchedCompleteCallback(MoveUp)
      if elevatorState == "Bottom" then
        switchTop_Obj.LuaObjectScript.Enable()
        switchTop_Obj.LuaObjectScript.Unlock()
      elseif elevatorState == "Top" then
        switchTop_Obj.LuaObjectScript.Disable(true)
      end
    end
  end
end
function SetBottomFloorCallSwitch(obj, isBeforeInit)
  if obj then
    switchBottom_Obj = obj
    if not isBeforeInit then
      switchBottom_Obj.LuaObjectScript.AddSwitchedCompleteCallback(MoveDown)
      if elevatorState == "Bottom" then
        switchBottom_Obj.LuaObjectScript.Disable(true)
      elseif elevatorState == "Top" then
        switchBottom_Obj.LuaObjectScript.Enable()
        switchBottom_Obj.LuaObjectScript.Unlock()
      end
    end
  end
end
function UpdateFloorFrames(newTopFrame, newBottomFrame)
  if newTopFrame then
    topFloorFrame = ParseFrame(newTopFrame)
  end
  if newBottomFrame then
    bottomFloorFrame = ParseFrame(newBottomFrame)
  end
  UpdateState()
  UpdateMoveSpeed()
end
function SetBottomFloorFrame(frame)
  bottomFloorFrame = ParseFrame(frame)
  UpdateState()
  UpdateMoveSpeed()
end
function UpdateMoveSpeed(newMoveUpSpeed, newMoveDownSpeed, doNotUpdateCallSpeed)
  if newMoveUpSpeed then
    moveUpSpeed = newMoveUpSpeed
  end
  if newMoveDownSpeed then
    moveDownSpeed = newMoveDownSpeed
  end
  if topFloorFrame < bottomFloorFrame then
    moveUpSpeed = -1 * math.abs(moveUpSpeed)
    moveDownSpeed = 1 * math.abs(moveDownSpeed)
  else
    moveUpSpeed = 1 * math.abs(moveUpSpeed)
    moveDownSpeed = -1 * math.abs(moveDownSpeed)
  end
  if not doNotUpdateCallSpeed then
    callUpSpeed = moveUpSpeed
    callDownSpeed = moveDownSpeed
  end
end
function UpdateCallSpeed(newCallUpSpeed, newCallDownSpeed)
  if newCallUpSpeed then
    callUpSpeed = newCallUpSpeed
  end
  if newCallDownSpeed then
    callDownSpeed = newCallDownSpeed
  end
  if topFloorFrame < bottomFloorFrame then
    callUpSpeed = -1 * math.abs(callUpSpeed)
    callDownSpeed = 1 * math.abs(callDownSpeed)
  else
    callUpSpeed = 1 * math.abs(callUpSpeed)
    callDownSpeed = -1 * math.abs(callDownSpeed)
  end
end
function MoveUp()
  if enabled == false or elevatorState == "Top" or elevatorState == "MovingUp" or elevatorState == "MovingDown" then
    return
  end
  elevatorState = "MovingUp"
  if playerActivatedElevator then
    elevatorObject:PlayAnimationToFrame(topFloorFrame, {Rate = moveUpSpeed})
  else
    elevatorObject:PlayAnimationToFrame(topFloorFrame, {Rate = callUpSpeed})
  end
  if triggerCinematic == false then
    LD.ExecuteCallbacksForEvent(thisLevel, elevatorObject, onMoveUp, "BASIC ELEVATOR: " .. elevatorState)
    ExecuteOnMoveUpCallbacks()
  end
  if switchTop_Obj then
    switchTop_Obj.LuaObjectScript.Disable(playerActivatedElevator)
  end
  if switchBottom_Obj then
    switchBottom_Obj.LuaObjectScript.Disable(true)
  end
  PlayOnMoveUpSound()
end
function MoveDown()
  if enabled == false or elevatorState == "Bottom" or elevatorState == "MovingDown" or elevatorState == "MovingUp" then
    return
  end
  elevatorState = "MovingDown"
  if playerActivatedElevator then
    elevatorObject:PlayAnimationToFrame(bottomFloorFrame, {Rate = moveDownSpeed})
  else
    elevatorObject:PlayAnimationToFrame(bottomFloorFrame, {Rate = callDownSpeed})
  end
  if triggerCinematic == false then
    LD.ExecuteCallbacksForEvent(thisLevel, elevatorObject, onMoveDown, "BASIC ELEVATOR: " .. elevatorState)
    ExecuteOnMoveDownCallbacks()
  end
  if switchTop_Obj then
    switchTop_Obj.LuaObjectScript.Disable(true)
  end
  if switchBottom_Obj then
    switchBottom_Obj.LuaObjectScript.Disable(playerActivatedElevator)
  end
  PlayOnMoveDownSound()
end
function MoveToFrame(frame)
  if enabled == false then
    return
  end
  if 0 < moveUpSpeed then
    if frame < elevatorObject.AnimFrame then
      elevatorObject:PlayAnimToFrame(frame, moveDownSpeed)
      elevatorState = "MovingDown"
      PlayOnMoveDownSound()
    elseif frame > elevatorObject.AnimFrame then
      elevatorObject:PlayAnimToFrame(frame, moveUpSpeed)
      elevatorState = "MovingUp"
      PlayOnMoveUpSound()
    end
  elseif frame < elevatorObject.AnimFrame then
    elevatorObject:PlayAnimToFrame(frame, moveUpSpeed)
    elevatorState = "MovingUp"
    PlayOnMoveUpSound()
  elseif frame > elevatorObject.AnimFrame then
    elevatorObject:PlayAnimToFrame(frame, moveDownSpeed)
    elevatorState = "MovingDown"
    PlayOnMoveDownSound()
  end
  if invisSheet then
    invisSheet:ShowCollision()
  end
  elevatorObject:OnAnimationDone(thisObj, "ResetElevator", {Force = true})
end
function PlayerMoveElevator()
  if elevatorState == "MovingUp" or elevatorState == "MovingDown" then
    return
  end
  if switch_Obj then
    switch_Obj.LuaObjectScript.Disable()
  else
    PlayCameraShakeEffect()
  end
  playerActivatedElevator = true
  if invisSheet then
    invisSheet:ShowCollision()
  end
  if elevatorState == "Top" then
    MoveDown()
  elseif elevatorState == "Bottom" then
    MoveUp()
  end
end
function DisableSonWarpOnElevator()
  enableSonWarp = false
end
function WaitForSonWhileOnScreen()
  debuglog = ""
  if not triggerCinematic and son and enableSonWarp then
    debuglog = "WaitForSonWhileOnScreen"
    if LocationIsOnScreen(son:GetWorldPosition()) and not son:IsInsideEntityZone(elevatorObject) then
      EnableGamePad(false)
      ExecuteWaitingCallbacks()
      game.SubObject.Wake(thisObj)
    else
      WarpSon()
    end
  end
end
function GetCurrentSonRidePositionObject()
  if (elevatorState == "Top" or elevatorState == "MovingDown") and sonTable.moveDown then
    return sonTable.moveDown
  elseif (elevatorState == "Bottom" or elevatorState == "MovingUp") and sonTable.moveUp then
    return sonTable.moveUp
  else
    return nil
  end
end
local SetSonUnavailableForCombat = function()
  if son ~= nil and sonTable.availableForCombat == false and son:IsAvailableInLevel() then
    local availabilityState = {
      AvailableInLevel = true,
      AvailableForBanter = son:IsAvailableForBanter(),
      AvailableForSync = son:IsAvailableForSync(),
      AvailableForCombat = false,
      Unoccupied = son:IsUnoccupied()
    }
    son:SetNewAvailabilityRequest("SonInteraction", availabilityState)
  end
end
local RemoveSonAvailableForCombat = function()
  if son ~= nil and sonTable.availableForCombat == false then
    son:RemoveAvailabilityRequest("SonInteraction")
  end
end
function WarpSon()
  debuglog = "WarpSon()"
  if not triggerCinematic and son and enableSonWarp then
    if son.OwnedPOI ~= nil and son.OwnedPOI.Type == "ContextAction" and son.OwnedPOI.LuaObjectScript and son.OwnedPOI.LuaObjectScript.Interrupt then
      son.OwnedPOI.LuaObjectScript.Interrupt()
    end
    local sonPos
    son_MoveToPoint.LuaObjectScript.Disable()
    if (elevatorState == "Top" or elevatorState == "MovingDown") and sonTable.moveDown then
      sonPos = sonTable.moveDown
    elseif (elevatorState == "Bottom" or elevatorState == "MovingUp") and sonTable.moveUp then
      sonPos = sonTable.moveUp
    end
    if sonPos then
      son_MoveToPoint:SetWorldPosition(sonPos:GetWorldPosition())
      son_MoveToPoint:SetWorldFacing(sonPos:GetWorldForward())
      if not son:IsInsideEntityZone(elevatorObject) then
        son:Warp(sonPos:GetWorldPosition(), sonPos:GetWorldForward(), true)
        debuglog = "son:Warp"
      end
      if sonTable.overrideCA_Facing then
        LD.CallFunctionAfterDelay(function()
          son_MoveToPoint:SetWorldFacing(sonPos:GetWorldForward())
          son_MoveToPoint.LuaObjectScript.Enable()
        end, 0.1)
      else
        son_MoveToPoint.LuaObjectScript.Enable()
      end
    end
    SetSonUnavailableForCombat()
  end
  enableSonWarp = true
end
function AddWaitingCallback(fn)
  if waitingOnSonCallback == nil then
    waitingOnSonCallback = {}
  end
  table.insert(waitingOnSonCallback, fn)
end
function ExecuteWaitingCallbacks()
  if waitingOnSonCallback ~= nil then
    for _, callback in ipairs(waitingOnSonCallback) do
      callback(elevatorState)
    end
  end
end
function AddOnWaitCompleteCallback(fn)
  if onWaitComplete == nil then
    onWaitComplete = {}
  end
  table.insert(onWaitComplete, fn)
end
function ExecuteOnWaitCompleteCallbacks()
  if onWaitComplete ~= nil then
    for _, callback in ipairs(onWaitComplete) do
      callback(elevatorState)
    end
  end
end
function JumpToTopFloor()
  if enabled == false or elevatorState == "Top" or elevatorState == "MovingUp" or elevatorState == "MovingDown" then
    return
  end
  if triggerCinematic == false then
    elevatorState = "Top"
    elevatorObject:JumpAnimToFrame(topFloorFrame)
    elevatorObject:PauseAnim()
    LD.ExecuteCallbacksForEvent(thisLevel, elevatorObject, onMoveUp, "BASIC ELEVATOR: " .. elevatorState)
    ExecuteOnMoveUpCallbacks()
  else
    elevatorState = "Top"
  end
  if switchBottom_Obj then
    switchBottom_Obj.LuaObjectScript.Enable()
    switchBottom_Obj.LuaObjectScript.Unlock()
  end
  if switchTop_Obj then
    switchTop_Obj.LuaObjectScript.Disable(true)
  end
  LD.ExecuteCallbacksForEvent(thisLevel, elevatorObject, onReachTop, "BASIC ELEVATOR: OnReachedTop Callback")
  ExecuteOnReachTopCallbacks()
end
function JumpToBottomFloor()
  if enabled == false or elevatorState == "Bottom" or elevatorState == "MovingDown" or elevatorState == "MovingUp" then
    return
  end
  if triggerCinematic == false then
    elevatorState = "Bottom"
    elevatorObject:JumpAnimToFrame(bottomFloorFrame)
    elevatorObject:PauseAnim()
    LD.ExecuteCallbacksForEvent(thisLevel, elevatorObject, onMoveDown, "BASIC ELEVATOR: " .. elevatorState)
    ExecuteOnMoveDownCallbacks()
  else
    elevatorState = "Bottom"
  end
  if switchBottom_Obj then
    switchBottom_Obj.LuaObjectScript.Disable(true)
  end
  if switchTop_Obj then
    switchTop_Obj.LuaObjectScript.Enable()
    switchTop_Obj.LuaObjectScript.Unlock()
  end
  LD.ExecuteCallbacksForEvent(thisLevel, elevatorObject, onReachBottom, "BASIC ELEVATOR: OnReachedBottom Callback")
  ExecuteOnReachBottomCallbacks()
end
function JumpToFrame(frame)
  elevatorObject:JumpAnimToFrame(frame)
  elevatorObject:PauseAnim()
  elevatorState = "InBetween"
end
function ResetElevator()
  if elevatorState == "Top" or elevatorState == "Bottom" or triggerCinematic then
    return
  end
  elevatorObject:OnAnimationDone(thisObj, "ResetElevator", {Force = true})
  if switch_Obj and playerActivatedElevator == true then
    switch_Obj.LuaObjectScript.Enable()
  end
  if invisSheet then
    invisSheet:Hide()
  end
  RemoveSonAvailableForCombat()
  if elevatorState == "MovingDown" then
    elevatorState = "Bottom"
    LD.ExecuteCallbacksForEvent(thisLevel, elevatorObject, onReachBottom, "BASIC ELEVATOR: OnReachedBottom Callback")
    ExecuteOnReachBottomCallbacks()
    if cameraShakeType ~= "None" and bIsUsingRealmTravelShakeTiming == false then
      PlayCameraShakeEffect()
    end
    if switchTop_Obj then
      switchTop_Obj.LuaObjectScript.Enable()
      switchTop_Obj.LuaObjectScript.Unlock()
    end
    if switchBottom_Obj then
      switchBottom_Obj.LuaObjectScript.Disable(true)
    end
  elseif elevatorState == "MovingUp" then
    elevatorState = "Top"
    LD.ExecuteCallbacksForEvent(thisLevel, elevatorObject, onReachTop, "BASIC ELEVATOR: OnReachedTop Callback")
    ExecuteOnReachTopCallbacks()
    if cameraShakeType ~= "None" and bIsUsingRealmTravelShakeTiming == false then
      PlayCameraShakeEffect()
    end
    if switchTop_Obj then
      switchTop_Obj.LuaObjectScript.Disable(true)
    end
    if switchBottom_Obj then
      switchBottom_Obj.LuaObjectScript.Enable()
      switchBottom_Obj.LuaObjectScript.Unlock()
    end
  end
  if checkpointOnFloor == true and ignoreNextCheckpointOnFloor == false then
    if elevatorState == "Top" then
      LD.StoreCheckpointOverride(checkpointOverrideTopFloor)
    elseif elevatorState == "Bottom" then
      LD.StoreCheckpointOverride(checkpointOverrideBottomFloor)
    end
  end
  ignoreNextCheckpointOnFloor = false
  son_MoveToPoint.LuaObjectScript.Disable()
  playerActivatedElevator = false
end
function CineResetElevator()
  if elevatorState == "Top" or elevatorState == "Bottom" then
    return
  end
  elevatorObject:OnAnimationDone(thisObj, "ResetElevator", {Force = true})
  if switch_Obj ~= nil and playerActivatedElevator == true then
    switch_Obj.LuaObjectScript.Enable()
  end
  playerActivatedElevator = false
  if invisSheet then
    invisSheet:Hide()
  end
  RemoveSonAvailableForCombat()
  if elevatorState == "MovingDown" then
    elevatorState = "Bottom"
    if switchTop_Obj then
      switchTop_Obj.LuaObjectScript.Enable()
    end
    if switchBottom_Obj then
      switchBottom_Obj.LuaObjectScript.Disable(true)
    end
  elseif elevatorState == "MovingUp" then
    elevatorState = "Top"
    if switchTop_Obj then
      switchTop_Obj.LuaObjectScript.Disable(true)
    end
    if switchBottom_Obj then
      switchBottom_Obj.LuaObjectScript.Enable()
    end
  end
  son_MoveToPoint.LuaObjectScript.Disable()
  triggerCinematic = false
end
function AddOnMoveDownCallback(fn)
  if fireOnMoveDown == nil then
    fireOnMoveDown = {}
  end
  table.insert(fireOnMoveDown, fn)
end
function ExecuteOnMoveDownCallbacks()
  if fireOnMoveDown ~= nil then
    for _, callback in ipairs(fireOnMoveDown) do
      callback()
    end
  end
end
function AddOnMoveUpCallback(fn)
  if fireOnMoveUp == nil then
    fireOnMoveUp = {}
  end
  table.insert(fireOnMoveUp, fn)
end
function ExecuteOnMoveUpCallbacks()
  if fireOnMoveUp ~= nil then
    for _, callback in ipairs(fireOnMoveUp) do
      callback()
    end
  end
end
function AddOnReachTopCallback(fn)
  if fireOnReachTop == nil then
    fireOnReachTop = {}
  end
  table.insert(fireOnReachTop, fn)
end
function ExecuteOnReachTopCallbacks()
  if fireOnReachTop ~= nil then
    for _, callback in ipairs(fireOnReachTop) do
      callback()
    end
  end
end
function AddOnReachBottomCallback(fn)
  if fireOnReachBottom == nil then
    fireOnReachBottom = {}
  end
  table.insert(fireOnReachBottom, fn)
end
function ExecuteOnReachBottomCallbacks()
  if fireOnReachBottom ~= nil then
    for _, callback in ipairs(fireOnReachBottom) do
      callback()
    end
  end
end
function PlayCameraShakeEffect()
  game.Blender.Trigger({Name = cameraShakeType})
  game.Blender.Trigger({Name = rumbleType})
end
function ParseFrame(stringFrames)
  stringFrames = ("" .. stringFrames):lower()
  if stringFrames == nil or stringFrames == "" then
    return nil
  elseif stringFrames == "end" or stringFrames == "animlengthframes" then
    return elevatorObject.AnimLengthFrames
  elseif tonumber(stringFrames) == nil then
    error(">" .. stringFrames .. "On " .. elevatorObject:GetName() .. " ParseFloorFrame does not have a number value or 'animlengthframes' as value ")
    return 0
  else
    return tonumber(stringFrames)
  end
end
function ParseAttribute(obj, attribute)
  local stringFrames = obj:FindLuaTableAttribute(attribute):lower()
  if stringFrames == nil or stringFrames == "" then
    return nil
  elseif stringFrames == "end" or stringFrames == "animlengthframes" then
    return elevatorObject.AnimLengthFrames
  elseif tonumber(stringFrames) == nil then
    error(">" .. stringFrames .. "On " .. elevatorObject:GetName() .. " " .. attribute .. " does not have a number value or 'animlengthframes' as value ")
    return 0
  else
    return tonumber(stringFrames)
  end
end
function LocationIsOnScreen(loc)
  local yOffset = engine.Vector.New(0, 0.5, 0)
  if not sonTable.useCollisionCheck then
    return game.World.IsSphereOnScreen(loc + yOffset, 0.25)
  else
    local collisionCheck = require("camera.collisioncheck")
    local check = collisionCheck.isTargetInFrustumUnobstructed(loc + yOffset, 1, 1, 0, false)
    if check then
      return true
    else
      return false
    end
  end
end
function EnableGamePad(value)
  local buttons = {
    "kPadRightStickX",
    "kPadRightStickY"
  }
  for i = 1, #buttons do
    if value then
      player.Pad:EnableGameButton(tweaks.ePad[buttons[i]])
    else
      player.Pad:DisableGameButton(tweaks.ePad[buttons[i]])
    end
  end
end
local animForwardOnElevatorUp, soundEmitter, soundEmitterName, freyaLift
local elevatorSound = {
  OnStart = "",
  AnimFrameOnStart = 0,
  OnReturnToStart = "",
  AnimFrameOnReturnToStart = 0,
  OnForward = "",
  AnimFrameOnForward = 0,
  OnBackward = "",
  AnimFrameOnBackward = 0,
  OnStartFromEnd = "",
  AnimFrameOnStartFromEnd = 0,
  OnEnd = "",
  AnimFrameOnEnd = 0,
  AnimFrameTopFloor = -1,
  AnimFrameBottomFloor = -1
}
function SoundInit()
  animForwardOnElevatorUp = bottomFloorFrame < topFloorFrame
  soundEmitterName = thisObj:FindLuaTableAttribute("soundEmitterName")
  if soundEmitterName ~= nil and soundEmitterName ~= "" then
    if soundEmitterName == "SNDFreyaElevator" then
      freyaLift = thisLevel:GetGameObject("Lift_02")
      soundEmitter = freyaLift:FindSingleGOByName("Sound").SoundEmitters[1]
    else
      soundEmitter = thisObj:FindSingleSoundEmitterByName(soundEmitterName)
    end
  end
  elevatorSound.OnStart = thisObj:FindLuaTableAttribute("soundOnStart")
  elevatorSound.AnimFrameOnStart = select(2, LD.FindAnimFrameOrPercentInfoFromLuaTableAttr(thisObj, "soundAnimFrameOnStart"))
  elevatorSound.OnReturnToStart = thisObj:FindLuaTableAttribute("soundOnReturnToStart")
  elevatorSound.AnimFrameOnReturnToStart = select(2, LD.FindAnimFrameOrPercentInfoFromLuaTableAttr(thisObj, "soundAnimFrameOnReturnToStart"))
  elevatorSound.OnForward = thisObj:FindLuaTableAttribute("soundOnForward")
  elevatorSound.AnimFrameOnForward = select(2, LD.FindAnimFrameOrPercentInfoFromLuaTableAttr(thisObj, "soundAnimFrameOnForward"))
  elevatorSound.OnBackward = thisObj:FindLuaTableAttribute("soundOnBackward")
  elevatorSound.AnimFrameOnBackward = select(2, LD.FindAnimFrameOrPercentInfoFromLuaTableAttr(thisObj, "soundAnimFrameOnBackward"))
  elevatorSound.OnStartFromEnd = thisObj:FindLuaTableAttribute("soundOnStartFromEnd")
  elevatorSound.AnimFrameOnStartFromEnd = select(2, LD.FindAnimFrameOrPercentInfoFromLuaTableAttr(thisObj, "soundAnimFrameOnStartFromEnd"))
  elevatorSound.OnEnd = thisObj:FindLuaTableAttribute("soundOnEnd")
  elevatorSound.AnimFrameOnEnd = select(2, LD.FindAnimFrameOrPercentInfoFromLuaTableAttr(thisObj, "soundAnimFrameOnEnd"))
end
function SoundSetup(newTable)
  if newTable ~= nil then
    if newTable.SoundEmitter ~= nil then
      if soundEmitter ~= nil and soundEmitter == thisObj:FindSingleSoundEmitterByName(soundEmitterName) then
        thisObj:Hide()
      end
      soundEmitter = newTable.SoundEmitter
    end
    for newKey, newValue in pairs(newTable) do
      for baseKey in pairs(elevatorSound) do
        if newKey == baseKey and newValue ~= nil and newValue ~= "" then
          elevatorSound[baseKey] = newValue
        end
      end
    end
  end
end
function PlayOnMoveUpSound()
  local direction
  if animForwardOnElevatorUp then
    PlayForwardSound()
    direction = "forward"
  else
    PlayBackwardSound()
    direction = "backward"
  end
  if elevatorSound.AnimFrameTopFloor > -1 then
    StopLoopingSoundOnFrame(elevatorSound.AnimFrameTopFloor, direction)
  else
    StopLoopingSoundOnFrame(topFloorFrame, direction)
  end
end
function PlayOnMoveDownSound()
  local direction
  if animForwardOnElevatorUp then
    PlayBackwardSound()
    direction = "backward"
  else
    PlayForwardSound()
    direction = "forward"
  end
  if elevatorSound.AnimFrameBottomFloor > -1 then
    StopLoopingSoundOnFrame(elevatorSound.AnimFrameBottomFloor, direction)
  else
    StopLoopingSoundOnFrame(bottomFloorFrame, direction)
  end
end
function PlayStartSound()
  if elevatorObject.AnimFrame < 3 then
    LD.PlaySoundOnFrame(soundEmitter, elevatorObject, elevatorSound.OnStart, elevatorSound.AnimFrameOnStart, "forward")
  end
end
function PlayReturnToStartSound()
  LD.PlaySoundOnFrame(soundEmitter, elevatorObject, elevatorSound.OnReturnToStart, elevatorSound.AnimFrameOnReturnToStart, "backward")
end
function PlayForwardSound()
  PlayStartSound()
  LD.PlaySoundOnFrame(soundEmitter, elevatorObject, elevatorSound.OnForward, elevatorSound.AnimFrameOnForward, "forward", true)
  PlayEndSound()
end
function PlayBackwardSound()
  PlayStartFromEndSound()
  LD.PlaySoundOnFrame(soundEmitter, elevatorObject, elevatorSound.OnBackward, elevatorSound.AnimFrameOnBackward, "backward", true)
  PlayReturnToStartSound()
end
function PlayEndSound()
  LD.PlaySoundOnFrame(soundEmitter, elevatorObject, elevatorSound.OnEnd, elevatorSound.AnimFrameOnEnd, "forward")
end
function PlayStartFromEndSound()
  LD.PlaySoundOnFrame(soundEmitter, elevatorObject, elevatorSound.OnStartFromEnd, elevatorSound.AnimFrameOnStartFromEnd, "backward")
end
function StopLoopingSoundOnFrame(frame, direction)
  local animRate = 0
  if elevatorState == "MovingUp" then
    if playerActivatedElevator then
      animRate = moveUpSpeed
    else
      animRate = callUpSpeed
    end
  elseif elevatorState == "MovingDown" then
    if playerActivatedElevator then
      animRate = moveDownSpeed
    else
      animRate = callDownSpeed
    end
  end
  local animDirection
  if 0 < animRate then
    animDirection = "forward"
  elseif animRate < 0 then
    animDirection = "backward"
  end
  if string.lower(direction) == "backward" then
    if bIsUsingRealmTravelShakeTiming == false then
      LD.StopSoundOnFrame(soundEmitter, elevatorObject, elevatorSound.OnBackward, frame, animDirection)
    else
      LD.StopSoundOnFrame(soundEmitter, elevatorObject, elevatorSound.OnBackward, frame, animDirection, false, PlayCameraShakeEffect)
    end
  elseif string.lower(direction) == "forward" then
    if bIsUsingRealmTravelShakeTiming == false then
      LD.StopSoundOnFrame(soundEmitter, elevatorObject, elevatorSound.OnForward, frame, animDirection)
    else
      LD.StopSoundOnFrame(soundEmitter, elevatorObject, elevatorSound.OnForward, frame, animDirection, false, PlayCameraShakeEffect)
    end
  end
end
function PlaySoundOnElevator(snd)
  LD.PlaySound(soundEmitter, snd)
end
function ShowDebugTable(X, Y, distance, Title)
  distance = distance or 200
  if distance >= game.Player.FindPlayer():GetWorldPosition():Distance(elevatorObject:GetWorldPosition()) then
    local debugTable = {}
    debugTable.Title = "Basic Elevator Info"
    debugTable.X = X or 120
    debugTable.Y = Y or 10
    debugTable.TitleColor = engine.Vector.New(255, 0, 128)
    debugTable[#debugTable + 1] = {
      "elevatorObject: ",
      elevatorObject
    }
    debugTable[#debugTable + 1] = {
      "thisObj: ",
      thisObj.Parent
    }
    debugTable[#debugTable + 1] = {"State: ", elevatorState}
    debugTable[#debugTable + 1] = {"debuglog: ", debuglog}
    debugTable[#debugTable + 1] = {
      "Is Son On Elevator: ",
      tostring(son:IsInsideEntityZone(elevatorObject))
    }
    debugTable[#debugTable + 1] = {
      "IsSonOnScreen: ",
      tostring(LocationIsOnScreen(son:GetWorldPosition()))
    }
    engine.DrawDebugTable(debugTable)
  end
end
function ShowDebugText(distance)
  distance = distance or 100
  if distance >= game.Player.FindPlayer():GetWorldPosition():Distance(elevatorObject:GetWorldPosition()) then
    local color = require("core.color")
    local debugText = "elevatorObject: " .. ", " .. elevatorObject.Parent:GetName()
    debugText = debugText .. "\n" .. "AnimFrame: " .. ", " .. tostring(elevatorObject.AnimFrame)
    debugText = debugText .. "\n" .. "State: " .. ", " .. elevatorState
    debugText = debugText .. "\n" .. "isEnabled: " .. ", " .. tostring(enabled)
    debugText = debugText .. "\n" .. "TriggerCinematic: " .. ", " .. tostring(triggerCinematic)
    local position = thisObj:GetWorldPosition()
    if switch_Obj then
      position = switch_Obj:GetWorldPosition()
    end
    engine.DrawTextInWorld(position, debugText, color.white)
  end
end
function SetIsUsingRealmTravelShakeTiming()
  bIsUsingRealmTravelShakeTiming = true
end
