local LD = require("design.LevelDesignLibrary")
local CCOS, timers, player, thisObj, interactZone, interactZone_Back, interactZonePair, interactFunction, enableFront, enableBack, disableAfterUse
local includeSon = true
local isLocked
local allowSyncWhileSonNotAvailable = false
local heroBranch, heroBranch_Back, sonBranch, sonBranch_Back, objectAnimName, objectAnimName_Back, uniqueMoves, promptJointName, promptJointName_Back, synchJointName, synchJointName_Back, lateRumbleName, lateRumbleDelayFromStart, onInteractRight, onInteractLeft, onDoorShutRight, onDoorShutLeft, doorName, cineInteractionTriggers_SideA, cineInteractionTriggers_SideB, interactionTriggers, interactionTriggers_SideA, interactionTriggers_SideB
local triggerCinematic = false
local luaOnStartEvents, luaHook_OnStartOpen, onStartDoor, onStartSync_SideA, onStartSync_SideB, interactionOnStart, interactionOnFinish, disableObstacleAfterUse
local camInteractApproach = "ENV_InteractApproach"
local camera_InteractApproach, InteractApproachYaw
local bSkipInteractApproachYaw = false
local cameraWorldSpaceForward = engine.Vector.New(0, 0, 1)
local interactType, subType
local interactDirection = "Forward"
local weaponMode, completionPercentage
local availabilitySet = false
local approachDirection = "Front"
local playFXOnMoveStart = false
local fxList = {}
local disabledCombat = false
local CCOS_LoadLibrary = function()
  if CCOS == nil then
    CCOS = require("camera.camera_oneshot")
  end
end
local timers_LoadLibrary = function()
  if timers == nil then
    timers = require("level.timer")
  end
