local LD = require("design.LevelDesignLibrary")
local monitors = require("level.MonitorLibrary")
local REENABLE_COOLDOWN = 5
local thisObj, thisBowl, thisLevel, player, son, playerSonInteractZone, activateMonitor
local reEnable_Timer = 0
local puzzleSolved = false
local isAborted = false
local sonPuppeteer
local timeCooldown = 0
local inactiveFX, usedFX
local played_InitalEngage = false
local played_Observe = false
local played_Question = false
local played_Command = false
local played_Solve = false
local STATE_READY, STATE_UNSOLVED, STATE_SOLVED, STATE_ACTIVATE, STATE_DONE = 0, 1, 2, 3, 4
local STATE_BOWL_IDLE, STATE_BOWL_INIT, STATE_BOWL_ACTIVE, STATE_BOWL_INTERACT, STATE_BOWL_DONE = 0, 1, 2, 3, 4
local STATE_BOWL_HAS_ANSWER, STATE_BOWL_PUZZLE_SOLVED, STATE_BOWL_PUZZLE_SOLVE_START_AFTER_BANTER_FINISHES, STATE_BOWL_PUZZLE_SOLVE_START_AFTER_BANTER_FINISHED = 5, 6, 7, 8
local bowlState = STATE_BOWL_IDLE
local stateString = {
  "STATE_BOWL_IDLE",
  "STATE_BOWL_INIT",
  "STATE_BOWL_ACTIVE",
  "STATE_BOWL_INTERACT",
  "STATE_BOWL_DONE",
  "STATE_BOWL_HAS_ANSWER",
  "STATE_BOWL_PUZZLE_SOLVED"
}
local delayEnableCommand = 2
local startEnabled
local runeDiscover = false
local questionGiven = false
local visual_state, optionalBehavior, maxHeightRange, enableLOS, useAngle
local inCombat = false
local playerCommandRange, activateRange, activateZone
local useAlf320 = false
local approachIgnoreNavmesh = false
local branch_Observe = "BRA_SandBowlDoObserve"
local branch_Question = "BRA_SandbowlQuestionInspect"
local branch_Solve = "BRA_SandbowlApproach"
local branch_QuestionSolve = "BRA_SandbowlApproachWithAnswer"
local branch_PostSolve
local branch_SolveAlf320 = "BRA_RunebowlAlfNeverCare"
local branch_SolveContinuous = "BRA_SandbowlContinuous"
local event_Approach, event_Arrive, event_ObserveStart, event_QuestionStart, event_QuestionComplete, event_SolveStart, event_QuestionThenSolveStart, event_SolveComplete, event_ReuseStart, banter_InitalEngage, banter_Observe, banter_Question, banter_QuestionWithAnswer, banter_Solve, banter_DiscoverAnswer, banter_DiscoverAnswerBeforeQuestion, banter_Reuse
function OnScriptLoaded(level, obj)
  thisObj = obj
  thisLevel = level
  player = game.Player.FindPlayer()
  son = game.AI.FindSon()
  thisBowl = thisObj:FindSingleGOByName("sandbowl")
  startEnabled = obj:GetLuaTableAttribute("startEnabled")
  activateRange = obj:GetLuaTableAttribute("activateRange")
  activateZone = obj:GetLuaTableAttribute("activateZone")
  playerCommandRange = obj:GetLuaTableAttribute("playerCommandRange")
  maxHeightRange = obj:GetLuaTableAttribute("maxHeightRange")
  optionalBehavior = obj:GetLuaTableAttribute("optionalBehavior")
  enableLOS = obj:GetLuaTableAttribute("enableLOS")
  useAngle = obj:GetLuaTableAttribute("useAngle")
  approachIgnoreNavmesh = obj:FindLuaTableAttribute("approachIgnoreNavmesh")
  useAngle = useAngle
  if obj:FindLuaTableAttribute("event_Approach") then
    event_Approach = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("event_Approach"))
  end
  if obj:FindLuaTableAttribute("event_Arrive") then
    event_Arrive = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("event_Arrive"))
  end
  if obj:FindLuaTableAttribute("event_QuestionStart") then
    event_QuestionStart = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("event_QuestionStart"))
  end
  if obj:FindLuaTableAttribute("event_QuestionComplete") then
    event_QuestionComplete = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("event_QuestionComplete"))
  end
  if obj:FindLuaTableAttribute("event_SolveStart") then
    event_SolveStart = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("event_SolveStart"))
  end
  if obj:FindLuaTableAttribute("event_QuestionThenSolveStart") then
    event_QuestionThenSolveStart = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("event_QuestionThenSolveStart"))
  end
  if obj:FindLuaTableAttribute("event_SolveComplete") then
    event_SolveComplete = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("event_SolveComplete"))
  end
  if obj:FindLuaTableAttribute("event_ReuseStart") then
    event_ReuseStart = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("event_ReuseStart"))
  end
  banter_InitalEngage = obj:FindLuaTableAttribute("banter_InitalEngage")
  banter_Observe = obj:FindLuaTableAttribute("banter_Observe")
  banter_Question = obj:FindLuaTableAttribute("banter_Question")
  banter_QuestionWithAnswer = obj:FindLuaTableAttribute("banter_QuestionWithAnswer")
  banter_Solve = obj:FindLuaTableAttribute("banter_Solve")
  banter_DiscoverAnswer = obj:FindLuaTableAttribute("banter_DiscoverAnswer")
  banter_DiscoverAnswerBeforeQuestion = obj:FindLuaTableAttribute("banter_DiscoverAnswerBeforeQuestion")
  banter_Reuse = obj:FindLuaTableAttribute("banter_Reuse")
  inactiveFX = obj:FindSingleGOByName("mesh_inactivestate_liquid")
  usedFX = obj:FindSingleGOByName("mesh_deadstate_liquid")
  thisBowl:AddChild(inactiveFX)
  thisBowl:AddChild(usedFX)
  usedFX:Hide()
  inactiveFX:Hide()
  game.SubObject.SetUpdateDisableDistance(obj, 50)
  playerSonInteractZone = game.InteractZone.New(obj, "promptJoint_Son")
  LD.CreateSonInteractZone(playerSonInteractZone)
  playerSonInteractZone:SetEnableLineOfSightTest(enableLOS)
  playerSonInteractZone:SetXZRange(playerCommandRange)
  playerSonInteractZone:SetYRange(maxHeightRange)
  playerSonInteractZone:SetHintXZRange(playerCommandRange)
  playerSonInteractZone:SetAngle(useAngle)
  playerSonInteractZone:SetOnScreenPercent(1, 0.5)
  playerSonInteractZone:SetRequiresSonUnoccupied()
  playerSonInteractZone:SetTags("NotAllowedOnBoat")
  playerSonInteractZone:SetTags("NotInCombat")
  playerSonInteractZone:Disable()
