local LD = require("design.LevelDesignLibrary")
local monitors
disableCooldownTimer = 0
local thisObj, thisLevel, son, player, sonPuppeteer, delayTimer, sonInteractZone, interactBranch, interactBranchOverride, activateMonitor, deactivateMonitor
local forceSaveEnabled = false
local interactCompleteEvents, interactInterruptedEvents, interactAbortedEvents, onStartEvents, onEngagedEvents, Event_sonInteractBanterDone, Event_sonApproachBanterDone, startEnabled, interactTrigger, startEventsDelay, fxObject, sonInteractBanter, ActivateRange, ActivateZone, deactivateZone, syncInteractBranch, enableDelay
local breakOutDistance = -1
local ArrivalDistance, syncSlaveObject, syncSlaveObjectAnim, sonApproachBanter, sonSuccessBanter, sonCantInteractBanter, enableLOS, approachIgnoreNavmesh
local requiresSonUnoccupied = true
local sonInteractBanter_isCritical, sonApproachBanter_isCritical, sonSuccessBanter_isCritical, sonCantInteractBanter_isCritical, journal, overrideKratosRange, maxHeightRange
local breakOutAllowed = false
local breakOutTriggered = false
local IsSonOccupied
local STATE_INIT, STATE_WAIT_FOR_SON, STATE_NOT_ALLOW = 0, 1, 2
local STATE_SHOW_ICON, STATE_SON_USE_OTHER_POI, STATE_DISABLED, STATE_AUTO = 3, 4, 5, 6
local STATE_DONE, STATE_START_DISABLED, STATE_ENABLE_DELAY, STATE_BREAKOUT = 7, 8, 9, 10
local objectInteractState
local monitors_LoadLibrary = function()
  if monitors == nil then
    monitors = require("level.MonitorLibrary")
  end
end
function GetInteractZone()
  return sonInteractZone
end
function OverrideInteractZone(newInteract)
  assert(newInteract ~= nil, "Passed a nil reference for interactZone override")
  sonInteractZone:Destroy()
  sonInteractZone = newInteract
  sonInteractZone:SetEnableLineOfSightTest(enableLOS)
  sonInteractZone:SetXZRange(overrideKratosRange)
  sonInteractZone:SetYRange(maxHeightRange)
  sonInteractZone:SetHintXZRange(overrideKratosRange + 8)
  sonInteractZone:SetAngle(360)
  sonInteractZone:SetOnScreenPercent(1, 0.25)
  sonInteractZone:SetRequiresSonUnoccupied(requiresSonUnoccupied)
  sonInteractZone:SetTags("NotAllowedOnBoat")
  sonInteractZone:SetTags("NotInCombat")
  sonInteractZone:Disable()
end
local EventApproachBanterDone = function()
  if thisObj:FindLuaTableAttribute("Event_sonApproachBanterDone") ~= "" then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, Event_sonApproachBanterDone, "Son Banter Approach Done")
  end
end
local EventInteractBanterDone = function()
  if thisObj:FindLuaTableAttribute("Event_sonInteractBanterDone") ~= "" then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, Event_sonInteractBanterDone, "Son Banter Interact Done")
  end
end
local CreateSonInteractZone = function(obj)
  sonInteractZone = game.InteractZone.New(obj, "promptJoint_Son")
  LD.CreateSonInteractZone(sonInteractZone)
  sonInteractZone:SetEnableLineOfSightTest(enableLOS)
  sonInteractZone:SetXZRange(overrideKratosRange)
  sonInteractZone:SetYRange(maxHeightRange)
  sonInteractZone:SetHintXZRange(overrideKratosRange + 8)
  sonInteractZone:SetAngle(360)
  sonInteractZone:SetOnScreenPercent(1, 0.25)
  sonInteractZone:SetRequiresSonUnoccupied(requiresSonUnoccupied)
  sonInteractZone:SetTags("NotAllowedOnBoat")
  sonInteractZone:SetTags("NotInCombat")
  sonInteractZone:Disable()