end
function OnScriptLoaded(level, obj)
  player = game.Player.FindPlayer()
  thisObj = obj
  local externalTable = obj:FindSingleGOByName("TableRefNode")
  if externalTable ~= nil then
    if externalTable.IsRefNode == true then
      externalTable = externalTable.Child
    end
  else
    externalTable = obj
  end
  enableFront = externalTable:FindLuaTableAttribute("startEnabled") or obj:FindLuaTableAttribute("startEnabled")
  enableBack = enableFront
  disableAfterUse = externalTable:FindLuaTableAttribute("disableAfterUse") or obj:FindLuaTableAttribute("disableAfterUse")
  disableObstacleAfterUse = externalTable:FindLuaTableAttribute("disableObstacleAfterUse") or obj:FindLuaTableAttribute("disableObstacleAfterUse")
  interactType = externalTable:FindLuaTableAttribute("interactType") or obj:FindLuaTableAttribute("interactType")
  subType = externalTable:FindLuaTableAttribute("subType")
  heroBranch = externalTable:FindLuaTableAttribute("heroBranch")
  heroBranch_Back = externalTable:FindLuaTableAttribute("heroBranch_Back")
  sonBranch = externalTable:FindLuaTableAttribute("sonBranch")
  sonBranch_Back = externalTable:FindLuaTableAttribute("sonBranch_Back")
  objectAnimName = externalTable:FindLuaTableAttribute("objectAnimName")
  objectAnimName_Back = externalTable:FindLuaTableAttribute("objectAnimName_Back")
  uniqueMoves = externalTable:FindLuaTableAttribute("uniqueMoves")
  promptJointName = externalTable:FindLuaTableAttribute("promptJointName")
  promptJointName_Back = externalTable:FindLuaTableAttribute("promptJointName_Back")
  synchJointName = externalTable:FindLuaTableAttribute("synchJointName")
  synchJointName_Back = externalTable:FindLuaTableAttribute("synchJointName_Back")
  weaponMode = externalTable:FindLuaTableAttribute("weaponMode") or "Bare"
  completionPercentage = externalTable:FindLuaTableAttribute("completionPercentage") or 0
  lateRumbleName = externalTable:FindLuaTableAttribute("lateRumbleName")
  lateRumbleDelayFromStart = externalTable:FindLuaTableAttribute("lateRumbleDelayFromStart")
  onInteractLeft = obj:FindLuaTableAttribute("onInteractLeft") or externalTable:FindLuaTableAttribute("onInteractLeft")
  onInteractRight = obj:FindLuaTableAttribute("onInteractRight") or externalTable:FindLuaTableAttribute("onInteractRight")
  onDoorShutRight = obj:FindLuaTableAttribute("onDoorShutRight") or externalTable:FindLuaTableAttribute("onDoorShutRight")
  onDoorShutLeft = obj:FindLuaTableAttribute("onDoorShutLeft") or externalTable:FindLuaTableAttribute("onDoorShutLeft")
  doorName = obj:FindLuaTableAttribute("doorName") or externalTable:FindLuaTableAttribute("doorName")
  playFXOnMoveStart = externalTable:FindLuaTableAttribute("PlayFXOnMOV") or obj:FindLuaTableAttribute("PlayFXOnMOV")
  cineInteractionTriggers_SideA = obj:FindLuaTableAttribute("cineInteractionTriggers_SideA")
  cineInteractionTriggers_SideB = obj:FindLuaTableAttribute("cineInteractionTriggers_SideB")
  interactionTriggers = obj:FindLuaTableAttribute("interactionTriggers")
  interactionTriggers_SideA = obj:FindLuaTableAttribute("interactionTriggers_SideA")
  interactionTriggers_SideB = obj:FindLuaTableAttribute("interactionTriggers_SideB")
  interactionOnStart = obj:FindLuaTableAttribute("interactionOnStart")
  interactionOnFinish = obj:FindLuaTableAttribute("interactionOnFinish")
  onStartDoor = externalTable:FindLuaTableAttribute("onDoorStartOpen") or obj:FindLuaTableAttribute("onDoorStartOpen")
  onStartSync_SideA = externalTable:FindLuaTableAttribute("OnStartSync_SideA") or obj:FindLuaTableAttribute("OnStartSync_SideA")
  onStartSync_SideB = externalTable:FindLuaTableAttribute("OnStartSync_SideB") or obj:FindLuaTableAttribute("OnStartSync_SideB")
  isLocked = false
  SoundInit()
  triggerCinematic = false
  interactionTriggers = LD.ExtractCallbacksForEvent(level, obj, interactionTriggers)
  if interactionOnStart ~= nil then
    interactionOnStart = LD.ExtractCallbacksForEvent(level, obj, interactionOnStart)
  end
  if interactionOnFinish ~= nil then
    interactionOnFinish = LD.ExtractCallbacksForEvent(level, obj, interactionOnFinish)
  end
  if cineInteractionTriggers_SideA then
    cineInteractionTriggers_SideA = LD.ExtractCallbacksForEvent(level, obj, cineInteractionTriggers_SideA)
  end
  if cineInteractionTriggers_SideB then
    cineInteractionTriggers_SideB = LD.ExtractCallbacksForEvent(level, obj, cineInteractionTriggers_SideB)
  end
  if interactionTriggers_SideA then
    interactionTriggers_SideA = LD.ExtractCallbacksForEvent(level, obj, interactionTriggers_SideA)
  end
  if interactionTriggers_SideB then
    interactionTriggers_SideB = LD.ExtractCallbacksForEvent(level, obj, interactionTriggers_SideB)
  end
  if onStartSync_SideA then
    onStartSync_SideA = LD.ExtractCallbacksForEvent(level, obj, onStartSync_SideA)
  end
  if onStartSync_SideB then
    onStartSync_SideB = LD.ExtractCallbacksForEvent(level, obj, onStartSync_SideB)
  end
  if onStartDoor then
    onStartDoor = LD.ExtractCallbacksForEvent(level, obj, onStartDoor)
  end
  interactZone = LD.CreateInteractZone_Standard_180(obj, promptJointName)
  interactZone:SetHintAngle(180)
  interactZone_Back = LD.CreateInteractZone_Standard_180(obj, promptJointName_Back)
  interactZone_Back:SetHintAngle(180)
  interactZone:SetRequiresSonUnoccupied(true)
  interactZone_Back:SetRequiresSonUnoccupied(true)
  interactZonePair = {}
  table.insert(interactZonePair, interactZone)
  table.insert(interactZonePair, interactZone_Back)
  interactZonePair.hasMultiple = true
  if (subType == "Debris" or subType == "Spread") and completionPercentage == 0 then
    completionPercentage = 0.75
  end
end
function OnSaveCheckpoint(level, obj)
  return {
    enableFront = enableFront,
    enableBack = enableBack,
    isLocked = isLocked
  }
end
function OnRestoreCheckpoint(level, obj, savedInfo)
  enableFront = savedInfo.enableFront
  enableBack = savedInfo.enableBack
  isLocked = savedInfo.isLocked
end
function OnStart(level, obj)
  if not enableFront then
    DisableFront()
  else
    EnableFront()
  end
  if not enableBack then
    DisableBack()
  else
    EnableBack()
  end
  if isLocked == true then
    Lock()
  else
    Unlock()
  end
  if playFXOnMoveStart then
    local _fx = thisObj:FindGOsByName("FX_OnMOV")
    for i = 1, #_fx do
      table.insert(fxList, _fx[i])
    end
  else
    fxList = nil
  end
  game.SubObject.Sleep(thisObj)
end
function OnUpdate(level, obj)
  if camera_InteractApproach ~= nil then
    camera_InteractApproach:Update()
  end
end
local IsSonAvailable = function()
  local son = game.AI.FindSon()
  if son == nil or son:IsAvailableForSync() == true or includeSon == false or son:IsAvailableInLevel() == false then
    return true
  elseif son:IsAvailableForSync() == false and allowSyncWhileSonNotAvailable == true then
    return true
  else
    return false
  end
