local LD = require("design.LevelDesignLibrary")
local CCOS, timers, player, thisObj, interactZone, startEnabled, disableAfterUse, disableObstacleAfterUse
local includeSon = true
local isLocked
local allowSyncWhileSonNotAvailable = false
local heroBranch, sonBranch, objectAnimName, promptJointName, synchJointName, interactionTriggers
local triggerCinematic = false
local cineInteractionTriggers, interactionOnStart, interactionOnFinish, luaOnStartEvents, luaHook_OnStartOpen, additionalSlaves, additionalAnims, onStartDoor, customEvent, interactType, subType, weaponMode, completionPercentage
local availabilitySet = false
local disabledCombat = false
local camInteractApproach = "ENV_InteractApproach"
local camera_InteractApproach, InteractApproachYaw
local bSkipInteractApproachYaw = false
local cameraSubmitDuration = 3
local cameraWorldSpaceForward = engine.Vector.New(0, 0, 1)
local playFXOnMoveStart = false
local fxList = {}
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
  interactType = externalTable:FindLuaTableAttribute("interactType") or obj:FindLuaTableAttribute("interactType")
  subType = externalTable:FindLuaTableAttribute("subType")
  startEnabled = externalTable:FindLuaTableAttribute("startEnabled") or obj:FindLuaTableAttribute("startEnabled")
  disableAfterUse = externalTable:FindLuaTableAttribute("disableAfterUse") or obj:FindLuaTableAttribute("disableAfterUse")
  disableObstacleAfterUse = externalTable:FindLuaTableAttribute("disableObstacleAfterUse") or obj:FindLuaTableAttribute("disableObstacleAfterUse")
  sonBranch = externalTable:FindLuaTableAttribute("sonBranch")
  heroBranch = externalTable:FindLuaTableAttribute("heroBranch")
  objectAnimName = externalTable:FindLuaTableAttribute("objectAnimName")
  synchJointName = externalTable:FindLuaTableAttribute("synchJointName")
  weaponMode = externalTable:FindLuaTableAttribute("weaponMode") or "Bare"
  completionPercentage = externalTable:FindLuaTableAttribute("completionPercentage") or 0
  promptJointName = externalTable:FindLuaTableAttribute("promptJointName")
  includeSon = obj:FindLuaTableAttribute("includeSon")
  cineInteractionTriggers = obj:FindLuaTableAttribute("cineInteractionTriggers")
  interactionTriggers = obj:FindLuaTableAttribute("interactionTriggers")
  interactionOnStart = obj:FindLuaTableAttribute("interactionOnStart")
  interactionOnFinish = obj:FindLuaTableAttribute("interactionOnFinish")
  onStartDoor = externalTable:FindLuaTableAttribute("onDoorStartOpen") or obj:FindLuaTableAttribute("onDoorStartOpen") or obj:FindLuaTableAttribute("OnStartSync")
  customEvent = obj:FindLuaTableAttribute("customEvent")
  playFXOnMoveStart = externalTable:FindLuaTableAttribute("PlayFXOnMOV") or obj:FindLuaTableAttribute("PlayFXOnMOV")
  if includeSon == nil then
    includeSon = true
  end
  triggerCinematic = false
  if interactionTriggers ~= nil then
    interactionTriggers = LD.ExtractCallbacksForEvent(level, obj, interactionTriggers)
  end
  if interactionOnStart ~= nil then
    interactionOnStart = LD.ExtractCallbacksForEvent(level, obj, interactionOnStart)
  end
  if interactionOnFinish ~= nil then
    interactionOnFinish = LD.ExtractCallbacksForEvent(level, obj, interactionOnFinish)
  end
  if cineInteractionTriggers then
    cineInteractionTriggers = LD.ExtractCallbacksForEvent(level, obj, cineInteractionTriggers)
  end
  if onStartDoor then
    onStartDoor = LD.ExtractCallbacksForEvent(level, obj, onStartDoor)
  end
  if customEvent and customEvent ~= "" then
    customEvent = LD.ExtractCallbacksForEvent(level, obj, customEvent)
  end
  interactZone = LD.CreateInteractZone_Standard_180(obj, promptJointName)
  interactZone:SetHintAngle(180)
  interactZone:SetRequiresSonUnoccupied(true)
  if interactType == "Gate" then
    LD.EnableInteractZoneGlint(interactZone)
    interactZone:SetOnScreenPercentWeight(0)
    interactZone:SetCameraFrontAngleWeight(1)
    interactZone:SetCameraFrontAngle(180)
    interactZone:SetGlintAngle(180)
    interactZone:SetPromptIconSet({
      normal = "WORLD_INTERACT_ALLOW_OFFSCREEN",
      unavailable = "WORLD_INTERACT_UNAVAILABLE",
      locked = "WORLD_INTERACT_LOCKED",
      hint = "WORLD_INTERACT_HINT"
    })
  end
  if (subType == "Debris" or subType == "Spread") and completionPercentage == 0 then
    completionPercentage = 0.75
  end
  SoundInit()
  game.SubObject.Sleep(thisObj)