end
local function GetRefnodeName(obj)
  if obj.LuaObjectScript ~= nil and obj.LuaObjectScript.IsParent ~= nil then
    return obj.Parent
  elseif obj.Parent ~= nil then
    return GetRefnodeName(obj.Parent)
  else
    return obj
  end
end
function OnScriptLoaded(level, obj)
  thisObj = obj
  thisLevel = level
  player = game.Player.FindPlayer()
  son = game.AI.FindSon()
  SoundInit()
  interactTrigger = obj:GetLuaTableAttribute("InteractTrigger")
  startEnabled = obj:GetLuaTableAttribute("StartEnabled")
  interactBranch = obj:GetLuaTableAttribute("sonBranch")
  startEventsDelay = obj:GetLuaTableAttribute("startEventsDelay")
  sonInteractBanter = obj:GetLuaTableAttribute("sonInteractBanter")
  sonApproachBanter = obj:GetLuaTableAttribute("sonApproachBanter")
  sonSuccessBanter = obj:GetLuaTableAttribute("sonSuccessBanter")
  sonCantInteractBanter = obj:GetLuaTableAttribute("sonCantInteractBanter")
  sonInteractBanter_isCritical = obj:GetLuaTableAttribute("sonInteractBanter_isCritical")
  sonApproachBanter_isCritical = obj:GetLuaTableAttribute("sonApproachBanter_isCritical")
  sonSuccessBanter_isCritical = obj:GetLuaTableAttribute("sonSuccessBanter_isCritical")
  sonCantInteractBanter_isCritical = obj:GetLuaTableAttribute("sonCantInteractBanter_isCritical")
  ActivateRange = obj:GetLuaTableAttribute("ActivateRange")
  ActivateZone = obj:GetLuaTableAttribute("ActivateZone")
  deactivateZone = obj:GetLuaTableAttribute("deactivateZone")
  syncInteractBranch = obj:GetLuaTableAttribute("syncInteractBranch")
  enableDelay = obj:GetLuaTableAttribute("enableDelay")
  enableLOS = obj:GetLuaTableAttribute("enableLOS")
  breakOutAllowed = obj:FindLuaTableAttribute("breakOutAllowed") or false
  syncSlaveObject = obj.Parent.Parent
  syncSlaveObjectAnim = obj:FindLuaTableAttribute("syncSlaveObjectAnim")
  maxHeightRange = obj:GetLuaTableAttribute("maxHeightRange")
  approachIgnoreNavmesh = obj:FindLuaTableAttribute("approachIgnoreNavmesh")
  IsSonOccupied = obj:FindLuaTableAttribute("SetSonOccupied")
  if obj:FindLuaTableAttribute("breakOutDistance") ~= nil then
    breakOutDistance = obj:GetLuaTableAttribute("breakOutDistance")
  end
  if obj:FindLuaTableAttribute("ArrivalDistance") then
    ArrivalDistance = obj:FindLuaTableAttribute("ArrivalDistance")
  else
    ArrivalDistance = 0.6
  end
  if obj:FindLuaTableAttribute("Event_InteractComplete") ~= "" then
    interactCompleteEvents = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("Event_InteractComplete"))
  end
  if obj:FindLuaTableAttribute("Event_InteractInterrupted") ~= "" then
    interactInterruptedEvents = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("Event_InteractInterrupted"))
  end
  if obj:FindLuaTableAttribute("Event_InteractAborted") ~= "" then
    interactAbortedEvents = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("Event_InteractAborted"))
  end
  if obj:FindLuaTableAttribute("Event_OnStart") ~= "" then
    onStartEvents = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("Event_OnStart"))
  end
  if obj:FindLuaTableAttribute("Event_OnEngaged") ~= "" then
    onEngagedEvents = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("Event_OnEngaged"))
  end
  if obj:FindLuaTableAttribute("Event_sonInteractBanterDone") ~= "" then
    Event_sonInteractBanterDone = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("Event_sonInteractBanterDone"))
  end
  if obj:FindLuaTableAttribute("Event_sonApproachBanterDone") ~= "" then
    Event_sonApproachBanterDone = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("Event_sonApproachBanterDone"))
  end
  requiresSonUnoccupied = obj:GetLuaTableAttribute("requiresSonUnoccupied")
  if obj:FindLuaTableAttribute("FXObject") ~= nil and obj:FindLuaTableAttribute("FXObject") ~= "" then
    local objName = obj:FindLuaTableAttribute("FXObject")
    local refObj
    if string.find(objName, "_Parent:") ~= nil then
      objName = string.gsub(objName, "_Parent:", "")
      if obj.Parent.IsRefNode then
        refObj = obj.Parent.Parent
      else
        refObj = obj.Parent
      end
      fxObject = refObj:FindSingleGOByName(objName)
    elseif string.find(objName, "_GParent:") ~= nil then
      objName = string.gsub(objName, "_GParent:", "")
      if obj.Parent.Parent.Parent.IsRefNode then
        refObj = obj.Parent.Parent.Parent.Parent
      else
        refObj = obj.Parent.Parent.Parent
      end
      fxObject = refObj:FindSingleGOByName(objName)
    else
      fxObject = level:GetGameObject(objName)
    end
    if fxObject.IsRefNode then
      fxObject = fxObject.Child
    end
    if fxObject and not fxObject.AnimFrame then
      engine.Warning("The fxObject: ", fxObject, " does not seem to have any animation data on it. Please double check that you are referencing the correct object")
    end
  end
  if obj:FindLuaTableAttribute("KratosRange") ~= nil then
    overrideKratosRange = obj:GetLuaTableAttribute("KratosRange")
  end
  if syncInteractBranch == "NA" then
    syncInteractBranch = nil
  end
  objectInteractState = STATE_INIT
  if 0 < ActivateRange then
    game.SubObject.SetUpdateDisableDistance(obj, ActivateRange)
  else
    game.SubObject.SetUpdateDisableDistance(obj, 30)
  end
  CreateSonInteractZone(obj)