end
function OnUseWorld(level, obj)
  if interactZone:PlayerCanInteract() and IsSonAvailable() then
    if cineInteractionTriggers_SideA then
      LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, cineInteractionTriggers_SideA, "Cine Interaction Event (Side A)")
    end
    if triggerCinematic == false then
      interactFunction = PerformInteraction
      player:RequestInteract(obj, interactZone)
    end
    triggerCinematic = false
  elseif interactZone_Back:PlayerCanInteract() and IsSonAvailable() then
    if cineInteractionTriggers_SideB then
      LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, cineInteractionTriggers_SideB, "Cine Interaction Event (Side B)")
    end
    if triggerCinematic == false then
      interactFunction = PerformInteraction_Back
      player:RequestInteract(obj, interactZone_Back)
    end
    triggerCinematic = false
  end
end
function RegisterOnInteractStartCallback(fn)
  if luaOnStartEvents == nil then
    luaOnStartEvents = {}
  end
  luaOnStartEvents[#luaOnStartEvents + 1] = fn
end
function FireOnInteractStartCallbacks()
  if luaOnStartEvents ~= nil then
    for _, fn in pairs(luaOnStartEvents) do
      fn()
    end
  end
end
function ForceOnInteractStart(isInteractBack, overrideBranch)
  if isInteractBack == true then
    function interactFunction()
      PerformInteraction_Back(overrideBranch)
    end
  else
    function interactFunction()
      PerformInteraction(overrideBranch)
    end
  end
  player:RequestInteract(thisObj)
end
function OnInteractStart(level, obj)
  local son = game.AI.FindSon()
  if son ~= nil then
    son:AddMarker("SonInDoor")
  end
  game.SubObject.Wake(thisObj)
  if triggerCinematic == false then
    if interactionOnStart ~= nil then
      LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, interactionOnStart, "Interaction Event Start")
    end
    if interactFunction ~= nil then
      if game.Combat.InteractOverrideCombatStatus ~= nil then
        game.Combat.InteractOverrideCombatStatus(true)
        disabledCombat = true
      elseif son ~= nil and son:IsAvailableForSync() and son:IsAvailableInLevel() and includeSon then
        local availabilityState = {
          AvailableInLevel = true,
          AvailableForBanter = son:IsAvailableForBanter(),
          AvailableForSync = true,
          AvailableForCombat = false,
          Unoccupied = son:IsUnoccupied()
        }
        son:SetNewAvailabilityRequest("SonInteraction", availabilityState)
        availabilitySet = true
      end
      FireOnInteractStartCallbacks()
      interactFunction()
      if lateRumbleName and lateRumbleDelayFromStart then
        timers_LoadLibrary()
        timers.StartLevelTimer(lateRumbleDelayFromStart, function()
          game.Blender.Trigger({
            Name = lateRumbleName,
            Duration = 0.4,
            TweenIn = {Time = 0},
            TweenOut = {Time = 0.25}
          })
          game.Blender.Trigger({
            Name = "FSE_SHAKE_GENERIC_MEDIUM",
            Weight = 0.5,
            Duration = 1.5,
            TweenIn = {Time = 0},
            TweenOut = {Time = 0.25}
          })
        end)
      end
      thisObj:HideNavObstacle()
    end
  else
    engine.Warning("Warning: interact started with InteractiveObject_TwoWay (" .. thisObj:GetName() .. ") while triggerCinematic is true. These should not be done together. Ise the cine object as the interaction object for cinematics.")
  end
  local playerPosition = player:GetWorldPosition()
  local interactPosition = obj:GetWorldPosition()
  local toInteract = interactPosition - playerPosition
  local interactFwd = obj:GetWorldForward()
  local interactAngle
  local cameraSubmitTime = 3
  if toInteract:Dot(interactFwd) > 0 then
    print("Approaching from front")
    interactAngle = LD.GetAngleBetweenVector(-interactFwd, cameraWorldSpaceForward)
    PlaySoundGuardianDoor_Forward()
    approachDirection = "Front"
  else
    print("Approaching from behind")
    interactAngle = LD.GetAngleBetweenVector(interactFwd, cameraWorldSpaceForward)
    PlaySoundGuardianDoor_Backward()
    approachDirection = "Back"
  end
  if not bSkipInteractApproachYaw then
    InteractApproachYaw = {Yaw = interactAngle, Pitch = 4}
  end
  if toInteract:Length() < 2 then
    cameraSubmitTime = 2
  end
  game.Camera.Recenter({
    TimeStart = 0,
    TimeDuration = cameraSubmitTime,
    LockRecenter = 1,
    YawRange = -1,
    TriggerLeft = 0,
    TriggerRight = 0,
    ReturnLeft = 180,
    ReturnRight = -180,
    PitchRange = 0
  })
  CCOS_LoadLibrary()
  camera_InteractApproach = CCOS.OneShotCamera.New(camInteractApproach, cameraSubmitTime, InteractApproachYaw)
  camera_InteractApproach:Start()