end
function OnStart(level, obj)
  if not startEnabled then
    Disable()
  else
    Enable()
  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
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 then
      LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, cineInteractionTriggers, "Cine Interaction Event")
    end
    if triggerCinematic == false then
      player:RequestInteract(obj, interactZone)
    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 OnInteractStart(level, obj)
  local son = game.AI.FindSon()
  if son ~= nil then
    son:AddMarker("SonInDoor")
  end
  game.SubObject.Wake(thisObj)
  if interactionOnStart ~= nil then
    LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, interactionOnStart, "Interaction Event Start")
  end
  FireOnInteractStartCallbacks()
  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
  PerformInteraction()
  thisObj:HideNavObstacle()
  local playerPosition = player:GetWorldPosition()
  local interactPosition = obj:GetWorldPosition()
  local toInteract = interactPosition - playerPosition
  local interactFwd = obj:GetWorldForward()
  local interactAngle
  if toInteract:Dot(interactFwd) > 0 then
    print("Approaching from front")
    interactAngle = LD.GetAngleBetweenVector(-interactFwd, cameraWorldSpaceForward)
  else
    print("Approaching from behind")
    interactAngle = LD.GetAngleBetweenVector(interactFwd, cameraWorldSpaceForward)
  end
  if not bSkipInteractApproachYaw then
    InteractApproachYaw = {Yaw = interactAngle, Pitch = 4}
  end
  game.Camera.Recenter({
    TimeStart = 0,
    TimeDuration = cameraSubmitDuration,
    LockRecenter = 1,
    YawRange = -1,
    TriggerLeft = 0,
    TriggerRight = 0,
    ReturnLeft = 180,
    ReturnRight = -180,
    PitchRange = 0
  })
  CCOS_LoadLibrary()
  camera_InteractApproach = CCOS.OneShotCamera.New(camInteractApproach, cameraSubmitDuration, InteractApproachYaw)
  camera_InteractApproach:Start()
end
function OnInteractAbort()
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()
  if interactionTriggers ~= nil then
    LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, interactionTriggers, "Interaction Event (One Way)")
  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())
    else
      LD.PlaySingleSynchMove_KratosSonObject(thisObj, synchJointName, "Interactive1WayWithSon_" .. thisObj:GetName(), heroBranch, sonBranch, objectAnimName, interactZone, disableAfterUse, true, weaponMode, {completion_percentage = completionPercentage})
    end
  elseif heroBranch == "" then
    engine.Warning("Missing Branch, 'heroBranch' in Lua Toolbox on object: " .. thisObj:GetName())
  else
    local synchObj = thisObj
    local anims = objectAnimName
    if additionalSlaves ~= nil and additionalAnims ~= nil then
      synchObj = additionalSlaves
      anims = additionalAnims
    elseif additionalSlaves ~= nil then
      synchObj = additionalSlaves
    end
    LD.PlaySingleSynchMove_KratosObject(synchObj, synchJointName, "Interactive1Way_" .. thisObj:GetName(), heroBranch, anims, interactZone, disableAfterUse, weaponMode, {completion_percentage = completionPercentage})
  end
end
function LuaHook_OnStartOpen()
  if fxList then
    for i = 1, #fxList do
      LD.ShowFX(fxList[i])
    end
  end
  PlaySoundOnInteract_Forward()
  LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, onStartDoor, "Interaction Event (One Way) - LuaHook_OnStartOpen ")
  FireOnStartOpenCallbacks()
end
function LuaHook_CustomEvent()
  LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, customEvent, "Interaction Event (One Way) - LuaHook_CustomEvent ")
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 Enable()
  startEnabled = true
  interactZone:Enable()