end
function OnStart(level, obj)
  if fxObject and fxObject.AnimFrame then
    if objectInteractState ~= STATE_DONE then
      fxObject:JumpAnimToFrame(0)
      fxObject:PauseAnim()
    else
      local animLength = fxObject.AnimLengthFrames
      fxObject:JumpAnimToFrame(animLength)
      fxObject:PlayAnimToEnd()
      HideFxObject()
    end
  end
  journal = thisObj:FindSingleGOByName("interact_journal")
  if journal then
    journal = journal.Child
  end
  LuaHook_HideJournal()
end
function OnSonInteractButtonPressed(level, obj)
  if sonApproachBanter ~= nil and sonApproachBanter ~= "" and interactTrigger ~= "Auto" and not FreyaCheck() and objectInteractState ~= STATE_DONE then
    local sonPosition = son:GetWorldPosition()
    local sonDistanceToInteract = sonPosition:Distance(thisObj:GetWorldPosition())
    game.Audio.SetBanterFact("Son Distance To Interact", sonDistanceToInteract)
  end
  sonInteractZone:Disable()
end
function OnSonInteractButtonPressedWhileOccupied(level, obj)
  if sonCantInteractBanter and sonCantInteractBanter ~= "" then
    PlayBanter(sonCantInteractBanter, sonCantInteractBanter_isCritical)
  end