end
function OnFirstStart(level, obj)
  SetSandBowlVisualState(STATE_READY)
end
function OnStart(level, obj)
  if activateZone ~= "" then
    local zoneObject = level:GetGameObject(activateZone)
    activateMonitor = monitors.CreateEntityZoneMonitor(player, zoneObject)
    activateMonitor:OnEnter(EnableSandBowl)
    if not startEnabled then
      activateMonitor:Stop()
    end
  end
  if startEnabled and not puzzleSolved then
    bowlState = STATE_BOWL_INIT
  end
end
function OnSonInteractButtonPressed(level, obj)
end
local CleanupSonPuppeteer = function()
  if sonPuppeteer ~= nil then
    sonPuppeteer:Clear()
    sonPuppeteer:DetachPuppet()
    sonPuppeteer = nil
  end
  son:ClearLocomotionSlowDownBehavior()
end
local GetSonApproach = function(branchName)
  local destinationLength = (son.WorldPosition - thisObj.WorldPosition):Length()
  local stoppingDist = 0.6
  local speedAdjustment = 4
  if destinationLength < stoppingDist then
    stoppingDist = destinationLength * 0.75
    speedAdjustment = 1
  end
  local approachParam = {
    branch_name = branchName,
    joint_name = "synchJoint",
    focus_end_direction = true,
    speed = speedAdjustment,
    stop_distance = stoppingDist,
    complete_radius = stoppingDist,
    close_range_distance = 1,
    ignore_navmesh = approachIgnoreNavmesh
  }
  return approachParam