end
function OnInteractFinish()
  if interactionOnFinish ~= nil then
    LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, interactionOnFinish, "Interaction Event Finish")
  end
  if disableAfterUse then
    Disable()
  end
  if disableObstacleAfterUse then
    thisObj:HideNavObstacle()
  else
    thisObj:ShowNavObstacle()
  end
end
function OnInteractDone()
  local son = game.AI.FindSon()
  if son ~= nil then
    son:RemoveMarker("SonInDoor")
  end
  if game.Combat.InteractOverrideCombatStatus ~= nil then
    game.Combat.InteractOverrideCombatStatus(false)
    disabledCombat = false
  elseif son ~= nil and availabilitySet then
    son:RemoveAvailabilityRequest("SonInteraction")
    availabilitySet = false
  end
  game.SubObject.Sleep(thisObj)
end
function OnTeardown()
  if disabledCombat and game.Combat.InteractOverrideCombatStatus ~= nil then
    game.Combat.InteractOverrideCombatStatus(false)
    disabledCombat = false
  end
end
function PerformInteraction(overrideBranch)
  interactDirection = "Forward"
  LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, interactionTriggers, "Interaction Event")
  if interactionTriggers_SideA then
    LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, interactionTriggers_SideA, "Interaction Event (Side A)")
  end
  local son = game.AI.FindSon()
  if son ~= nil and includeSon and son:IsAvailableForSync() == true and son:IsAvailableInLevel() == true then
    if sonBranch == "" or heroBranch == "" then
      engine.Warning("Missing Branch, 'sonBranch' or 'heroBranch' in Lua Toolbox on object: " .. thisObj:GetName())
    elseif overrideBranch ~= nil and overrideBranch ~= "" then
      LD.PlaySingleSynchMove_KratosSonObject(thisObj, synchJointName, "Interactive2WayWithSon_" .. thisObj:GetName(), overrideBranch, sonBranch, objectAnimName, interactZonePair, disableAfterUse, true, weaponMode, {completion_percentage = completionPercentage})
    else
      LD.PlaySingleSynchMove_KratosSonObject(thisObj, synchJointName, "Interactive2WayWithSon_" .. thisObj:GetName(), heroBranch, sonBranch, objectAnimName, interactZonePair, disableAfterUse, true, weaponMode, {completion_percentage = completionPercentage})
    end
  elseif heroBranch == "" then
    engine.Warning("Missing Branch, 'heroBranch' in Lua Toolbox on object: " .. thisObj:GetName())
  elseif overrideBranch ~= nil and overrideBranch ~= "" then
    LD.PlaySingleSynchMove_KratosObject(thisObj, synchJointName, "Interactive2Way_" .. thisObj:GetName(), overrideBranch, objectAnimName, interactZonePair, disableAfterUse, weaponMode, {completion_percentage = completionPercentage})
  else
    LD.PlaySingleSynchMove_KratosObject(thisObj, synchJointName, "Interactive2Way_" .. thisObj:GetName(), heroBranch, objectAnimName, interactZonePair, disableAfterUse, weaponMode, {completion_percentage = completionPercentage})
  end