end
local StartDistance = {StartDistance = 0.1}
function OnUpdate(level, obj)
  if breakOutAllowed == true and son.OwnedPOI and son.OwnedPOI:GetStageName() == "Approach" then
    local playerDistFromInteract = game.AIUtil.Distance(player, thisObj)
    if playerDistFromInteract >= breakOutDistance and breakOutTriggered == false then
      breakOutTriggered = true
      objectInteractState = STATE_BREAKOUT
      TriggerBreakout()
    end
  end
  if son.OwnedPOI and son.OwnedPOI:GetStageName() == "Approach" then
    son:SetActuator(StartDistance)
  end
  if objectInteractState == STATE_INIT then
    if startEnabled then
      if 0 < ActivateRange or ActivateZone ~= nil and ActivateZone ~= "" then
        objectInteractState = STATE_DISABLED
      else
        Enable()
      end
    else
      objectInteractState = STATE_START_DISABLED
    end
  elseif objectInteractState == STATE_AUTO then
    if son.OwnedPOI and not son.OwnedPOI.Type == "ContextAction" then
      objectInteractState = STATE_SON_USE_OTHER_POI
      return
    end
  elseif objectInteractState == STATE_ENABLE_DELAY then
    if 0 < delayTimer then
      delayTimer = delayTimer - level:GetUnitTime()
      if delayTimer <= 0 then
        Enable()
      end
    end
  elseif objectInteractState == STATE_DISABLED then
    if disableCooldownTimer == nil then
      return
    end
    if 0 < disableCooldownTimer then
      disableCooldownTimer = disableCooldownTimer - level:GetUnitTime()
      return
    end
    if ActivateZone ~= nil and ActivateZone ~= "" then
      if activateMonitor == nil then
        local zoneObject = GameObjects[ActivateZone]
        monitors_LoadLibrary()
        activateMonitor = monitors.CreateEntityZoneMonitor(player, zoneObject)
        activateMonitor:OnEnter(Enable)
      end
    elseif 0 < ActivateRange then
      if game.AIUtil.Distance(obj, player) < ActivateRange then
        Enable()
      end
    else
      game.SubObject.Sleep(obj)
    end
  end
end
function SonInCombat()
  if sonPuppeteer ~= nil then
    disableCooldownTimer = 5
    son:AbortInteract()
    game.Interact.EnableTags("NotWhileLoreReadActive")
    if ActivateRange == 0 then
      ActivateRange = breakOutDistance
    end
    Disable()
    if thisObj:FindLuaTableAttribute("Event_InteractInterrupted") ~= "" then
      LD.ExecuteCallbacksForEvent(thisLevel, thisObj, interactInterruptedEvents, "Son Interrupted")
    end
  end
end
function SonEngaged()
  breakOutTriggered = false
  if son:HasMarker("SonInDoor") or player.OnOrPathingToActiveTraversePath and player:OnOrPathingToActiveTraversePath() and son:IsAvailableForSync() and interactTrigger == "ButtonPress" then
    InteractAbort()
    objectInteractState = STATE_INIT
    Enable()
    return
  end
  local interactObj = thisObj
  if thisObj.Parent.Parent ~= nil and thisObj.Parent.Parent.LuaObjectScript ~= nil and thisObj.Parent.Parent.LuaObjectScript.OverrideInteract ~= nil then
    interactObj = thisObj.Parent.Parent.LuaObjectScript.OverrideInteract()
  end
  son:RequestInteract(interactObj)
end
function OnInteractReject(level, obj, creature)
  print("OnInteractReject")
  InteractAbort()
  objectInteractState = STATE_INIT
  Enable()
end
function TriggerOnInteractEngagedCallbacks()
  if thisObj:FindLuaTableAttribute("Event_OnEngaged") ~= "" then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, onEngagedEvents, "Son Just Engaged")
  end
end
function OnInteractStart(level, obj, creature)
  if sonApproachBanter ~= nil and sonApproachBanter ~= "" and interactTrigger ~= "Auto" and not FreyaCheck() then
    PlayBanter(sonApproachBanter, sonApproachBanter_isCritical, EventApproachBanterDone)
  end
  if IsSonOccupied then
    son:SetNewAvailabilityRequest("SonInteraction", {
      AvailableForSync = false,
      AvailableForCombat = false,
      Unoccupied = false
    })
  end
  SonApproach()
  TriggerOnInteractEngagedCallbacks()