end
local DisablingSonInteractZone = function()
  sonPuppeteer:Clear()
  playerSonInteractZone:Disable()
end
local SonInteractAborted = function()
  isAborted = true
  if sonPuppeteer ~= nil then
    sonPuppeteer:Clear()
  end
  CleanupSonPuppeteer()
  playerSonInteractZone:Disable()
  son:RemoveAvailabilityRequest("SonInteraction")
end
local StartInactiveFX = function()
  inactiveFX:Show()
  usedFX:Hide()
end
local StartUsedFX = function()
  inactiveFX:Show()
  usedFX:Show()
end
function SetSandBowlVisualState(state)
  visual_state = state
  if visual_state == STATE_READY then
    StartInactiveFX()
    thisBowl:StartAnim("runebowl_Ready")
  elseif visual_state == STATE_ACTIVATE then
    thisBowl:StartAnim("runebowlActive")
  elseif visual_state == STATE_DONE then
    thisBowl:StartAnim("runebowl_Off")
    StartUsedFX()
  end
end
local UpdateDebug = function(level, obj)
  local debugTable = {}
  debugTable[#debugTable + 1] = {
    "Current Bowl State",
    stateString[bowlState + 1]
  }
  debugTable[#debugTable + 1] = {
    "Puppeteer Object  ",
    sonPuppeteer
  }
  debugTable[#debugTable + 1] = {
    "Is Aborted  ",
    isAborted
  }
  debugTable[#debugTable + 1] = {
    "Is Son UnOccupied  ",
    son:IsUnoccupied()
  }
  debugTable[#debugTable + 1] = {
    "Is Son for Combat  ",
    son:IsAvailableForCombat()
  }
  debugTable.Title = "Sandbowl"
  debugTable.X = 3
  debugTable.Y = 8
  engine.DrawDebugTable(debugTable)
end
function OnUpdate(level, obj)
  if bowlState == STATE_BOWL_INIT then
    if game.AIUtil.Distance(obj, player) < activateRange and game.Combat.GetCombatStatus() == false then
      if optionalBehavior == "reuseableeBowl" and puzzleSolved then
        EnableSandBowl(optionalBehavior)
      else
        EnableSandBowl()
      end
    end
  elseif bowlState == STATE_BOWL_DONE then
    if 0 < reEnable_Timer and game.Combat.GetCombatStatus() == false then
      reEnable_Timer = reEnable_Timer - level:GetUnitTime()
      if reEnable_Timer <= 0 then
        if puzzleSolved and optionalBehavior == "reuseableeBowl" then
          EnableSandBowl(optionalBehavior)
          SetSandBowlVisualState(STATE_READY)
        else
          bowlState = STATE_BOWL_INIT
        end
      end
    end
  elseif bowlState == STATE_BOWL_PUZZLE_SOLVE_START_AFTER_BANTER_FINISHES and 0 < game.Audio.GetTimeSinceBanterPlayed() then
    ForcePOIFromAnswerInteractionAfterBanter()
    bowlState = STATE_BOWL_PUZZLE_SOLVE_START_AFTER_BANTER_FINISHED
  end
  if game.Combat.GetCombatStatus() and sonPuppeteer ~= nil then
    timeCooldown = timeCooldown + thisLevel:GetUnitTime()
    if 3 < timeCooldown then
      timeCooldown = 0
      OnAbortInvestigate()
    end
  end
end
function OnSaveCheckpoint(level, obj)
  return {
    bowlState = bowlState,
    visual_state = visual_state,
    runeDiscover = runeDiscover,
    puzzleSolved = puzzleSolved
  }
end
function OnRestoreCheckpoint(level, obj, savedInfo)
  bowlState = savedInfo.bowlState
  runeDiscover = savedInfo.runeDiscover
  puzzleSolved = savedInfo.puzzleSolved
  if bowlState ~= STATE_BOWL_PUZZLE_SOLVED and not puzzleSolved and startEnabled then
    bowlState = STATE_BOWL_INIT
  end
  if optionalBehavior == "reuseableeBowl" and bowlState == STATE_BOWL_DONE or bowlState == STATE_BOWL_ACTIVE then
    bowlState = STATE_BOWL_INIT
  end
  SetSandBowlVisualState(savedInfo.visual_state)