end
function PerformInteraction_Back(overrideBranch)
  interactDirection = "Backward"
  LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, interactionTriggers, "Interaction Event")
  if interactionTriggers_SideB then
    LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, interactionTriggers_SideB, "Interaction Event (Side B)")
  end
  local son = game.AI.FindSon()
  if son ~= nil and includeSon and son:IsAvailableForSync() == true and son:IsAvailableInLevel() == true then
    if uniqueMoves then
      if sonBranch_Back == "" or heroBranch_Back == "" then
        engine.Warning("Missing Branch, 'sonBranch_Back' or 'heroBranch_Back' in Lua Toolbox on object: " .. thisObj:GetName())
      elseif overrideBranch ~= nil and overrideBranch ~= "" then
        LD.PlaySingleSynchMove_KratosSonObject(thisObj, synchJointName_Back, "Interactive2WayBackWithSon_" .. thisObj:GetName(), overrideBranch, sonBranch_Back, objectAnimName_Back, interactZonePair, disableAfterUse, true, weaponMode, {completion_percentage = completionPercentage})
      else
        LD.PlaySingleSynchMove_KratosSonObject(thisObj, synchJointName_Back, "Interactive2WayBackWithSon_" .. thisObj:GetName(), heroBranch_Back, sonBranch_Back, objectAnimName_Back, interactZonePair, disableAfterUse, true, weaponMode, {completion_percentage = completionPercentage})
      end
    elseif sonBranch == "" or heroBranch == "" then
      engine.Warning("Missing Branch, 'sonBranch' or 'heroBranch' in Lua Toolbox on object: " .. thisObj:GetName())
    elseif overrideBranch ~= nil and overrideBranch ~= "" then
      LD.PlaySingleSynchMove_KratosSonObject(thisObj, synchJointName_Back, "Interactive2WayBackWithSon_" .. thisObj:GetName(), overrideBranch, sonBranch, objectAnimName, interactZonePair, disableAfterUse, true, weaponMode, {completion_percentage = completionPercentage})
    else
      LD.PlaySingleSynchMove_KratosSonObject(thisObj, synchJointName_Back, "Interactive2WayBackWithSon_" .. thisObj:GetName(), heroBranch, sonBranch, objectAnimName, interactZonePair, disableAfterUse, true, weaponMode, {completion_percentage = completionPercentage})
    end
  elseif uniqueMoves then
    if heroBranch == "" then
      engine.Warning("Missing Branch, 'heroBranch_Back' in Lua Toolbox on object: " .. thisObj:GetName())
    elseif overrideBranch ~= nil and overrideBranch ~= "" then
      LD.PlaySingleSynchMove_KratosObject(thisObj, synchJointName_Back, "Interactive2WayBack_" .. thisObj:GetName(), overrideBranch, objectAnimName_Back, interactZonePair, disableAfterUse, weaponMode, {completion_percentage = completionPercentage})
    else
      LD.PlaySingleSynchMove_KratosObject(thisObj, synchJointName_Back, "Interactive2WayBack_" .. thisObj:GetName(), heroBranch_Back, objectAnimName_Back, interactZonePair, disableAfterUse, weaponMode, {completion_percentage = completionPercentage})
    end
  elseif heroBranch == "" then
    engine.Warning("Missing Branch, 'heroBranch' in Lua Toolbox on object: " .. thisObj:GetName())
  elseif overrideBranch ~= nil and overrideBranch ~= "" then
    LD.PlaySingleSynchMove_KratosObject(thisObj, synchJointName_Back, "Interactive2WayBack_" .. thisObj:GetName(), overrideBranch, objectAnimName, interactZonePair, disableAfterUse, weaponMode, {completion_percentage = completionPercentage})
  else
    LD.PlaySingleSynchMove_KratosObject(thisObj, synchJointName_Back, "Interactive2WayBack_" .. thisObj:GetName(), heroBranch, objectAnimName, interactZonePair, disableAfterUse, weaponMode, {completion_percentage = completionPercentage})
  end
end
function LuaHook_OnStartOpen()
  print(" Firing LUA HOOK - LuaHook_OnStartOpen ")
  LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, onStartDoor, "Interaction Event (Two Way) - LuaHook_OnStartOpen ")
  if fxList then
    for i = 1, #fxList do
      LD.ShowFX(fxList[i])
    end
  end
  if interactDirection == "Forward" then
    LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, onStartSync_SideA, "Interaction Event (Two Way) - LuaHook_OnStartSync_SideA ")
    PlaySoundOnInteract_Forward()
  else
    LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, onStartSync_SideB, "Interaction Event (Two Way) - LuaHook_OnStartSync_SideB ")
    PlaySoundOnInteract_Backward()
  end
  FireOnStartOpenCallbacks()