end
function Disable()
  startEnabled = false
  interactZone:Disable()
end
function Lock()
  isLocked = true
  interactZone:Lock()
end
function Unlock()
  isLocked = false
  interactZone:Unlock()
end
function OverideInteractApproachYaw(val)
  bSkipInteractApproachYaw = val
end
function OverrideCameraInteractApproach(newCamera)
  camInteractApproach = newCamera
end
function OverrideCameraSubmissionTime(newTime)
  cameraSubmitDuration = newTime
end
function GetInteractZone()
  return interactZone
end
function OverrideInteractZone(overrideTable)
  LD.OverrideInteractZone(interactZone, overrideTable)
end
function EnableCinematicTrigger()
  triggerCinematic = true
end
function DisableCinematicTrigger()
  triggerCinematic = false
end
function IncludeSon()
  includeSon = true
end
function ExcludeSon()
  includeSon = false
end
function OverrideBranches(newBranch)
  heroBranch = newBranch
  sonBranch = newBranch
end
function SetAllowSyncWithSonNotAvailable(value)
  allowSyncWhileSonNotAvailable = value or true
end
function AddSlavesToSynch(slaveAnimTable)
  local uniqueAnims = false
  for _, slaveAT in pairs(slaveAnimTable) do
    if slaveAT.Anim ~= nil then
      uniqueAnims = true
      break
    end
  end
  if additionalSlaves == nil then
    additionalSlaves = {thisObj}
    if uniqueAnims then
      additionalAnims = {objectAnimName}
    end
  else
    engine.Warning("Trying to add additional slaves to the synch more than once. This isn't allowed. Please add all slaves at once")
  end
  for _, slaveAT in pairs(slaveAnimTable) do
    additionalSlaves[#additionalSlaves + 1] = slaveAT.Slave
    if uniqueAnims then
      additionalAnims[#additionalAnims + 1] = slaveAT.Anim
    end
  end
end
function PlayCurrentAnim(speed)
  local rate = speed or 1
  thisObj:PlayAnimationToEnd({Animation = objectAnimName, Rate = rate})
  if speed and 0 < speed then
    timers_LoadLibrary()
    timers.StartLevelTimer(2, OnInteractFinish)
  end
end
function ForceDoorOpen(jumpAnimOpen, speed)
  local animSpeed = speed or 1
  if jumpAnimOpen == true then
    if objectAnimName then
      thisObj:JumpAnimationToPercent(1, {Animation = objectAnimName})
    end
  else
    if objectAnimName then
      thisObj:StartAnimation({Animation = objectAnimName, Rate = animSpeed})
    else
      thisObj:PlayAnimationToEnd({Rate = animSpeed})
    end
    PlaySoundOnForceOpen()
  end
  if disableObstacleAfterUse then
    thisObj:HideNavObstacle()
  end
  if disableAfterUse then
    Disable()
  end
end
function ForceDoorClose(jumpAnimClose, speed)
  local animSpeed = (speed or 1) * -1
  if jumpAnimClose == true then
    thisObj:JumpAnimationToFrame(0, {Animation = objectAnimName})
    thisObj:PauseAnimation()
  else
    if objectAnimName then
      thisObj:StartAnimation({Animation = objectAnimName, Rate = animSpeed})
    else
      thisObj:PlayAnimationToFrame(0, {Rate = animSpeed})
    end
    PlaySoundOnForceClose()
  end
  thisObj:ShowNavObstacle()
  Enable()
end
local doorSound = {
  SoundEmitter_Center = nil,
  SoundEmitter_Left = nil,
  SoundEmitter_Right = nil,
  OnInteractForward_Center = "",
  OnInteractForward_Left = "SND_DOOR_Stone_Double_Pry_Apart_Push_L",
  OnInteractForward_Right = "SND_DOOR_Stone_Double_Pry_Apart_Push_R"
}
local genericSound = {SoundEmitter = nil, OnInteract_Forward = ""}
local gateSound = {
  SoundEmitter = nil,
  OnInteract_Start = "SND_DOOR_Gate_Metal_Lift_Start",
  OnInteract_Scrape_LP = "SND_DOOR_Gate_Metal_Lift_Scrape_LP",
  OnInteract_Gears_LP = "",
  OnInteract_Push = "",
  OnForceOpen_Start = "",
  OnForceOpen_StartFrame = -1,
  OnForceOpen_Stop = "",
  OnForceOpen_StopFrame = -1,
  OnForceOpen_LP = "",
  OnForceClose_Start = "",
  OnForceClose_StartFrame = -1,
  OnForceClose_Stop = "",
  OnForceClose_StopFrame = -1,
  OnForceClose_LP = ""
}
function SoundInit()
  if interactType == "Door" then
    doorSound.SoundEmitter_Center = thisObj:FindSingleSoundEmitterByName("SNDDoorCenter")
    doorSound.SoundEmitter_Left = thisObj:FindSingleSoundEmitterByName("SNDDoorLeft")
    doorSound.SoundEmitter_Right = thisObj:FindSingleSoundEmitterByName("SNDDoorRight")
  elseif interactType == "Gate" then
    gateSound.SoundEmitter = thisObj:FindSingleSoundEmitterByName("SNDLiftGate")
  else
    genericSound.SoundEmitter = thisObj:FindSingleSoundEmitterByName("SNDGenericInteract")
  end
end
function SoundSetup(sounds)
  if sounds ~= nil then
    if interactType == "Door" then
      if sounds.SoundEmitter_Center ~= nil then
        doorSound.SoundEmitter_Center = thisObj:FindSingleSoundEmitterByName(sounds.SoundEmitter_Center)
      end
      if sounds.SoundEmitter_Left ~= nil then
        doorSound.SoundEmitter_Left = thisObj:FindSingleSoundEmitterByName(sounds.SoundEmitter_Left)
      end
      if sounds.SoundEmitter_Right ~= nil then
        doorSound.SoundEmitter_Right = thisObj:FindSingleSoundEmitterByName(sounds.SoundEmitter_Right)
      end
      OverrideVariables(doorSound, sounds)
    elseif interactType == "Gate" then
      if sounds.SoundEmitter ~= nil then
        gateSound.SoundEmitter = thisObj:FindSingleSoundEmitterByName(sounds.SoundEmitter)
      end
      OverrideVariables(gateSound, sounds)
    else
      if sounds.SoundEmitter ~= nil then
        genericSound.SoundEmitter = thisObj:FindSingleSoundEmitterByName(sounds.SoundEmitter)
      end
      OverrideVariables(genericSound, sounds)
    end
  end
end
function OverrideVariables(baseTable, newTable)
  for newKey, newValue in pairs(newTable) do
    for baseKey in pairs(baseTable) do
      if newKey == baseKey and newValue ~= nil then
        baseTable[baseKey] = newValue
      end
    end
  end
end
function PlaySoundOnInteract_Forward()
  if interactType == "Door" then
    LD.PlaySound(doorSound.SoundEmitter_Center, doorSound.OnInteractForward_Center)
    LD.PlaySound(doorSound.SoundEmitter_Left, doorSound.OnInteractForward_Left)
    LD.PlaySound(doorSound.SoundEmitter_Right, doorSound.OnInteractForward_Right)
  elseif interactType == "Gate" then
    LD.PlaySound(gateSound.SoundEmitter, gateSound.OnInteract_Start)
    LD.PlaySound(gateSound.SoundEmitter, gateSound.OnInteract_Scrape_LP)
    LD.PlaySound(gateSound.SoundEmitter, gateSound.OnInteract_Gears_LP)
    LD.PlaySoundOnFrame(gateSound.SoundEmitter, thisObj, gateSound.OnInteract_Push, 70)
    LD.StopSoundOnFrame(gateSound.SoundEmitter, thisObj, gateSound.OnInteract_Gears_LP, thisObj.AnimLengthFrames)
    LD.StopSoundOnFrame(gateSound.SoundEmitter, thisObj, gateSound.OnInteract_Scrape_LP, thisObj.AnimLengthFrames)
  else
    LD.PlaySound(genericSound.SoundEmitter, genericSound.OnInteract_Forward)
  end
end
function PlaySoundOnForceOpen()
  if interactType == "Gate" then
    if gateSound.OnForceOpen_StartFrame > -1 then
      LD.PlaySoundOnFrame(gateSound.SoundEmitter, thisObj, gateSound.OnForceOpen_Start, gateSound.OnForceOpen_StartFrame, "forward")
      LD.PlaySoundOnFrame(gateSound.SoundEmitter, thisObj, gateSound.OnForceOpen_LP, gateSound.OnForceOpen_StartFrame, "forward")
    else
      LD.PlaySound(gateSound.SoundEmitter, gateSound.OnForceOpen_Start)
      LD.PlaySound(gateSound.SoundEmitter, gateSound.OnForceOpen_LP)
    end
    if -1 < gateSound.OnForceOpen_StopFrame then
      LD.PlaySoundOnFrame(gateSound.SoundEmitter, thisObj, gateSound.OnForceOpen_Stop, gateSound.OnForceOpen_StopFrame, "forward")
      LD.StopSoundOnFrame(gateSound.SoundEmitter, thisObj, gateSound.OnForceOpen_LP, gateSound.OnForceOpen_StopFrame, "forward")
    else
      LD.PlaySoundOnFrame(gateSound.SoundEmitter, thisObj, gateSound.OnForceOpen_Stop, thisObj.AnimLengthFrames - 1, "forward")
      LD.StopSoundOnFrame(gateSound.SoundEmitter, thisObj, gateSound.OnForceOpen_LP, thisObj.AnimLengthFrames - 1, "forward")
    end
  end
end
function PlaySoundOnForceClose()
  if interactType == "Gate" then
    if gateSound.OnForceClose_StartFrame > -1 then
      LD.PlaySoundOnFrame(gateSound.SoundEmitter, thisObj, gateSound.OnForceClose_Start, gateSound.OnForceClose_StartFrame, "backward")
      LD.PlaySoundOnFrame(gateSound.SoundEmitter, thisObj, gateSound.OnForceClose_LP, gateSound.OnForceClose_StartFrame, "backward")
    else
      LD.PlaySound(gateSound.SoundEmitter, gateSound.OnForceClose_Start)
      LD.PlaySound(gateSound.SoundEmitter, gateSound.OnForceClose_LP)
    end
    if -1 < gateSound.OnForceClose_StopFrame then
      LD.PlaySoundOnFrame(gateSound.SoundEmitter, thisObj, gateSound.OnForceClose_Stop, gateSound.OnForceClose_StopFrame, "backward")
      LD.StopSoundOnFrame(gateSound.SoundEmitter, thisObj, gateSound.OnForceClose_LP, gateSound.OnForceClose_StopFrame, "backward")
    else
      LD.PlaySoundOnFrame(gateSound.SoundEmitter, thisObj, gateSound.OnForceClose_Stop, 1, "backward")
      LD.StopSoundOnFrame(gateSound.SoundEmitter, thisObj, gateSound.OnForceClose_LP, 1, "backward")
    end
  end
end
function ShowDebugTable(x, y)
  local debugTable = {}
  debugTable.Title = "Door Anim Info"
  debugTable.X = x or 10
  debugTable.Y = y or 10
  debugTable.TitleColor = engine.Vector.New(255, 0, 128)
  debugTable[#debugTable + 1] = {
    "AnimFrame: ",
    thisObj.AnimFrame
  }
  engine.DrawDebugTable(debugTable)
end
function DebugDrawJoints(inputname, duration)
  local color = require("core.color")
  local frontColor = color.fuchsia
  local approachColor = color.red
  if duration == nil or duration < 1 then
    duration = 60
  end
  local templateObj = thisObj.Child
  local indexfront = thisObj:GetJointIndex(synchJointName)
  local syncFrontPos = thisObj:GetWorldJointPosition(indexfront)
  engine.DrawTextInWorld(syncFrontPos, synchJointName, frontColor)
  engine.DrawPoint(syncFrontPos, frontColor, duration)
end
function DebugDrawJointName(jointName, duration)
  local color = require("core.color")
  local frontColor = color.fuchsia
  local approachColor = color.red
  if duration == nil or duration < 1 then
    duration = 60
  end
  local templateObj = thisObj.Child
  local indexfront = thisObj:GetJointIndex(jointName)
  local syncFrontPos = thisObj:GetWorldJointPosition(indexfront)
  engine.DrawTextInWorld(syncFrontPos, jointName, frontColor)
  engine.DrawPoint(syncFrontPos, frontColor, duration)
end
function OnSaveCheckpoint(level, obj)
  return {
    enabled = interactZone:IsEnabled(),
    isLocked = isLocked
  }
end
function OnRestoreCheckpoint(level, obj, savedInfo)
  startEnabled = savedInfo.enabled
  isLocked = savedInfo.isLocked
end