end
function OnEngage(poi)
  local stageName = "Investigate Approaching"
  sonPuppeteer:Clear()
  if event_Approach ~= nil then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, event_Approach, "A")
  end
  sonPuppeteer:SetStageName(stageName)
  sonPuppeteer:OnEvent(OnAbortInvestigate, "kESonAbort")
  sonPuppeteer:OnEvent(OnAbortInvestigate, "BreakOut")
  if son:HasMarker("AllowSyncShots") == false then
    son:AddMarker("AllowSyncShots")
  end
  if son:HasMarker("DisableSummon") == false then
    son:AddMarker("DisableSummon")
  end
  son:ClearLocomotionSlowDownBehavior()
  son:SetNewAvailabilityRequest("SonInteraction", {AvailableForSync = false, Unoccupied = false})
  if runeDiscover then
    playerSonInteractZone:Disable()
    stageName = "Solve Approaching"
    if useAlf320 then
      branch_Solve = branch_SolveAlf320
    elseif optionalBehavior == "continuousBowl" then
      branch_Solve = branch_SolveContinuous
    elseif questionGiven == false then
      branch_Solve = branch_QuestionSolve
    end
    sonPuppeteer:Approach(GetSonApproach(branch_Solve))
    son:SetActuator({StartDistance = 0.001})
    sonPuppeteer:OnComplete(OnArriveSolve)
  else
    if not played_InitalEngage then
      if banter_InitalEngage ~= nil and banter_InitalEngage ~= "" then
        game.Audio.PlayBanter(banter_InitalEngage)
      end
      played_InitalEngage = true
    end
    sonPuppeteer:OnEvent(OnKratosCommandEngage, "kESonInteractBowl")
    sonPuppeteer:Approach(GetSonApproach(branch_Observe))
    sonPuppeteer:OnComplete(OnArriveObserve)
    son:SetActuator({StartDistance = 0.001})
  end
end
function OnArriveSolve(poi)
  sonPuppeteer:SetStageName("Interact Solve Auto")
  SetSandBowlVisualState(STATE_ACTIVATE)
  if event_SolveStart ~= "" then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, event_SolveStart, "Solve Start")
  end
  sonPuppeteer:Sync(branch_Solve, true, {}, "synchJoint")
  if not played_Solve and not puzzleSolved then
    if questionGiven then
      if banter_Solve ~= nil and banter_Solve ~= "" then
        game.Audio.PlayBanter(banter_Solve)
      end
    elseif banter_QuestionWithAnswer ~= nil and banter_QuestionWithAnswer ~= "" and not useAlf320 and not played_Solve then
      game.Audio.PlayBanter(banter_QuestionWithAnswer)
    end
  end
  played_Solve = true
  if puzzleSolved and event_ReuseStart ~= nil then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, event_ReuseStart, "Reuse Start")
  end
  if optionalBehavior == "continuousBowl" then
    sonPuppeteer:OnEvent(OnSolveBranch, "CompleteSandbowl")
  end
  sonPuppeteer:OnComplete(OnSolveBranch)
  if son:HasMarker("AllowSyncShots") then
    son:RemoveMarker("AllowSyncShots")
  end
  if son:HasMarker("DisableSummon") then
    son:RemoveMarker("DisableSummon")
  end
end
local DelayEnableInteractZone = function()
  if bowlState ~= STATE_BOWL_ACTIVE or bowlState == STATE_BOWL_PUZZLE_SOLVE_START_AFTER_BANTER_FINISHES then
    return
  end
  playerSonInteractZone:Enable()