end
function SonApproach()
  local speed = 4
  son:ClearFocus()
  son:ClearLocomotionSlowDownBehavior()
  if sonPuppeteer ~= nil then
    sonPuppeteer:Clear()
    sonPuppeteer:SetStageName("Approach")
    sonPuppeteer:OnEvent(SonInCombat, "kEHitReaction")
    sonPuppeteer:OnEvent(SonUnreachable, "kEUnreachable")
    sonPuppeteer:OnEvent(SonUnreachable, "BreakOut")
    if interactTrigger == "IgnoreApproach" then
      local approachParam = {
        pos = son:GetWorldPosition(),
        dir = (thisObj:GetWorldPosition() - son:GetWorldPosition()):Normalized(),
        focus_end_direction = true,
        speed = 0.5,
        close_range_distance = 1,
        complete_radius = 100
      }
      if syncInteractBranch ~= nil then
        interactBranch = syncInteractBranch
      end
      sonPuppeteer:Approach(approachParam)
      sonPuppeteer:OnComplete(OnSonArrived)
    elseif syncInteractBranch ~= nil then
      if interactBranchOverride ~= nil then
        syncInteractBranch = interactBranchOverride
      end
      if syncInteractBranch == "BRA_RunebowlApproach" and 1 > game.AIUtil.Distance(thisObj, son) then
        syncInteractBranch = "BRA_RunebowlStandardOneshot"
      end
      local approachParam = {
        branch_name = syncInteractBranch,
        joint_name = "synchJoint",
        focus_end_direction = true,
        speed = speed,
        stop_distance = ArrivalDistance,
        complete_radius = ArrivalDistance,
        close_range_distance = 1,
        ignore_navmesh = approachIgnoreNavmesh
      }
      sonPuppeteer:Approach(approachParam)
      interactBranch = syncInteractBranch
      sonPuppeteer:OnComplete(OnSonArrived)
    elseif interactBranch ~= nil and interactBranch == "BRA_JournalInteract" then
      game.Interact.DisableTags("NotWhileLoreReadActive")
      local synchJoint = thisObj:GetJointIndex("synchJoint")
      local approachParam = {
        pos = thisObj:GetWorldJointPosition(synchJoint),
        dir = thisObj:GetWorldJointForward(synchJoint),
        focus_end_direction = true,
        speed = speed,
        stop_distance = ArrivalDistance,
        complete_radius = ArrivalDistance,
        close_range_distance = 1,
        ignore_navmesh = approachIgnoreNavmesh
      }
      sonPuppeteer:Approach(approachParam)
      sonPuppeteer:OnComplete(OnSonArrived)
    else
      local approachPos = thisObj:GetWorldJointPosition(thisObj:GetJointIndex("synchJoint"))
      sonPuppeteer:Approach({
        pos = approachPos,
        speed = speed,
        stop = true
      })
      sonPuppeteer:OnArrival(OnSonArrived, {pos = approachPos, radius = ArrivalDistance})
    end
  end
end
function OnInteractAbort(level, obj, creature)
  if IsSonOccupied then
    son:RemoveAvailabilityRequest("SonInteraction")
  end
end
function TriggerOnInteractCompleteCallbacks()
  if thisObj:FindLuaTableAttribute("Event_InteractComplete") ~= "" then
    LD.CallFunctionAfterDelay(function()
      LD.ExecuteCallbacksForEvent(thisLevel, thisObj, interactCompleteEvents, "Interact Complete")
    end, startEventsDelay)
  end
end
function OnInteractFinish(level, obj, creature)
  if objectInteractState == STATE_DONE then
    return
  end
  if objectInteractState == STATE_BREAKOUT then
    objectInteractState = STATE_DISABLED
    Enable()
    return
  end
  TriggerOnInteractCompleteCallbacks()
  if sonPuppeteer ~= nil then
    sonPuppeteer:Clear()
    sonPuppeteer:SetStageName("Complete")
    son:TriggerMoveEvent("kLE_Disengage")
    sonPuppeteer:OnComplete(function()
      sonPuppeteer:DetachPuppet()
      sonPuppeteer = nil
    end)
  end
  if sonSuccessBanter ~= nil and sonSuccessBanter ~= "" then
    PlayBanter(sonSuccessBanter, sonSuccessBanter_isCritical)
  end
  objectInteractState = STATE_DONE
  if deactivateMonitor ~= nil then
    deactivateMonitor:Stop()
    deactivateMonitor = nil
  end
  if IsSonOccupied then
    son:RemoveAvailabilityRequest("SonInteraction")
  end
  game.SubObject.Sleep(obj)
end
function TriggerOnStartCallbacks()
  if thisObj:FindLuaTableAttribute("Event_OnStart") ~= "" then
    LD.CallFunctionAfterDelay(function()
      LD.ExecuteCallbacksForEvent(thisLevel, thisObj, onStartEvents, "Started Interaction")
    end, startEventsDelay)
  end