end
function RegisterOnStartOpenCallback(fn)
  if luaHook_OnStartOpen == nil then
    luaHook_OnStartOpen = {}
  end
  luaHook_OnStartOpen[#luaHook_OnStartOpen + 1] = fn
end
function FireOnStartOpenCallbacks()
  if luaHook_OnStartOpen ~= nil then
    for _, fn in pairs(luaHook_OnStartOpen) do
      fn()
    end
  end
end
function LuaHook_OnStartClose()
  print("CLOSING NOW")
  if interactDirection == "Forward" then
    StopSoundOnInteract_Forward()
  else
    StopSoundOnInteract_Backward()
  end
end
function Enable()
  EnableFront()
  EnableBack()
end
function EnableFront()
  enableFront = true
  interactZone:Enable()
end
function EnableBack()
  enableBack = true
  interactZone_Back:Enable()
end
function Disable()
  DisableFront()
  DisableBack()
end
function DisableFront()
  enableFront = false
  interactZone:Disable()
end
function DisableBack()
  enableBack = false
  interactZone_Back:Disable()
end
function Lock()
  isLocked = true
  interactZone:Lock()
  interactZone_Back:Lock()
end
function Unlock()
  isLocked = false
  interactZone:Unlock()
  interactZone_Back:Unlock()
end
function SetAllowSyncWithSonNotAvailable(value)
  allowSyncWhileSonNotAvailable = value or true
end
function GetInteractDirection()
  return interactDirection
end
function OverideInteractApproachYaw(val)
  bSkipInteractApproachYaw = val
end
function OverrideCameraInteractApproach(newCamera)
  camInteractApproach = newCamera
end
function GetFrontInteractZone()
  return interactZone
end
function GetBackInteractZone()
  return interactZone_Back
end
function OverrideInteractZone(overrideTable)
  LD.OverrideInteractZone(interactZone, overrideTable)
  LD.OverrideInteractZone(interactZone_Back, overrideTable)
end
function EnableCinematicTrigger(disableNavObstable)
  if disableNavObstable then
    thisObj:HideNavObstacle()
  end
  triggerCinematic = true
end
function DisableCinematicTrigger()
  triggerCinematic = false
end
function IncludeSon()
  includeSon = true
end
function ExcludeSon()
  includeSon = false
end
function PlayCurrentAnim(speed)
  local rate = speed or 1
  thisObj:StartAnimation({Animation = objectAnimName, Rate = rate})
end
function ForceDoorOpen(jumpAnimOpen)
  if jumpAnimOpen == true then
    if objectAnimName then
      thisObj:JumpAnimationToPercent(1, {Animation = objectAnimName})
    end
  elseif objectAnimName then
    thisObj:StartAnimation({Animation = objectAnimName})
  end
  if disableObstacleAfterUse then
    thisObj:HideNavObstacle()
  end
  if disableAfterUse then
    Disable()
  end
end
function ForceDoorClose(jumpAnimClose)
  if jumpAnimClose == true then
    thisObj:JumpAnimationToFrame(0, {Animation = objectAnimName})
    thisObj:PauseAnimation()
  elseif objectAnimName ~= "" and objectAnimName ~= nil then
    thisObj:PlayAnimationToFrame(0, {Animation = objectAnimName, Rate = -1})
  end
  thisObj:ShowNavObstacle()
  Enable()
end
local doorSound = {
  UseDirectSoundEmitterOverride = false,
  SoundEmitter_Center = nil,
  SoundEmitter_Left = nil,
  SoundEmitter_Right = nil,
  OnInteractCloseFrameDelay = 100,
  OnInteractForward_Center = "",
  OnInteractForward_Left = "SND_DOOR_Stone_Double_Pry_Apart_Push_Open_L",
  OnInteractForward_Right = "SND_DOOR_Stone_Double_Pry_Apart_Push_Open_R",
  OnInteractBackward_Center = "",
  OnInteractBackward_Left = "SND_DOOR_Stone_Double_Pry_Apart_Push_Open_L",
  OnInteractBackward_Right = "SND_DOOR_Stone_Double_Pry_Apart_Push_Open_R",
  OnInteractClose_Center = "",
  OnInteractClose_Left = "SND_DOOR_Stone_Double_Pry_Apart_Push_Close_L",
  OnInteractClose_Right = "SND_DOOR_Stone_Double_Pry_Apart_Push_Close_R"
}
local hingeSound = {
  hingeLeft = nil,
  hingeRight = nil,
  hingeCenter = nil
}
local genericSound = {
  SoundEmitter = nil,
  OnInteract_Forward = "",
  OnInteract_Backward = ""
}
function SoundInit()
  if interactType == "Door" then
    doorSound.SoundEmitter_Center = thisObj:FindSingleSoundEmitterByName("SNDDoorCenter")
    doorSound.SoundEmitter_Left = thisObj:FindSingleSoundEmitterByName("SNDDoorLeft")
    doorSound.SoundEmitter_Right = thisObj:FindSingleSoundEmitterByName("SNDDoorRight")
  else
    genericSound.SoundEmitter = thisObj:FindSingleSoundEmitterByName("SNDGenericInteract")
  end
end
function SoundSetup(sounds)
  if sounds ~= nil then
    if sounds.SoundEmitter_Center ~= nil then
      if not sounds.UseDirectSoundEmitterOverride then
        doorSound.SoundEmitter_Center = thisObj:FindSingleSoundEmitterByName(sounds.SoundEmitter_Center)
      else
        doorSound.SoundEmitter_Center = sounds.SoundEmitter_Center
      end
    end
    if sounds.SoundEmitter_Left ~= nil then
      if not sounds.UseDirectSoundEmitterOverride then
        doorSound.SoundEmitter_Left = thisObj:FindSingleSoundEmitterByName(sounds.SoundEmitter_Left)
      else
        doorSound.SoundEmitter_Left = sounds.SoundEmitter_Left
      end
    end
    if sounds.SoundEmitter_Right ~= nil then
      if not sounds.UseDirectSoundEmitterOverride then
        doorSound.SoundEmitter_Right = thisObj:FindSingleSoundEmitterByName(sounds.SoundEmitter_Right)
      else
        doorSound.SoundEmitter_Right = sounds.SoundEmitter_Right
      end
    end
    OverrideVariables(doorSound, sounds)
  end
end
function OverrideVariables(baseTable, newTable)
  for key, value in pairs(baseTable) do
    LD.SoundDebug("BEFORE: " .. tostring(key) .. ": " .. tostring(baseTable[key]))
    for newKey, newValue in pairs(newTable) do
      if newKey == key and newValue ~= nil and newValue ~= value then
        baseTable[key] = newValue
      end
    end
    LD.SoundDebug(tostring(key) .. ": " .. tostring(baseTable[key]))
  end
end
function PlaySoundOnInteract_Forward()
  if interactType == "Door" and doorName ~= "Guardian Door" then
    if player:IsPlayingMove("MOV_DoorSpreadFreyaOpen") then
      LD.PlaySoundOnFrame(doorSound.SoundEmitter_Left, thisObj, "SND_DOOR_Freya_Double_Spread_Close_L", 372, "forward")
      LD.PlaySoundOnFrame(doorSound.SoundEmitter_Right, thisObj, "SND_DOOR_Freya_Double_Spread_Close_R", 372, "forward")
    else
      LD.PlaySound(doorSound.SoundEmitter_Center, doorSound.OnInteractForward_Center)
      LD.PlaySound(doorSound.SoundEmitter_Left, doorSound.OnInteractForward_Left)
      LD.PlaySound(doorSound.SoundEmitter_Right, doorSound.OnInteractForward_Right)
      LD.PlaySoundOnFrame(doorSound.SoundEmitter_Center, thisObj, doorSound.OnInteractClose_Center, doorSound.OnInteractCloseFrameDelay, "forward")
      LD.PlaySoundOnFrame(doorSound.SoundEmitter_Left, thisObj, doorSound.OnInteractClose_Left, doorSound.OnInteractCloseFrameDelay, "forward")
      LD.PlaySoundOnFrame(doorSound.SoundEmitter_Right, thisObj, doorSound.OnInteractClose_Right, doorSound.OnInteractCloseFrameDelay, "forward")
    end
  else
    LD.PlaySound(genericSound.SoundEmitter, genericSound.OnInteract_Forward)
  end
end
function PlaySoundOnInteract_Backward()
  if interactType == "Door" and doorName ~= "Guardian Door" then
    if player:IsPlayingMove("MOV_DoorSpreadFreyaOpen") then
      LD.PlaySoundOnFrame(doorSound.SoundEmitter_Left, thisObj, "SND_DOOR_Freya_Double_Spread_Close_L", 372, "forward")
      LD.PlaySoundOnFrame(doorSound.SoundEmitter_Right, thisObj, "SND_DOOR_Freya_Double_Spread_Close_R", 372, "forward")
    else
      LD.PlaySound(doorSound.SoundEmitter_Center, doorSound.OnInteractForward_Center)
      LD.PlaySound(doorSound.SoundEmitter_Left, doorSound.OnInteractForward_Left)
      LD.PlaySound(doorSound.SoundEmitter_Right, doorSound.OnInteractForward_Right)
      LD.PlaySoundOnFrame(doorSound.SoundEmitter_Center, thisObj, doorSound.OnInteractClose_Center, doorSound.OnInteractCloseFrameDelay, "forward")
      LD.PlaySoundOnFrame(doorSound.SoundEmitter_Left, thisObj, doorSound.OnInteractClose_Left, doorSound.OnInteractCloseFrameDelay, "forward")
      LD.PlaySoundOnFrame(doorSound.SoundEmitter_Right, thisObj, doorSound.OnInteractClose_Right, doorSound.OnInteractCloseFrameDelay, "forward")
    end
  else
    LD.PlaySound(genericSound.SoundEmitter, genericSound.OnInteract_Backward)
  end
end
function StopSoundOnInteract_Forward()
  LD.StopSound(doorSound.SoundEmitter_Center, doorSound.OnInteractForward_Center)
end
function StopSoundOnInteract_Backward()
  LD.StopSound(doorSound.SoundEmitter_Center, doorSound.OnInteractBackward_Center)
end
function PlaySoundGuardianDoor_Forward()
  if interactType == "Door" and doorName == "Guardian Door" then
    LD.PlaySoundOnFrame(doorSound.SoundEmitter_Left, thisObj, onInteractLeft, 1)
    LD.PlaySoundOnFrame(doorSound.SoundEmitter_Right, thisObj, onInteractRight, 1)
    LD.PlaySoundOnFrame(doorSound.SoundEmitter_Left, thisObj, onDoorShutLeft, 1)
    LD.PlaySoundOnFrame(doorSound.SoundEmitter_Right, thisObj, onDoorShutRight, 1)
  end
end
function PlaySoundGuardianDoor_Backward()
  if interactType == "Door" and doorName == "Guardian Door" then
    LD.PlaySoundOnFrame(doorSound.SoundEmitter_Left, thisObj, "SND_DOOR_Hel_Guardian_Open_L_02", 1)
    LD.PlaySoundOnFrame(doorSound.SoundEmitter_Right, thisObj, "SND_DOOR_Hel_Guardian_Open_R_02", 1)
    LD.PlaySoundOnFrame(doorSound.SoundEmitter_Left, thisObj, "SND_DOOR_Hel_Guardian_Shut_L_02", 1)
    LD.PlaySoundOnFrame(doorSound.SoundEmitter_Right, thisObj, "SND_DOOR_Hel_Guardian_Shut_R_02", 1)
  end
end
function LuaHook_Sound_FreyaSpreadDoor_InitialOpen()
  LD.PlaySound(doorSound.SoundEmitter_Left, "SND_DOOR_Freya_Double_Spread_Open_Partial_L")
  LD.PlaySound(doorSound.SoundEmitter_Right, "SND_DOOR_Freya_Double_Spread_Open_Partial_R")
end
function LuaHook_Sound_FreyaSpreadDoor_OpenDoorSideOne()
  if approachDirection == "Front" then
    LD.PlaySound(doorSound.SoundEmitter_Right, "SND_DOOR_Freya_Double_Spread_Left_Open_R")
  else
    LD.PlaySound(doorSound.SoundEmitter_Left, "SND_DOOR_Freya_Double_Spread_Left_Open_L")
  end
end
function LuaHook_Sound_FreyaSpreadDoor_Shift()
  if approachDirection == "Front" then
    LD.PlaySound(doorSound.SoundEmitter_Left, "SND_DOOR_Freya_Double_Spread_ShiftToOpen_L")
  else
    LD.PlaySound(doorSound.SoundEmitter_Right, "SND_DOOR_Freya_Double_Spread_ShiftToOpen_R")
  end
end
function LuaHook_Sound_FreyaSpreadDoor_OpenDoorSideTwo()
  if approachDirection == "Front" then
    LD.PlaySound(doorSound.SoundEmitter_Left, "SND_DOOR_Freya_Double_Spread_Left_Open_L")
  else
    LD.PlaySound(doorSound.SoundEmitter_Right, "SND_DOOR_Freya_Double_Spread_Left_Open_R")
  end
end
function DebugDrawDistance()
  local indexfront = thisObj:GetJointIndex(synchJointName)
  local indexback = thisObj:GetJointIndex(synchJointName_Back)
  local syncFrontPos = thisObj:GetWorldJointPosition(indexfront)
  local syncBackPos = thisObj:GetWorldJointPosition(indexback)
  local text = ""
  local textPos = thisObj:GetWorldJointPosition(thisObj:GetJointIndex(promptJointName))
  local jointNames = {
    synchJointName,
    synchJointName_Back,
    promptJointName,
    promptJointName_Back
  }
  for i = 1, #jointNames do
    local jointIndex = thisObj:GetJointIndex(jointNames[i])
    local jointPos = thisObj:GetWorldJointPosition(jointIndex)
    jointPos.y = player:GetWorldPosition().y
    jointPos.z = player:GetWorldPosition().z
    local distance = player:GetWorldPosition():Distance(jointPos)
    if distance < 3 then
      text = text .. "\n" .. jointNames[i] .. ": " .. distance
    end
  end
  local color = require("core.color")
  engine.DrawTextInWorld(textPos, text, color.white)
end
function DebugDrawJoints(inputname, duration)
  local color = require("core.color")
  local frontColor = color.fuchsia
  local backColor = color.green
  local approachColor = color.red
  if duration == nil or duration < 1 then
    duration = 60
  end
  local templateObj = thisObj.Child
  local indexfront = thisObj:GetJointIndex(synchJointName)
  local indexback = thisObj:GetJointIndex(synchJointName_Back)
  local syncFrontPos = thisObj:GetWorldJointPosition(indexfront)
  local syncBackPos = thisObj:GetWorldJointPosition(indexback)
  local approachIndex = thisObj:FindJointIndex("approachJoint")
  if approachIndex ~= nil then
    local approachPos = thisObj:GetWorldJointPosition(approachIndex)
    engine.DrawTextInWorld(approachPos, "approachJoint", approachColor)
    engine.DrawPoint(approachPos, approachColor, duration)
  end
  engine.DrawTextInWorld(syncFrontPos, synchJointName, frontColor)
  engine.DrawPoint(syncFrontPos, frontColor, duration)
  if syncFrontPos ~= syncBackPos then
    engine.DrawTextInWorld(syncBackPos, synchJointName_Back, backColor)
    engine.DrawPoint(syncBackPos, backColor, duration)
  end
end
function ShowDebugTable(X, Y, Title)
  local debugTable = {}
  debugTable.Title = Title or thisObj:GetName() .. " Interact2way "
  debugTable.X = X or 10
  debugTable.Y = Y or 10
  debugTable.TitleColor = engine.Vector.New(20, 0, 128)
  engine.DrawDebugTable(debugTable)
end
function ShowDebugText()
  local color = require("core.color")
  local debugTable = " "
  debugTable = "\n" .. debugTable .. " triggerCinematic = " .. tostring(triggerCinematic)
  engine.DrawTextInWorld(thisObj:GetWorldPosition() + engine.Vector.New(0, 1, 0), debugTable, color.white)
end