end
function OnArriveObserve(poi)
  sonPuppeteer:Clear()
  if optionalBehavior == "approachBowl" then
    _G.StartLevelTimer(delayEnableCommand, DelayEnableInteractZone)
  end
  if not played_Observe then
    if banter_Observe ~= nil and banter_Observe ~= "" then
      game.Audio.PlayBanter(banter_Observe)
    end
    played_Observe = true
  end
  sonPuppeteer:SetStageName("Investigate Observe")
  sonPuppeteer:Sync(branch_Observe, true, {}, "synchJoint")
  sonPuppeteer:OnEvent(OnKratosCommand, "kESonInteractBowl")
  sonPuppeteer:OnEvent(OnAbortInvestigate, "kESonAbort")
  sonPuppeteer:OnEvent(OnAbortInvestigate, "BreakOut")
  sonPuppeteer:OnComplete(OnInvestigateBranch)
  if event_Arrive ~= nil then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, event_Arrive, "Arrive")
  end
end
function OnQuestionCompleted(poi)
  sonPuppeteer:Clear()
  sonPuppeteer:SetStageName("Question Looping")
  sonPuppeteer:OnEvent(OnAbortQuestionDone, "kESonAbort")
  son:RemoveAvailabilityRequest("SonInteraction")
  if event_QuestionComplete ~= "" then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, event_QuestionComplete, "Question Complete")
  end
  questionGiven = true
  bowlState = STATE_BOWL_DONE
end
function OnSolveBranch(poi)
  if sonPuppeteer then
    sonPuppeteer:Clear()
    sonPuppeteer:SetStageName("Solved")
  end
  son:RemoveAvailabilityRequest("SonInteraction")
  CleanupSonPuppeteer()
  if not puzzleSolved and event_SolveComplete ~= "" then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, event_SolveComplete, "Solve Complete")
  end
  SetSandBowlVisualState(STATE_DONE)
  bowlState = STATE_BOWL_PUZZLE_SOLVED
  puzzleSolved = true
  if optionalBehavior == "reuseableeBowl" then
    bowlState = STATE_BOWL_DONE
  end
  if son:HasMarker("AllowSyncShots") then
    son:RemoveMarker("AllowSyncShots")
  end
  if son:HasMarker("DisableSummon") then
    son:RemoveMarker("DisableSummon")
  end
end
function OnInvestigateBranch(poi)
  sonPuppeteer:Clear()
  sonPuppeteer:SetStageName("Check Question")
  sonPuppeteer:OnEvent(OnAbortInvestigate, "kESonAbort")
  sonPuppeteer:OnEvent(OnAbortInvestigate, "BreakOut")
  sonPuppeteer:OnEvent(OnKratosCommand, "kESonInteractBowl")
end
function OnKratosCommandApproach(poi)
  local stageName = "Approaching From Command"
  DisablingSonInteractZone()
  sonPuppeteer:OnEvent(OnAbortInvestigate, "kESonAbort")
  sonPuppeteer:Approach(GetSonApproach(branch_Solve))
  sonPuppeteer:OnComplete(OnArriveSolve)
  son:SetActuator({StartDistance = 0.001})
  if banter_Reuse and banter_Reuse ~= "" then
    game.Audio.PlayBanter(banter_Reuse)
  end
end
function OnKratosCommandEngage(poi)
  local stageName = "Engage From Command"
  DisablingSonInteractZone()
  sonPuppeteer:OnEvent(OnAbortInvestigate, "kESonAbort")
  sonPuppeteer:Approach(GetSonApproach(branch_Question))
  sonPuppeteer:OnComplete(OnKratosCommand)
  son:SetActuator({StartDistance = 0.001})