end
function OnSonArrived()
  son:ClearFocus()
  TriggerOnStartCallbacks()
  if sonPuppeteer ~= nil then
    sonPuppeteer:Clear()
    sonPuppeteer:OnEvent(SonInCombat, "kEHitReaction")
    sonPuppeteer:SetStageName("Interacting")
    if fxObject ~= nil then
      fxObject:PlayAnimToEnd()
      fxObject:OnAnimDone(thisObj, "HideFxObject")
      PlayFXSoundLoop()
    end
    if interactBranch == "BRA_JournalInteract" then
      local journal = thisObj:FindSingleGOByName("interact_journal").Child
      syncSlaveObject = journal
      syncSlaveObjectAnim = "navIdleJournalEnter"
    end
    if interactBranch ~= "" then
      if interactTrigger == "IgnoreApproach" then
        sonPuppeteer:StartBranch(interactBranch)
      elseif syncSlaveObjectAnim ~= "" then
        if IsPropJournalOrTreasureMap() and syncSlaveObject.Parent then
          syncSlaveObject:Unparent()
        end
        sonPuppeteer:Sync(interactBranch, GetShouldSyncToWorldPosition(), {
          {Slave = syncSlaveObject, Anim = syncSlaveObjectAnim}
        }, "synchJoint")
      else
        sonPuppeteer:Sync(interactBranch, true, {}, "synchJoint")
      end
    end
    if sonInteractBanter ~= "" then
      PlayBanter(sonInteractBanter, sonInteractBanter_isCritical, EventInteractBanterDone)
    end
  end
end
function HideFxObject()
  if fxObject then
    LD.HideFX(fxObject)
  end
end
function Enable()
  if objectInteractState == STATE_DISABLED or objectInteractState == STATE_DONE then
    game.SubObject.Wake(thisObj)
  end
  if son == nil then
    objectInteractState = STATE_NOT_ALLOW
    return
  end
  if thisObj.Parent.Parent ~= nil and thisObj.Parent.Parent.LuaObjectScript ~= nil and thisObj.Parent.Parent.LuaObjectScript.OnEnable ~= nil then
    thisObj.Parent.Parent.LuaObjectScript.OnEnable(thisObj, sonPuppeteer)
  end
  if objectInteractState == STATE_NOT_ALLOW then
    return
  end
  if objectInteractState > STATE_INIT and objectInteractState ~= STATE_DISABLED and objectInteractState ~= STATE_DONE and objectInteractState ~= STATE_START_DISABLED and objectInteractState ~= STATE_ENABLE_DELAY then
    return
  end
  if 0 < enableDelay then
    delayTimer = enableDelay
    enableDelay = 0
    objectInteractState = STATE_ENABLE_DELAY
    return
  end
  if activateMonitor ~= nil then
    activateMonitor:Stop()
    activateMonitor = nil
  end
  if sonPuppeteer == nil then
    local obj = GetRefnodeName(thisObj)
    local name = "SonIntearct: "
    if obj ~= nil then
      name = name .. obj:GetName()
    end
    sonPuppeteer = game.Puppeteer.New(thisObj, name, son)
    sonPuppeteer:OnEvent(SonInCombat, "kEHitReaction")
  end
  sonPuppeteer:SetUserFlag("ForcePushAway")
  sonPuppeteer:SetStageName("WaitingForSonInput")
  if interactTrigger == "ButtonPress" or interactTrigger == "IgnoreApproach" then
    sonPuppeteer:Advertise("INTERACT_SON")
    sonInteractZone:Enable()
    objectInteractState = STATE_WAIT_FOR_SON
  elseif interactTrigger == "Auto" then
    sonPuppeteer:Advertise("SON:AUTO")
    objectInteractState = STATE_AUTO
  end
  sonPuppeteer:SetEvent("IgnoreHero")
  syncInteractBranch = thisObj:GetLuaTableAttribute("syncInteractBranch")
  if syncInteractBranch == "NA" then
    syncInteractBranch = nil
  end
  sonPuppeteer:OnEngaged(SonEngaged)
  if deactivateZone ~= nil and deactivateZone ~= "" and deactivateMonitor == nil then
    local zoneObject = GameObjects[deactivateZone]
    monitors_LoadLibrary()
    deactivateMonitor = monitors.CreateEntityZoneMonitor(player, zoneObject)
    deactivateMonitor:OnEnter(Disable)
  end
  forceSaveEnabled = true
end
function TriggerBreakout()
  if sonPuppeteer ~= nil then
    sonPuppeteer:Clear()
    sonPuppeteer:ReturnToNav()
    local sonPoiGO
    if son.OwnedPOI ~= nil then
      sonPoiGO = son.OwnedPOI:GetGameObject()
    end
    if sonPoiGO ~= nil and sonPoiGO == thisObj then
      sonPuppeteer:SetType("")
      son:AbortInteract()
      game.Interact.EnableTags("NotWhileLoreReadActive")
      son:TriggerMoveEvent("kLE_Disengage")
    end
    sonPuppeteer:DetachPuppet()
    sonPuppeteer = nil
  end
  if son ~= nil and IsSonOccupied then
    son:RemoveAvailabilityRequest("SonInteraction")
  end
  if deactivateMonitor ~= nil then
    deactivateMonitor:Stop()
    deactivateMonitor = nil
  end
end
function Disable()
  if objectInteractState == STATE_DISABLED or objectInteractState == nil then
    return
  end
  if sonPuppeteer ~= nil then
    sonPuppeteer:Clear()
    sonPuppeteer:ReturnToNav()
    local sonPoiGO
    if son.OwnedPOI ~= nil then
      sonPoiGO = son.OwnedPOI:GetGameObject()
    end
    if sonPoiGO ~= nil and sonPoiGO == thisObj then
      sonPuppeteer:SetType("")
      son:AbortInteract()
      game.Interact.EnableTags("NotWhileLoreReadActive")
      son:TriggerMoveEvent("kLE_Disengage")
    end
    sonPuppeteer:DetachPuppet()
    sonPuppeteer = nil
  end
  sonInteractZone:Disable()
  objectInteractState = STATE_DISABLED
  if son ~= nil and IsSonOccupied then
    son:RemoveAvailabilityRequest("SonInteraction")
  end
  if deactivateMonitor ~= nil then
    deactivateMonitor:Stop()
    deactivateMonitor = nil
  end
  if thisObj.Parent.Parent ~= nil and thisObj.Parent.Parent.LuaObjectScript and thisObj.Parent.Parent.LuaObjectScript.OnDisable ~= nil then
    thisObj.Parent.Parent.LuaObjectScript.OnDisable(thisObj)
  end
  forceSaveEnabled = false
end
function DisableModule()
  sonInteractZone:Disable()
  objectInteractState = STATE_START_DISABLED
end
function SetNotAllow(notAllow)
  if notAllow then
    objectInteractState = STATE_NOT_ALLOW
  else
    objectInteractState = STATE_INIT
  end
end
function SetActivate()
  if 0 < ActivateRange or ActivateZone ~= "" then
    objectInteractState = STATE_DISABLED
    if ActivateZone ~= "" then
      local zoneObject = GameObjects[ActivateZone]
      if player:InsideZone(zoneObject) then
        Enable()
      end
    end
  else
    Enable()
  end
end
function GetSonPuppeteer()
  return sonPuppeteer
end
function OnSaveCheckpoint(level)
  return {objectInteractState = objectInteractState, forceSaveEnabled = forceSaveEnabled}
end
function OnRestoreCheckpoint(level, go, tab)
  objectInteractState = tab.objectInteractState
  forceSaveEnabled = tab.forceSaveEnabled
  if objectInteractState ~= STATE_DONE then
    objectInteractState = STATE_INIT
    if forceSaveEnabled then
      Enable()
    end
  end
end
function InteractAbort()
  if not son:HasMarker("exitingInteract") then
    if not son:PickupIsAcquired("InteractAbort") then
      son:PickupAcquire("InteractAbort")
    end
    if interactAbortedEvents ~= nil then
      LD.ExecuteCallbacksForEvent(thisLevel, thisObj, interactAbortedEvents, "Son Aborted")
    end
    disableCooldownTimer = 5
    son:AbortInteract()
    game.Interact.EnableTags("NotWhileLoreReadActive")
    if ActivateRange == 0 then
      ActivateRange = breakOutDistance
    end
    Disable()
  end