end
function OnKratosCommand(poi)
  local stageName = "Interact Question"
  local branch = branch_Question
  local onComplete = OnQuestionCompleted
  local banter = banter_Question
  DisablingSonInteractZone()
  if runeDiscover then
    if questionGiven then
      stageName = "Interact Solve"
      branch = branch_QuestionSolve
      onComplete = OnSolveBranch
      banter = banter_Solve
      if banter and banter ~= "" then
        game.Audio.PlayBanter(banter)
      end
      SetSandBowlVisualState(STATE_ACTIVATE)
      if event_QuestionThenSolveStart ~= "" then
        LD.ExecuteCallbacksForEvent(thisLevel, thisObj, event_QuestionThenSolveStart, "Question Then Solve Start")
      end
    else
      stageName = "Interact Solve with Answer"
      branch = branch_QuestionSolve
      onComplete = OnSolveBranch
      banter = banter_QuestionWithAnswer
      if banter and banter ~= "" then
        game.Audio.PlayBanter(banter)
      end
      SetSandBowlVisualState(STATE_ACTIVATE)
      if event_QuestionThenSolveStart ~= "" then
        LD.ExecuteCallbacksForEvent(thisLevel, thisObj, event_QuestionThenSolveStart, "Question Then Solve Start")
      end
    end
    if son:HasMarker("AllowSyncShots") then
      son:RemoveMarker("AllowSyncShots")
    end
    if son:HasMarker("DisableSummon") then
      son:RemoveMarker("DisableSummon")
    end
  else
    if questionGiven then
      stageName = "Interact QuestionDone"
    elseif event_QuestionStart ~= "" then
      LD.ExecuteCallbacksForEvent(thisLevel, thisObj, event_QuestionStart, "Question Start")
    end
    if not played_Command then
      if banter ~= nil and banter ~= "" then
        game.Audio.PlayBanter(banter)
      end
      played_Command = true
    end
  end
  sonPuppeteer:SetStageName(stageName)
  sonPuppeteer:Sync(branch, true, {}, "synchJoint")
  sonPuppeteer:OnComplete(onComplete)
end
function OnAbortInvestigate(poi)
  if sonPuppeteer ~= nil then
    sonPuppeteer:SetStageName("Abort Investigate")
  end
  SonInteractAborted()
  reEnable_Timer = REENABLE_COOLDOWN
  bowlState = STATE_BOWL_DONE
  son:TriggerMoveEvent("kLE_Disengage")
  if son:HasMarker("AllowSyncShots") then
    son:RemoveMarker("AllowSyncShots")
  end
  if son:HasMarker("DisableSummon") then
    son:RemoveMarker("DisableSummon")
  end
end
function CrystalSolvedAndWait(poi)
  if sonPuppeteer ~= nil then
    sonPuppeteer:SetStageName("Abort Investigate")
  end
  SonInteractAborted()
  reEnable_Timer = REENABLE_COOLDOWN
  bowlState = STATE_BOWL_ACTIVE
  son:TriggerMoveEvent("kLE_Disengage")
  if son:HasMarker("AllowSyncShots") then
    son:RemoveMarker("AllowSyncShots")
  end
  if son:HasMarker("DisableSummon") then
    son:RemoveMarker("DisableSummon")
  end
end
function OnAbortQuestionDone(poi)
  sonPuppeteer:SetStageName("Abort Question Done")
  SonInteractAborted()
  bowlState = STATE_BOWL_DONE
  if puzzleSolved == false or optionalBehavior == "reuseableeBowl" then
    reEnable_Timer = REENABLE_COOLDOWN
  end
  son:TriggerMoveEvent("kLE_Disengage")
  if son:HasMarker("AllowSyncShots") then
    son:RemoveMarker("AllowSyncShots")
  end
  if son:HasMarker("DisableSummon") then
    son:RemoveMarker("DisableSummon")
  end
end
local CreatePOI = function(interactType)
  if bowlState == STATE_BOWL_PUZZLE_SOLVE_START_AFTER_BANTER_FINISHES then
    return
  end
  if sonPuppeteer ~= nil then
    sonPuppeteer = nil
  end
  local name = thisObj.Parent:GetName()
  sonPuppeteer = game.Puppeteer.NewEngagement(thisObj, "SandBowl - refnode:" .. name)
  sonPuppeteer:SetUserFlag("ForcePushAway")
  sonPuppeteer:SetType("SonSandBowl")
  sonPuppeteer:SetStageName("Active")
  local adString = "SON:AUTO"
  local enageFunction = OnEngage
  if interactType == "reuseableeBowl" then
    adString = "INTERACT_SON"
    enageFunction = OnKratosCommandApproach
  end
  sonPuppeteer:Advertise(adString)
  sonPuppeteer:OnEngaged(enageFunction)