end
function SonUnreachable()
  PlayBanter("cbt_son_out_of_meter", false)
  InteractAbort()
end
function OverrideInteractBranch(newBranch)
  interactBranchOverride = newBranch
end
function PlayBanter(banter, critical, callback)
  if banter then
    if critical then
      if callback then
        game.Audio.PlayBanter(banter, callback)
      else
        game.Audio.PlayBanter(banter)
      end
    elseif game.Audio.CanBanterConversationPlay(banter) then
      if callback then
        game.Audio.PlayBanterNonCritical(banter, callback)
      else
        game.Audio.PlayBanterNonCritical(banter)
      end
    elseif callback then
      callback()
    end
  end
end
function FreyaCheck()
  if game.Level.GetVariable("FreyaLTWStage") > 0 and game.Level.GetVariable("FreyaLTWStage") < 500 then
    return true
  else
    return false
  end
end
function SetUpdateDisableDistance(value)
  if 0 < ActivateRange then
    game.SubObject.SetUpdateDisableDistance(thisObj, ActivateRange)
  elseif value ~= nil then
    game.SubObject.SetUpdateDisableDistance(thisObj, value)
  end
end
function GetShouldSyncToWorldPosition()
  return not IsPropJournalOrTreasureMap()
end
function IsPropJournalOrTreasureMap()
  return interactBranch == "BRA_JournalInteract" or syncInteractBranch == "BRA_JournalInteract" or interactBranch == "BRA_PickupScroll" or syncInteractBranch == "BRA_PickupScroll"
end
function LuaHook_ShowJournal()
  if journal then
    journal:Show()
  end
end
function LuaHook_HideJournal()
  if journal then
    journal:Hide()
  end
  game.Interact.EnableTags("NotWhileLoreReadActive")
end
function OnInteractCompleted(lvl, obj, ai)
  OnInteractFinish(lvl, obj, ai)
  ai:AbortInteract()
  game.Interact.EnableTags("NotWhileLoreReadActive")
end
local soundEmitter, soundEmitterName, SNDFXLP, sndStartFrame, sndEndFrame
function SoundInit()
  soundEmitterName = thisObj:FindLuaTableAttribute("soundEmitterName")
  if soundEmitterName ~= nil and soundEmitterName ~= "" then
    soundEmitter = thisObj:FindSingleSoundEmitterByName(soundEmitterName)
  end
  SNDFXLP = thisObj:FindLuaTableAttribute("SNDFXLP")
  sndStartFrame = thisObj:FindLuaTableAttribute("sndStartFrame")
  sndEndFrame = thisObj:FindLuaTableAttribute("sndEndFrame")
end
function PlayFXSoundLoop()
  if soundEmitter ~= nil and fxObject ~= nil then
    LD.PlayLoopingSoundToAnimFrame(soundEmitter, fxObject, SNDFXLP, sndStartFrame, sndEndFrame)
  end
end
function SoundSetup(newSoundTable)
  if newSoundTable.SoundEmitter ~= nil then
    soundEmitter = newSoundTable.SoundEmitter
  end
  if newSoundTable.SNDFXLP ~= nil then
    SNDFXLP = newSoundTable.SNDFXLP
  end
  if newSoundTable.StartFrame ~= nil then
    sndStartFrame = newSoundTable.StartFrame
  end
  if newSoundTable.EndFrame ~= nil then
    sndEndFrame = newSoundTable.EndFrame
  end
end
function LuaHook_PlayRopeDropOnParentObject()
  if thisObj.Parent and thisObj.Parent.Parent and thisObj.Parent.Parent.LuaObjectScript and thisObj.Parent.Parent.LuaObjectScript.PlaySoundOnObjectEmitter ~= nil then
    thisObj.Parent.Parent.LuaObjectScript.PlaySoundOnObjectEmitter("SND_MECH_Chain_Drop_Son_Toss")
  end
end