end
local ForcePOIFromAnswerInteraction = function()
  if game.Audio.GetTimeSinceBanterPlayed() <= 0 then
    bowlState = STATE_BOWL_PUZZLE_SOLVE_START_AFTER_BANTER_FINISHES
    return
  end
  if sonPuppeteer ~= nil then
    sonPuppeteer = nil
  end
  local name = thisObj.Parent:GetName()
  sonPuppeteer = game.Puppeteer.NewForce(thisObj, "SandBowl(Forced) - refnode:" .. name, son)
  sonPuppeteer:SetUserFlag("ForcePushAway")
  sonPuppeteer:SetType("SonSandBowl")
  sonPuppeteer:OnComplete(OnEngage)
end
function ForcePOIFromAnswerInteractionAfterBanter()
  if sonPuppeteer ~= nil then
    sonPuppeteer = nil
  end
  local name = thisObj.Parent:GetName()
  sonPuppeteer = game.Puppeteer.NewForce(thisObj, "SandBowl(Forced) - refnode:" .. name, son)
  sonPuppeteer:SetUserFlag("ForcePushAway")
  sonPuppeteer:SetType("SonSandBowl")
  sonPuppeteer:OnComplete(OnEngage)
end
function EnableSandBowl(interactType)
  isAborted = false
  CreatePOI(interactType)
  if optionalBehavior ~= "approachBowl" and bowlState ~= STATE_BOWL_HAS_ANSWER and runeDiscover == false or optionalBehavior == "reuseableeBowl" then
    playerSonInteractZone:Enable()
  end
  bowlState = STATE_BOWL_ACTIVE
end
function DisableSandBowl()
  playerSonInteractZone:Disable()
  CleanupSonPuppeteer()
end
function SetRuneDiscovered(isSimple)
  if runeDiscover == true then
    return
  end
  runeDiscover = true
  if not isSimple and bowlState ~= STATE_BOWL_IDLE and game.AIUtil.Distance(son, thisObj) < 10 then
    _G.StartLevelTimer(0.5, ForcePOIFromAnswerInteraction)
    playerSonInteractZone:Disable()
    bowlState = STATE_BOWL_HAS_ANSWER
  end
  if questionGiven then
    if banter_DiscoverAnswer ~= nil and banter_DiscoverAnswer ~= "" then
      game.Audio.PlayBanter(banter_DiscoverAnswer)
    end
  elseif banter_DiscoverAnswerBeforeQuestion ~= nil and banter_DiscoverAnswerBeforeQuestion ~= "" then
    game.Audio.PlayBanter(banter_DiscoverAnswerBeforeQuestion)
  end
end
function IsRuneDiscovered()
  return runeDiscover
end
function SetQuestionGiven()
  questionGiven = true
end
function SetSonAlf320Branch(enable)
  useAlf320 = enable or true
end
function Enable()
  if bowlState ~= STATE_BOWL_PUZZLE_SOLVE_START_AFTER_BANTER_FINISHES then
    bowlState = STATE_BOWL_INIT
  end
end
function Disable()
  DisableSandBowl()
  son:RemoveAvailabilityRequest("SonInteraction")
  if bowlState == STATE_BOWL_PUZZLE_SOLVED or bowlState == STATE_BOWL_DONE then
    return
  end
  bowlState = STATE_BOWL_IDLE
end
function ForcedDisable()
  Disable()
  bowlState = STATE_BOWL_IDLE
end
function EnableInteractPrompt()
  playerSonInteractZone:Enable()
end
function DisableInteractPrompt()
  playerSonInteractZone:Disable()
end
function Reset()
  DisableSandBowl()
  puzzleSolved = false
  questionGiven = false
  runeDiscover = false
  SetSandBowlVisualState(STATE_READY)
  bowlState = STATE_BOWL_IDLE
end
function SonAbortSandbowl()
  isAborted = true
  son:TriggerMoveEvent("kLE_Disengage")
  thisBowl:StartAnim("runebowl_Off")
  StartInactiveFX()
  Disable()
end
function SonCompleteSandbowl()
  son:TriggerMoveEvent("kLE_Complete")
  son.OwnedPOI:SendEvent("CompleteSandbowl")
end
function HideSandBowl()
  inactiveFX:Hide()
  usedFX:Hide()
  thisBowl:Hide()
  thisObj:HideCollision()
end
function ShowSandBowl()
  thisBowl:Show()
  thisObj:ShowCollision()
  SetSandBowlVisualState(visual_state)
end
