local DL = require("design.DesignerLibrary")
local timer = require("level.timer")
local mpicon = require("ui.mpicon")
local CCOS = require("camera.camera_oneshot")
local LD = require("design.LevelDesignLibrary")
local uiCalls = require("ui.uicalls")
local boatDockingState = 0
local dockingState_Docked = 0
local dockingState_EnteringFromDock = 1
local dockingState_InBoat = 2
local dockingState_DockingOnDock = 3
local dockingState_EnteringFromBeach = 4
local dockingState_DockingOnBeach = 5
local dockingState_Beached = 6
local player = game.Player.FindPlayer()
local pad = player.Pad
local boat, boatRightEnterFromDockInteractZone, boatLeftEnterFromDockInteractZone, boatLeftEnterFromBeachInteractZone, boatRightEnterFromBeachInteractZone
local approachingBoat = false
local beachingTargetPosition, beachingTargetForwardVector, beachApproach
local inBoatEnter = false
local boatDockMaxLongRangeEnter = 4
local boatDockLongRangeHint = 10
local boatBeachMaxLongRangeEnter = 4.5
local boatBeachLongRangeHint = 10
local camera_BoatEnterFar, camera_BoatEnterNear, camera_DockEnterNear, camera_BoatBeachEnterFar, camera_BoatBeachEnterNear, camBoatYaw, camDockYaw, boatAngle, dockAngle
local cameraWorldSpaceForward = engine.Vector.New(0, 0, 1)
local recenter_boatPreBoatEnterControlLock
local isInteractionDisabled = false
local recentApproachFailed = false
local pendingDockCinematicMarkerName = "pendingDockCinematic"
local pluckLootMarkerName = "preparingToPluckLoot"
local objectWithDockingMarker, myConfig
local minBoatStopLinearSpeed_BoatDocking = 4
local maxBoatStoppedLinearSpeed_BoatDocking = 1
local isBoatStopping_BoatDocking = false
local boatCrossMove_BoatDocking = ""
local maxBoatCrossMoveFrames_BoatDocking = 5
local boatCrossMoveFrames_BoatDocking = maxBoatCrossMoveFrames_BoatDocking
local maxBoatCrossMovePercent_BoatDocking = 0.6
local cachedDockMoveForAfterCross = ""
local cachedBoatFacingRightForAfterCross = true
local hideBeachedBoatsPing_Time = 1
local hideBeachedBoatsPing_Enabled = false
function OnScriptLoaded(level, obj)
  print("initing boat level helper")
  recenter_boatPreBoatEnterControlLock = {
    TimeStart = 0,
    TimeDuration = 4,
    LockRecenter = 1,
    YawRange = -1,
    TriggerLeft = 0,
    TriggerRight = 0,
    ReturnLeft = 180,
    ReturnRight = -180,
    PitchRange = 0
  }
  local objPosition = obj:GetWorldPosition()
  local allBoats = level:FindGameObjects("Boat00")
  local closestBoatDistance = 99999
  for i = 1, #allBoats do
    local toObjLength = game.AIUtil.Distance(allBoats[i], objPosition)
    if closestBoatDistance > toObjLength then
      if closestBoatDistance < 0.15 then
        error("Found two boats very close to each other when initing, this could confused the boat level helper.")
      end
      closestBoatDistance = toObjLength
      boat = allBoats[i]
    end
  end
  if boat == nil then
    error("Failed to find the associated boat in boatLevelHelper, this should only be spawned if there's a boat near the player")
  end
  local boatRightDockInteractType, boatLeftDockInteractType, boatRightBeachInteractType, boatLeftBeachInteractType
  if tweaks.tBranch.eInteractionType then
    boatRightDockInteractType = tweaks.tBranch.eInteractionType.kITBoatRight
    boatLeftDockInteractType = tweaks.tBranch.eInteractionType.kITBoatLeft
    boatRightBeachInteractType = tweaks.tBranch.eInteractionType.kITBoatBeachLeft
    boatLeftBeachInteractType = tweaks.tBranch.eInteractionType.kITBoatBeachRight
  else
    boatRightDockInteractType = tweaks.tBranchBase.eInteractionType.kITBoatRight
    boatLeftDockInteractType = tweaks.tBranchBase.eInteractionType.kITBoatLeft
    boatRightBeachInteractType = tweaks.tBranchBase.eInteractionType.kITBoatBeachLeft
    boatLeftBeachInteractType = tweaks.tBranchBase.eInteractionType.kITBoatBeachRight
  end
  local boatIconTable = {
    normal = "WORLD_INTERACT_ALLOW_OFFSCREEN",
    unavailable = "WORLD_INTERACT_UNAVAILABLE",
    locked = "WORLD_INTERACT_LOCKED",
    hint = "WORLD_INTERACT_HINT"
  }
  boatRightEnterFromDockInteractZone = game.InteractZone.New(boat, "rightEnterJoint", boatRightDockInteractType)
  boatRightEnterFromDockInteractZone:Disable()
  boatRightEnterFromDockInteractZone:SetXZRange(boatDockMaxLongRangeEnter)
  boatRightEnterFromDockInteractZone:SetHintXZRange(boatDockLongRangeHint)
  boatRightEnterFromDockInteractZone:SetAngle(180)
  boatRightEnterFromDockInteractZone:SetHintAngle(180)
  boatRightEnterFromDockInteractZone:SetOnScreenPercentWeight(0)
  boatRightEnterFromDockInteractZone:SetCameraFrontAngleWeight(1)
  boatRightEnterFromDockInteractZone:SetCameraFrontAngle(200)
  boatRightEnterFromDockInteractZone:SetPromptIconSet(boatIconTable)
  boatRightEnterFromDockInteractZone:SetRequiresSonUnoccupied(true)
  boatLeftEnterFromDockInteractZone = game.InteractZone.New(boat, "leftEnterJoint", boatLeftDockInteractType)
  boatLeftEnterFromDockInteractZone:Disable()
  boatLeftEnterFromDockInteractZone:SetXZRange(boatDockMaxLongRangeEnter)
  boatLeftEnterFromDockInteractZone:SetHintXZRange(boatDockLongRangeHint)
  boatLeftEnterFromDockInteractZone:SetAngle(180)
  boatLeftEnterFromDockInteractZone:SetHintAngle(180)
  boatLeftEnterFromDockInteractZone:SetOnScreenPercentWeight(0)
  boatLeftEnterFromDockInteractZone:SetCameraFrontAngleWeight(1)
  boatLeftEnterFromDockInteractZone:SetCameraFrontAngle(200)
  boatLeftEnterFromDockInteractZone:SetPromptIconSet(boatIconTable)
  boatLeftEnterFromDockInteractZone:SetRequiresSonUnoccupied(true)
  boatLeftEnterFromBeachInteractZone = game.InteractZone.New(boat, "beachLeftEnterJoint", boatLeftBeachInteractType)
  boatLeftEnterFromBeachInteractZone:Disable()
  boatLeftEnterFromBeachInteractZone:SetXZRange(boatBeachMaxLongRangeEnter)
  boatLeftEnterFromBeachInteractZone:SetHintXZRange(boatBeachLongRangeHint)
  boatLeftEnterFromBeachInteractZone:SetAngle(180)
  boatLeftEnterFromBeachInteractZone:SetHintAngle(180)
  boatLeftEnterFromBeachInteractZone:SetOnScreenPercent(0.1)
  boatLeftEnterFromBeachInteractZone:SetRequiresSonUnoccupied(true)
  boatRightEnterFromBeachInteractZone = game.InteractZone.New(boat, "beachRightEnterJoint", boatRightBeachInteractType)
  boatRightEnterFromBeachInteractZone:Disable()
  boatRightEnterFromBeachInteractZone:SetXZRange(boatBeachMaxLongRangeEnter)
  boatRightEnterFromBeachInteractZone:SetHintXZRange(boatBeachLongRangeHint)
  boatRightEnterFromBeachInteractZone:SetAngle(180)
  boatRightEnterFromBeachInteractZone:SetHintAngle(180)
  boatRightEnterFromBeachInteractZone:SetOnScreenPercent(0.1)
  boatRightEnterFromBeachInteractZone:SetRequiresSonUnoccupied(true)
  OnBoatDockingStateChanged(nil, game.Boat.GetDockingState(boat))
  SoundInit()
end
function OnUpdate(level, obj)
  if myConfig == nil and player.GroundLevel ~= nil then
    SetVehicleConfiguration(level, obj, boat)
  end
  if camera_BoatEnterFar ~= nil then
    camera_BoatEnterFar:Update()
  end
  if camera_BoatEnterNear ~= nil then
    camera_BoatEnterNear:Update()
  end
  if camera_DockEnterNear ~= nil then
    camera_DockEnterNear:Update()
    if player:HasMarker("Exit") then
      camera_DockEnterNear = nil
    end
  end
  if camera_BoatBeachEnterFar ~= nil then
    camera_BoatBeachEnterFar:Update()
  end
  if camera_BoatBeachEnterNear ~= nil then
    camera_BoatBeachEnterNear:Update()
  end
  local previousDockingState = boatDockingState
  boatDockingState = game.Boat.GetDockingState(boat)
  if previousDockingState ~= boatDockingState then
    OnBoatDockingStateChanged(previousDockingState, boatDockingState)
    if boatDockingState == dockingState_EnteringFromDock or boatDockingState == dockingState_EnteringFromBeach then
      ClearBoatApproachVariables()
    end
    AddRecentDockingStateTransition()
  end
  if (boatDockingState == dockingState_Docked or boatDockingState == dockingState_Beached) and approachingBoat == false then
    game.Interact.EnableTags("NotAllowedOnBoat")
    EnableBridgeGateWays()
  else
    game.Interact.DisableTags("NotAllowedOnBoat")
    DisableBridgeGateways()
  end
  local allowUseWorldHack = false
  if game.CHECK_FEATURE("ALLOW_USE_WORLD_HACK") then
    allowUseWorldHack = player:IsAllowUseWorldHack()
  end
  if approachingBoat == true and player:IsInNavigationMove() == false and allowUseWorldHack == false and inBoatEnter == false then
    print("Canceling boat approach")
    ClearBoatApproachVariables()
    camera_BoatEnterFar = nil
    camera_BoatEnterNear = nil
    camera_DockEnterNear = nil
    camera_BoatBeachEnterFar = nil
    camera_BoatBeachEnterNear = nil
    game.Camera.CancelRecenter()
    recentApproachFailed = true
    timer.StartLevelTimer(0.5, function()
      recentApproachFailed = false
    end)
  end
  UpdateInteractZones(boatDockingState)
  RecordBoatBlackBoardInfo(boatDockingState)
  if AnyRecentDockingStateTransitions() then
    return
  end
  local canTriggerApproach = true
  if player:IsPlayingMove("MOV_DefenseLoop") or player:IsPlayingMove("MOV_AimLoop") then
    canTriggerApproach = false
  end
  if player:IsInNavigationMove() == false and allowUseWorldHack == false then
    canTriggerApproach = false
  end
  if recentApproachFailed then
    canTriggerApproach = false
  end
  if pad.CombatOrEquipButtonJustDown ~= nil and pad.CombatOrEquipButtonJustDown then
    canTriggerApproach = false
  end
  if game.InteractZone.IsSonInteractStarting and game.InteractZone.IsSonInteractStarting() then
    canTriggerApproach = false
  end
  if game.GetEnterRageModeOptionNumber and game.GetEnterRageModeOptionNumber() == 1 and pad.CrossDown then
    local rageValue = player:MeterGetValue("Blood")
    if 100 <= rageValue then
      canTriggerApproach = false
    end
  end
  if canTriggerApproach and (boatRightEnterFromDockInteractZone:PlayerCanInteract() or boatLeftEnterFromDockInteractZone:PlayerCanInteract()) then
    if pad.CircleDown and player.CanUseWorld == true then
      ApproachAndEnterBoatFromDock(obj)
    end
  elseif canTriggerApproach and (boatLeftEnterFromBeachInteractZone:PlayerCanInteract() or boatRightEnterFromBeachInteractZone:PlayerCanInteract()) then
    if pad.CircleDown and player.CanUseWorld == true then
      ApproachAndEnterBoatFromBeach(obj)
    end
  elseif boatDockingState == dockingState_InBoat then
    BoatPrepareToDockUpdate()
  end
end
function DisableBridgeGateways()
  game.Compass.SetGatewayMarkerIsOpen("CALS_200_Bridge13", false)
  game.Compass.SetGatewayMarkerIsOpen("CALS_200_Bridge3", false)
  game.Compass.SetGatewayMarkerIsOpen("CALS_200_Bridge11", false)
  game.Compass.SetGatewayMarkerIsOpen("CALS_200_Bridge9", false)
  game.Compass.SetGatewayMarkerIsOpen("ISW_910_Helper7", false)
  game.Compass.SetGatewayMarkerIsOpen("CALS_200_Snake", false)
  game.Compass.SetGatewayMarkerIsOpen("CALS_200_Snake2", false)
  if game.Level.GetVariable("CompletedCineNumber") >= 250 and game.Level.GetVariable("CompletedCineNumber") < 340 then
    game.Compass.SetGatewayMarkerIsOpen("CALS_200_Helper166", true)
    game.Compass.SetGatewayMarkerIsOpen("CALS_200_Helper10A_WL1", true)
    game.Compass.SetGatewayMarkerIsOpen("CALS_200_Helper458", true)
  else
    game.Compass.SetGatewayMarkerIsOpen("CALS_200_Helper458", false)
    game.Compass.SetGatewayMarkerIsOpen("CALS_200_Helper166", false)
    game.Compass.SetGatewayMarkerIsOpen("CALS_200_Helper10A_WL1", false)
  end
end
function EnableBridgeGateWays()
  game.Compass.SetGatewayMarkerIsOpen("CALS_200_Snake", false)
  game.Compass.SetGatewayMarkerIsOpen("CALS_200_Bridge1", true)
  game.Compass.SetGatewayMarkerIsOpen("CALS_200_Bridge13", true)
  game.Compass.SetGatewayMarkerIsOpen("CALS_200_Bridge3", true)
  game.Compass.SetGatewayMarkerIsOpen("CALS_200_Bridge11", true)
  game.Compass.SetGatewayMarkerIsOpen("CALS_200_Bridge9", true)
  if game.Level.GetVariable("CompletedCineNumber") >= 340 then
    game.Compass.SetGatewayMarkerIsOpen("ISW_910_Helper7", true)
  end
  if game.Level.GetVariable("CompletedCineNumber") < 340 then
    game.Compass.SetGatewayMarkerIsOpen("CALS_200_Snake", true)
    game.Compass.SetGatewayMarkerIsOpen("CALS_200_Snake2", true)
  end
end
function BoatPrepareToDockUpdate()
  local boatLinearVelocityXZ = game.Boat.GetLinearVelocity(boat)
  boatLinearVelocityXZ.y = 0
  local boatLinearSpeedXZ = boatLinearVelocityXZ:Length()
  if isBoatStopping_BoatDocking then
    if boatLinearSpeedXZ < maxBoatStoppedLinearSpeed_BoatDocking then
      isBoatStopping_BoatDocking = false
      TriggerBoatDockingOrCross(boat)
    end
  elseif boatCrossMove_BoatDocking ~= "" then
    if 0 < boatCrossMoveFrames_BoatDocking then
      boatCrossMoveFrames_BoatDocking = boatCrossMoveFrames_BoatDocking - 1
    else
      local isPlayingMove = player:IsPlayingMove("MOV_" .. tostring(boatCrossMove_BoatDocking))
      if isPlayingMove and player:GetActiveMovePercent() > maxBoatCrossMovePercent_BoatDocking then
        print("BoatDocking: cross move finished")
        boatCrossMove_BoatDocking = ""
        boatCrossMoveFrames_BoatDocking = maxBoatCrossMoveFrames_BoatDocking
        Dock(boat, cachedBoatFacingRightForAfterCross, cachedDockMoveForAfterCross)
      end
    end
  else
    local blockedByInteract = false
    local son = game.AI.FindSon()
    if son and son.HasInteractAvailable and (son:HasInteractAvailable() or son:IsInteracting()) then
      blockedByInteract = true
    end
    if player.HasInteractAvailable and (player:HasInteractAvailable() or player:IsInteracting()) then
      blockedByInteract = true
    end
    local approachPointObj
    local isPluckLootAvailable = false
    if not blockedByInteract and not game.Cinematics.IsInCinematicMode() then
      isPluckLootAvailable, approachPointObj = FindBestApproachPoint("PluckLoot")
    end
    local isDockPointAvailable = false
    if isPluckLootAvailable == false and not blockedByInteract then
      isDockPointAvailable, approachPointObj = FindBestApproachPoint("DockPoint")
    end
    local padPressed = pad.CircleDown
    local worldSnakeIntroHack_Enabled = player:HasMarker("worldSnakeIntroDocking")
    if worldSnakeIntroHack_Enabled then
      padPressed = pad.SquareDown
    end
    if (isDockPointAvailable or isPluckLootAvailable) and padPressed == false then
      local objectToDisplayMarkerOn = approachPointObj
      if worldSnakeIntroHack_Enabled then
        local boatPosition = boat:GetWorldPosition()
        local worldSnakeIntroPrompts = game.World.FindGameObjectsByMarker("WorldSnakeIntroPrompt", boatPosition, 20)
        if #worldSnakeIntroPrompts == 1 then
          objectToDisplayMarkerOn = worldSnakeIntroPrompts[1]
        end
      end
      if objectWithDockingMarker ~= objectToDisplayMarkerOn then
        if objectWithDockingMarker ~= nil then
          if worldSnakeIntroHack_Enabled == true then
            mpicon.level.Off(objectWithDockingMarker, "WORLD_INTERACT_SON")
          else
            mpicon.level.Off(objectWithDockingMarker, "WORLD_INTERACT")
          end
        end
        if worldSnakeIntroHack_Enabled then
          mpicon.level.Create(objectToDisplayMarkerOn, "WORLD_INTERACT_SON")
        else
          mpicon.level.Create(objectToDisplayMarkerOn, "WORLD_INTERACT")
        end
      end
      objectWithDockingMarker = objectToDisplayMarkerOn
    elseif objectWithDockingMarker ~= nil then
      if worldSnakeIntroHack_Enabled == true then
        mpicon.level.Off(objectWithDockingMarker, "WORLD_INTERACT_SON")
      else
        mpicon.level.Off(objectWithDockingMarker, "WORLD_INTERACT")
      end
      objectWithDockingMarker = nil
    end
    if (isPluckLootAvailable or isDockPointAvailable) and padPressed then
      if isPluckLootAvailable then
        player:AddMarker(pendingDockCinematicMarkerName)
        player:AddMarker(pluckLootMarkerName)
        local sonForMarker = game.AI.FindSon()
        sonForMarker:AddMarker(pendingDockCinematicMarkerName)
        sonForMarker:AddMarker(pluckLootMarkerName)
        boat:AddMarker(pendingDockCinematicMarkerName)
        boat:AddMarker(pluckLootMarkerName)
        if game.Boat.XXX_KeepTimeInBoatWhenDocking ~= nil then
          game.Boat.XXX_KeepTimeInBoatWhenDocking()
        end
      else
        NotifyDockSystem_PlayerExitingBoat("dock", approachPointObj)
        SetSon_BoatContextOn(false)
      end
      if boatLinearSpeedXZ > minBoatStopLinearSpeed_BoatDocking then
        isBoatStopping_BoatDocking = true
        TriggerBoatDockingStop(boat)
      else
        TriggerBoatDockingOrCross(boat)
      end
    end
    if not blockedByInteract then
      local foundBeachPoint, beachLocInfo = FindBeachingPoint()
      if foundBeachPoint then
        BeachDock(beachLocInfo)
        SetSon_BoatContextOn(false)
      end
    end
  end
end
function HideBeachedBoatsPing()
  if hideBeachedBoatsPing_Enabled then
    timer.StartLevelTimer(hideBeachedBoatsPing_Time, HideBeachedBoatsPing)
    if boatDockingState == dockingState_InBoat then
      local docks = game.World.FindGameObjectsByMarker("BoatDock")
      for _, dock in pairs(docks) do
        if dock.LuaObjectScript ~= nil then
          dock.LuaObjectScript.NotifyDockSystem_PlayerEnteringBoat(nil, nil, boat)
        else
          dock:CallScript("NotifyDockSystem_PlayerEnteringBoat", boat)
        end
      end
    end
  end
end
function StartHideBeachedBoatsPing()
  if hideBeachedBoatsPing_Enabled == false then
    hideBeachedBoatsPing_Enabled = true
    timer.StartLevelTimer(hideBeachedBoatsPing_Time, HideBeachedBoatsPing)
  end
end
function StopHideBeachedBoatsPing()
  hideBeachedBoatsPing_Enabled = false
end
function HandleCineDockingOverrideRequest(level, obj)
  boat:AddMarker("pendingDockCinematic")
  player:AddMarker("pendingDockCinematic")
  local son = game.AI.FindSon()
  if son then
    son:AddMarker("pendingDockCinematic")
  end
  if player:HasMarker("worldSnakeIntroDocking") == false then
    player:CallScript("DisableNextDockingCheckpoint")
  end
end
function SetVehicleConfiguration(level, obj, boat, config, deferVisibility)
  if boat == nil then
    return
  end
  if deferVisibility == nil then
    deferVisibility = false
  end
  myConfig = config
  if config == nil and player.GroundLevel and string.find(player.GroundLevel.Name, "Alf") ~= nil then
    myConfig = "Alfheim"
  end
  local alfModel = boat:FindSingleGOByName("boat00_Alfheim")
  alfModel = alfModel and alfModel.Child
  local midModel = boat:FindSingleGOByName("boat00_Midgard")
  midModel = midModel and midModel.Child
  if deferVisibility then
    if alfModel then
      alfModel:HideModel()
      alfModel:HideLights()
    end
    if midModel then
      midModel:HideModel()
    end
    LD.CallFunctionAfterDelay(function()
      SetVehicleConfiguration(level, obj, boat, config, false)
    end, 0.45)
  elseif myConfig == "Alfheim" then
    if alfModel then
      alfModel:ShowModel()
      alfModel:ShowLights()
    end
    if midModel then
      midModel:HideModel()
    end
  else
    myConfig = "Default"
    if alfModel then
      alfModel:HideModel()
      alfModel:HideLights()
    end
    if midModel then
      midModel:ShowModel()
    end
  end
end
function DestroyBoat()
  if player:HasMarker("worldSnakeIntroDocking") then
    return
  end
  if boat ~= nil then
    boat:Destroy()
  end
end
function NotifyDockSystem_PlayerEnteringBoat(level, obj, boatObj)
  game.Combat.UnlockCombatStatus()
  if boatObj then
    boatObj:HideNavObstacle()
  end
  local docks = game.World.FindGameObjectsByMarker("BoatDock")
  for _, dock in pairs(docks) do
    if dock.LuaObjectScript ~= nil then
      dock.LuaObjectScript.NotifyDockSystem_PlayerEnteringBoat(nil, nil, boatObj)
    else
      dock:CallScript("NotifyDockSystem_PlayerEnteringBoat", boatObj)
    end
  end
  StartHideBeachedBoatsPing()
end
function NotifyDockSystem_PlayerExitingBoat(dockType, dockInfo)
  StopHideBeachedBoatsPing()
  if boat then
    boat:ShowNavObstacle()
  end
  local allDocks = game.World.FindGameObjectsByMarker("BoatDock")
  local targetDock
  if dockType == "dock" then
    targetDock = dockInfo
    while targetDock ~= nil do
      targetDock = targetDock.Parent
      if targetDock:HasMarker("BoatDock") then
        break
      end
    end
  elseif dockType == "beach" then
    local closestDistance = 99999
    for _, dock in pairs(allDocks) do
      local distanceToDock = game.AIUtil.Distance(dock:GetWorldPosition(), dockInfo)
      if closestDistance > distanceToDock then
        closestDistance = distanceToDock
        targetDock = dock
      end
    end
    dockInfo = nil
  end
  local myActiveDock = false
  for _, dock in pairs(allDocks) do
    if dock == targetDock then
      myActiveDock = true
    else
      myActiveDock = false
    end
    if dock.LuaObjectScript ~= nil then
      dock.LuaObjectScript.NotifyDockSystem_PlayerExitingBoat(nil, nil, myActiveDock, dockInfo)
    else
      dock:CallScript("NotifyDockSystem_PlayerExitingBoat", myActiveDock, dockInfo)
    end
  end
end
function UpdateInteractZones(boatDockingState)
  local shouldEnableDockInteractionZone = boatDockingState == dockingState_Docked and approachingBoat == false
  local shouldEnableBeachInteractionZone = boatDockingState == dockingState_Beached and approachingBoat == false
  if isInteractionDisabled or player:IsDoingSyncMove() then
    shouldEnableDockInteractionZone = false
    shouldEnableBeachInteractionZone = false
  end
  if shouldEnableDockInteractionZone and boatRightEnterFromDockInteractZone:IsEnabled() == false then
    print("Enabling Right Dock Interact Zone")
    boatRightEnterFromDockInteractZone:Enable()
  end
  if shouldEnableDockInteractionZone and boatLeftEnterFromDockInteractZone:IsEnabled() == false then
    print("Enabling Left Dock Interact Zone")
    boatLeftEnterFromDockInteractZone:Enable()
  end
  if shouldEnableBeachInteractionZone and boatLeftEnterFromBeachInteractZone:IsEnabled() == false then
    print("Enabling Left Beach Interact Zone")
    boatLeftEnterFromBeachInteractZone:Enable()
  end
  if shouldEnableBeachInteractionZone and boatRightEnterFromBeachInteractZone:IsEnabled() == false then
    print("Enabling Right Beach Interact Zone")
    boatRightEnterFromBeachInteractZone:Enable()
  end
  if shouldEnableDockInteractionZone == false and boatRightEnterFromDockInteractZone:IsEnabled() == true then
    print("Disabling Right Dock Interact Zone")
    boatRightEnterFromDockInteractZone:Disable()
  end
  if shouldEnableDockInteractionZone == false and boatLeftEnterFromDockInteractZone:IsEnabled() == true then
    print("Disabling Left Dock Interact Zone")
    boatLeftEnterFromDockInteractZone:Disable()
  end
  if shouldEnableBeachInteractionZone == false and boatLeftEnterFromBeachInteractZone:IsEnabled() == true then
    print("Disabling Left Beach Interact Zone")
    boatLeftEnterFromBeachInteractZone:Disable()
  end
  if shouldEnableBeachInteractionZone == false and boatRightEnterFromBeachInteractZone:IsEnabled() == true then
    print("Disabling Right Beach Interact Zone")
    boatRightEnterFromBeachInteractZone:Disable()
  end
  if game.CHECK_FEATURE("MANUAL_FILTER_INTERACTS") then
    local interactZonePosition
    local rightDockEnabled = boatRightEnterFromDockInteractZone:IsEnabled()
    local leftDockEnabled = boatLeftEnterFromDockInteractZone:IsEnabled()
    local rightBeachEnabled = boatRightEnterFromBeachInteractZone:IsEnabled()
    local leftBeachEnabled = boatLeftEnterFromBeachInteractZone:IsEnabled()
    if rightDockEnabled then
      interactZonePosition = boat:GetWorldJointPosition(boat:GetJointIndex("rightEnterJoint"))
    elseif leftDockEnabled then
      interactZonePosition = boat:GetWorldJointPosition(boat:GetJointIndex("leftEnterJoint"))
    elseif rightBeachEnabled then
      interactZonePosition = boat:GetWorldJointPosition(boat:GetJointIndex("beachRightEnterJoint"))
    elseif leftBeachEnabled then
      interactZonePosition = boat:GetWorldJointPosition(boat:GetJointIndex("beachLeftEnterJoint"))
    end
    if rightDockEnabled or leftDockEnabled or rightBeachEnabled or leftBeachEnabled then
      local playerPosition = player:GetWorldPosition()
      local playerDistanceToBoat = (playerPosition - interactZonePosition):Length()
      local shouldFilterInteract = false
      local enemies = DL.FindLivingEnemies(boat, 9)
      for i = 1, #enemies do
        local enemyDistanceToBoat = (enemies[i]:GetWorldPosition() - interactZonePosition):Length()
        if playerDistanceToBoat > enemyDistanceToBoat then
          shouldFilterInteract = true
          break
        end
      end
      if rightDockEnabled then
        boatRightEnterFromDockInteractZone:SetManuallyFilteredOut(shouldFilterInteract)
      elseif leftDockEnabled then
        boatLeftEnterFromDockInteractZone:SetManuallyFilteredOut(shouldFilterInteract)
      elseif rightBeachEnabled then
        boatRightEnterFromBeachInteractZone:SetManuallyFilteredOut(shouldFilterInteract)
      elseif leftBeachEnabled then
        boatLeftEnterFromBeachInteractZone:SetManuallyFilteredOut(shouldFilterInteract)
      end
    end
  end
end
function OnUseWorld(level, obj)
  print("On Use docking state", boatDockingState)
end
local numRecentDockingStateTransitions = 0
function AddRecentDockingStateTransition()
  numRecentDockingStateTransitions = numRecentDockingStateTransitions + 1
  print("numRecentTransitions", numRecentDockingStateTransitions)
  local timeToConsiderRecent = 0.2
  timer.StartLevelTimer(timeToConsiderRecent, RemoveRecentDockingStateTransition)
end
function RemoveRecentDockingStateTransition()
  numRecentDockingStateTransitions = numRecentDockingStateTransitions - 1
  print("numRecentTransitions", numRecentDockingStateTransitions)
end
function AnyRecentDockingStateTransitions()
  return 0 < numRecentDockingStateTransitions
end
function DisableInteractZone()
  isInteractionDisabled = true
end
function EnableInteractZone()
  isInteractionDisabled = false
end
local sonPuppeteer, son, boatPuppeteer, enterBranchName
function GivePaddle()
  if player.GroundLevel == nil then
    return
  end
  local levelName = player.GroundLevel.Name
  print("Level name = ", levelName)
  if string.match(levelName, "Alf") then
    player:TriggerMoveEvent("kAcquireAlfheimPaddle")
  else
    player:TriggerMoveEvent("kAcquireDefaultPaddle")
  end
end
function ApproachAndEnterBoatFromDock(obj)
  print("Approaching docked boat")
  local playerPosition = player:GetWorldPosition()
  local camEnterBoatL = "ENV_Boat_Pre_Enter_L"
  local camEnterBoatR = "ENV_Boat_Pre_Enter_R"
  local camEnterBoatType = "ENV_Boat_Pre_Enter"
  local boatPosition = boat:GetWorldPosition()
  local toBoat = boatPosition - playerPosition
  local boatLeft = boat:GetWorldLeft()
  print("Boat Position", boatPosition)
  print("Boat Left", boatLeft)
  local interactZoneToUse
  if toBoat:Dot(boatLeft) > 0 then
    print("Approaching from right")
    boatAngle = LD.GetAngleBetweenVector(-boat:GetWorldLeft(), cameraWorldSpaceForward) - 30
    if player:PickupIsAcquired("MimirHead") then
      enterBranchName = "BRA_BoatEnterRMimir"
    else
      enterBranchName = "BRA_BoatEnterR"
    end
    interactZoneToUse = boatRightEnterFromDockInteractZone
    camEnterBoatType = camEnterBoatR
  else
    print("Approaching from left")
    boatAngle = LD.GetAngleBetweenVector(boat:GetWorldLeft(), cameraWorldSpaceForward) + 30
    if player:PickupIsAcquired("MimirHead") then
      enterBranchName = "BRA_BoatEnterLMimir"
    else
      enterBranchName = "BRA_BoatEnterL"
    end
    interactZoneToUse = boatLeftEnterFromDockInteractZone
    camEnterBoatType = camEnterBoatL
  end
  ApproachAndEnterBoat(obj, interactZoneToUse, true)
  camBoatYaw = {Yaw = boatAngle, Pitch = 10}
  game.Camera.Recenter(recenter_boatPreBoatEnterControlLock)
  if toBoat:Length() > 3.5 then
    camera_BoatEnterFar = CCOS.OneShotCamera.New(camEnterBoatType, 3.2, camBoatYaw)
    camera_BoatEnterFar:Start()
  else
    camera_BoatEnterNear = CCOS.OneShotCamera.New(camEnterBoatType, 2.2, camBoatYaw)
    camera_BoatEnterNear:Start()
  end
  NotifyDockSystem_PlayerEnteringBoat(nil, nil, boat)
end
function ApproachAndEnterBoatFromBeach(obj)
  print("Approaching beached boat")
  local camEnterBeachType = "ENV_Boat_Beach_Pre_Enter"
  local camEnterBeachL = "ENV_Boat_Beach_Pre_Enter_L"
  local camEnterBeachR = "ENV_Boat_Beach_Pre_Enter_R"
  local playerPosition = player:GetWorldPosition()
  local boatPosition = boat:GetWorldPosition()
  local toBoat = boatPosition - playerPosition
  local boatLeft = boat:GetWorldLeft()
  print("Boat Position", boatPosition)
  print("Boat Left", boatLeft)
  local interactZoneToUse
  if toBoat:Dot(boatLeft) > 0 then
    print("Approaching from right")
    boatAngle = LD.GetAngleBetweenVector(-boat:GetWorldLeft(), cameraWorldSpaceForward) - 80
    if player:PickupIsAcquired("MimirHead") then
      enterBranchName = "BRA_BoatEnterFromBeachRMimir"
    else
      enterBranchName = "BRA_BoatEnterFromBeachR"
    end
    interactZoneToUse = boatRightEnterFromBeachInteractZone
    camEnterBeachType = camEnterBeachR
  else
    print("Approaching from left")
    boatAngle = LD.GetAngleBetweenVector(boat:GetWorldLeft(), cameraWorldSpaceForward) + 80
    if player:PickupIsAcquired("MimirHead") then
      enterBranchName = "BRA_BoatEnterFromBeachLMimir"
    else
      enterBranchName = "BRA_BoatEnterFromBeachL"
    end
    interactZoneToUse = boatLeftEnterFromBeachInteractZone
    camEnterBeachType = camEnterBeachL
  end
  camBoatYaw = {Yaw = boatAngle, Pitch = -15}
  game.Camera.Recenter(recenter_boatPreBoatEnterControlLock)
  if toBoat:Length() > 3.5 then
    camera_BoatBeachEnterFar = CCOS.OneShotCamera.New(camEnterBeachType, 4, camBoatYaw)
    camera_BoatBeachEnterFar:Start()
  else
    camera_BoatBeachEnterNear = CCOS.OneShotCamera.New(camEnterBeachType, 3, camBoatYaw)
    camera_BoatBeachEnterNear:Start()
  end
  ApproachAndEnterBoat(obj, interactZoneToUse, false)
  NotifyDockSystem_PlayerEnteringBoat(nil, nil, boat)
end
local heroPuppeteer
local pendingEnterBranch = false
local requiredStopMoveCompletion = 0.9
local stopMoveStarted = false
local enterBoatInteractionZone
function ApproachAndEnterBoat(obj, interactionZone, onDock)
  game.World.StoreCheckpoint()
  game.Interact.DisableTags("NotAllowedOnBoat")
  game.Boat.SetApproachingBoat(boat)
  approachingBoat = true
  son = game.AI.FindSon()
  heroPuppeteer = game.Puppeteer.NewForce(obj, "boatEnterHero", player)
  if son ~= nil then
    sonPuppeteer = game.Puppeteer.NewForce(obj, "boatEnterSon", son)
  end
  boatPuppeteer = game.Puppeteer.NewForce(obj, "boatEnterBoat", boat)
  player:MarkCurrentWeaponMode()
  if game.Level.GetVariable("CompletedCineNumber") == 165 then
    heroPuppeteer:WeaponEquip({weaponMode = "BareOnBack"})
  else
    heroPuppeteer:WeaponEquip({weaponMode = "Bare"})
  end
  print(enterBranchName)
  if player.SetAccelerationOverride ~= nil then
    player:SetAccelerationOverride(4)
  end
  if player.SetDecelerationOverride ~= nil then
    player:SetDecelerationOverride(3)
  end
  enterBoatInteractionZone = interactionZone
  if onDock then
    heroPuppeteer:Approach({
      branch_name = enterBranchName,
      joint_name = "levelHelperSynchJoint",
      ignore_navmesh = true,
      speed = 3,
      complete_radius = 0.1,
      close_range_distance = 1,
      focus_end_direction = true,
      disallow_very_close_behavior = false,
      stop = false,
      strafe_distance = 1.5
    })
  else
    heroPuppeteer:Approach({
      branch_name = enterBranchName,
      joint_name = "levelHelperSynchJoint",
      ignore_navmesh = true,
      speed = 4,
      complete_radius = 0.1,
      close_range_distance = 1,
      focus_end_direction = true,
      disallow_very_close_behavior = false,
      stop = false,
      strafe_distance = 1.8
    })
  end
  heroPuppeteer:OnComplete(EnterBoat)
end
function EnterBoat()
  print("trying to enter boat")
  player:RequestInteract(boat, enterBoatInteractionZone)
  GivePaddle()
  inBoatEnter = true
  heroPuppeteer:Clear()
  heroPuppeteer:AcceptSync()
  if son ~= nil then
    if sonPuppeteer == nil then
      sonPuppeteer = game.Puppeteer.NewForce(heroPuppeteer:GetGameObject(), "boatEnterSon", son)
    end
    sonPuppeteer:AcceptSync()
    boatPuppeteer:Sync(enterBranchName, false, {
      {Slave = sonPuppeteer, Branch = enterBranchName},
      {Slave = heroPuppeteer, Branch = enterBranchName}
    })
  else
    boatPuppeteer:Sync(enterBranchName, false, {
      {Slave = heroPuppeteer, Branch = enterBranchName}
    })
  end
  SetSon_BoatContextOn(true)
  LD.PlayEnterBoatBanter()
  PlayInBoatMusic()
  uiCalls.UI_Refresh_HUD()
end
function ClearBoatApproachVariables()
  if heroPuppeteer ~= nil then
    heroPuppeteer:Clear()
    heroPuppeteer:DetachPuppet()
  end
  heroPuppeteer = nil
  if sonPuppeteer ~= nil then
    sonPuppeteer:Clear()
    sonPuppeteer:DetachPuppet()
  end
  sonPuppeteer = nil
  if boatPuppeteer ~= nil then
    boatPuppeteer:Clear()
    boatPuppeteer:DetachPuppet()
  end
  boatPuppeteer = nil
  approachingBoat = false
  player:ClearFocus()
  if player.ClearAccelerationOverride ~= nil then
    player:ClearAccelerationOverride()
  end
  if player.ClearDecelerationOverride ~= nil then
    player:ClearDecelerationOverride()
  end
end
local dockingTargetPosition, dockingTargetForwardVector, DirToEnvProbe, PosToEnvProbe, selectedApproachPoint
local isApproachPointPluckLoot = false
local approachPointSearchDistance = 9
function TriggerBoatDockingStop(boat)
  local isPaddleLeft = player:PickupIsAcquired("PaddleLeft")
  local stopMove = "BoatDockingStopLeft"
  if not isPaddleLeft then
    stopMove = "BoatDockingStopRight"
  end
  boat:GetCreature():TriggerMoveEvent(stopMove)
  print("BoatDocking: TriggerBoatDockingStop()->" .. tostring(stopMove))
end
function DetermineDockMoveAndCrossMoveForBoatDocking(boat)
  local boatForwardVector = boat:GetWorldForward()
  local boatPosition = boat:GetWorldPosition()
  local toDockPoint = dockingTargetPosition - boatPosition
  toDockPoint = toDockPoint:Normalized()
  local dockPointLeft = selectedApproachPoint:GetWorldLeft()
  local dockingParallel
  local approachAngleDot = dockPointLeft:Dot(toDockPoint)
  if math.abs(approachAngleDot) > 0.707 then
    dockingParallel = true
  else
    dockingParallel = false
  end
  local dockingStraight
  local boatLeft = boat:GetWorldLeft()
  local boatAngleDot = math.abs(boatLeft:Dot(toDockPoint))
  local boatOrientation = math.deg(math.acos(boatAngleDot))
  print("boat to approach point distance: ", selectedApproachPoint)
  if boatOrientation < 22.5 and -22.5 < boatOrientation then
    dockingStraight = true
  else
    dockingStraight = false
  end
  local facingDockPoint
  if toDockPoint:Dot(boatForwardVector) > 0 then
    facingDockPoint = true
  else
    facingDockPoint = false
  end
  local boatFacingRight
  if dockPointLeft:Dot(boatForwardVector) < 0 then
    boatFacingRight = true
  else
    boatFacingRight = false
  end
  if game.Level.GetVariable("CompletedCineNumber") < 15 then
    boatFacingRight = false
  end
  print("boat angle to dock: ", boatOrientation)
  print("docking straight: ", dockingStraight)
  print("docking parallel: ", dockingParallel)
  print("docking angle: ", approachAngleDot)
  print("boat on facing right ", boatFacingRight)
  print("facing dock point: ", facingDockPoint)
  local dockMove
  dockingTargetForwardVector = selectedApproachPoint:GetWorldLeft()
  if dockingStraight then
    if boatFacingRight then
      dockMove = "BoatDockingStraightL"
      dockingTargetForwardVector = dockingTargetForwardVector * -1
      print("straight docking")
    else
      dockMove = "BoatDockingStraightR"
    end
  elseif dockingParallel then
    if facingDockPoint then
      if boatFacingRight then
        dockMove = "BoatDockingParallelRF"
        dockingTargetForwardVector = dockingTargetForwardVector * -1
      else
        dockMove = "BoatDockingParallelLF"
      end
    elseif boatFacingRight then
      dockMove = "BoatDockingParallelLB"
      dockingTargetForwardVector = dockingTargetForwardVector * -1
    else
      dockMove = "BoatDockingParallelRB"
    end
  elseif facingDockPoint then
    if boatFacingRight then
      dockMove = "BoatDockingPerpRF"
      dockingTargetForwardVector = dockingTargetForwardVector * -1
    else
      dockMove = "BoatDockingPerpLF"
    end
  elseif boatFacingRight then
    dockMove = "BoatDockingPerpLB"
    dockingTargetForwardVector = dockingTargetForwardVector * -1
  else
    dockMove = "BoatDockingPerpRB"
  end
  local crossMove = ""
  local isPaddleLeft = player:PickupIsAcquired("PaddleLeft")
  if isPaddleLeft then
    if dockMove == "BoatDockingStraightL" or dockMove == "BoatDockingParallelRF" or dockMove == "BoatDockingParallelLB" or dockMove == "BoatDockingPerpRF" or dockMove == "BoatDockingPerpLB" then
      crossMove = "BoatDockingCrossLeft"
    end
  elseif dockMove == "BoatDockingStraightR" or dockMove == "BoatDockingParallelLF" or dockMove == "BoatDockingParallelRB" or dockMove == "BoatDockingPerpLF" or dockMove == "BoatDockingPerpRB" then
    crossMove = "BoatDockingCrossRight"
  end
  return boatFacingRight, dockMove, crossMove
end
function Dock(boat, boatFacingRight, dockMove)
  print("dock move: ", dockMove)
  print("dock target forward vector: ", dockingTargetForwardVector)
  print("dock target position: ", dockingTargetPosition)
  DirToEnvProbe = boat:GetAnimDriver("DirToEnvProbe")
  PosToEnvProbe = boat:GetAnimDriver("PosToEnvProbe")
  DirToEnvProbe.ValueVec = dockingTargetForwardVector
  PosToEnvProbe.ValueVec = dockingTargetPosition
  boat:GetCreature():TriggerMoveEvent(dockMove)
  local camEnterDockType = "ENV_Boat_Dock_Pre_Enter"
  dockAngle = LD.GetAngleBetweenVector(-selectedApproachPoint:GetWorldForward(), cameraWorldSpaceForward)
  if boatFacingRight == true then
    dockAngle = dockAngle - 36
  else
    dockAngle = dockAngle + 36
  end
  camDockYaw = {Yaw = dockAngle, Pitch = -6}
  if isApproachPointPluckLoot == false then
    local toDockPoint = selectedApproachPoint:GetWorldPosition() - boat:GetWorldPosition()
    print("TO DOCK LENGTH: ", toDockPoint:Length())
    if toDockPoint:Length() < 4 then
      camera_DockEnterNear = CCOS.OneShotCamera.New(camEnterDockType, 5, camDockYaw)
      camera_DockEnterNear:Start()
      game.Camera.Recenter({
        TimeStart = 0,
        TimeDuration = 4,
        LockRecenter = 1,
        YawRange = -1,
        TriggerLeft = 0,
        TriggerRight = 0,
        ReturnLeft = 180,
        ReturnRight = -180,
        PitchRange = 0
      })
    else
      camera_DockEnterNear = CCOS.OneShotCamera.New(camEnterDockType, 7, camDockYaw)
      camera_DockEnterNear:Start()
      game.Camera.Recenter({
        TimeStart = 0,
        TimeDuration = 4,
        LockRecenter = 1,
        YawRange = -1,
        TriggerLeft = 0,
        TriggerRight = 0,
        ReturnLeft = 180,
        ReturnRight = -180,
        PitchRange = 0
      })
    end
  end
end
function TriggerBoatDockingOrCross(boat)
  local boatFacingRight, dockMove, crossMove = DetermineDockMoveAndCrossMoveForBoatDocking(boat)
  if crossMove == "" then
    Dock(boat, boatFacingRight, dockMove)
  else
    boatCrossMove_BoatDocking = crossMove
    boatCrossMoveFrames_BoatDocking = maxBoatCrossMoveFrames_BoatDocking
    cachedDockMoveForAfterCross = dockMove
    cachedBoatFacingRightForAfterCross = boatFacingRight
    boat:GetCreature():TriggerMoveEvent(boatCrossMove_BoatDocking)
    print("BoatDocking: TriggerBoatDockingOrCross()->" .. tostring(boatCrossMove_BoatDocking))
  end
end
function FindBestApproachPoint(approachPointName)
  local boatPosition = boat:GetWorldPosition()
  local boatForward = boat:GetWorldForward()
  local allApproachPoints = game.World.FindGameObjectsByMarker(approachPointName, boatPosition, approachPointSearchDistance)
  if #allApproachPoints == 0 then
    return false
  end
  local isDockPoint = false
  if approachPointName == "DockPoint" then
    isDockPoint = true
  end
  local maxAdditionalOrientationCost = 7
  local closestApproachPointCost = approachPointSearchDistance + maxAdditionalOrientationCost + 1
  selectedApproachPoint = nil
  for i = 1, #allApproachPoints do
    local isApproachPointValid = true
    local toApproachPointVec = allApproachPoints[i]:GetWorldPosition() - boatPosition
    local toApproachPoint = toApproachPointVec:Length()
    if isDockPoint then
      local dockPointFoward = allApproachPoints[i]:GetWorldForward()
      local angleError = math.asin(boatForward:Dot(dockPointFoward))
      angleError = math.abs(angleError * 2 / math.pi)
      local angleAdditionalCost = angleError * maxAdditionalOrientationCost
      local distanceBehindDockPoint = -dockPointFoward:Dot(toApproachPointVec)
      toApproachPoint = toApproachPoint + angleAdditionalCost
      if 0.25 < distanceBehindDockPoint then
        isApproachPointValid = player:HasMarker("worldSnakeIntroDocking")
      end
    end
    if closestApproachPointCost > toApproachPoint and isApproachPointValid then
      closestApproachPointCost = toApproachPoint
      selectedApproachPoint = allApproachPoints[i]
    end
  end
  if selectedApproachPoint == nil then
    print("Didn't find any nearby docking points")
    return false
  end
  dockingTargetPosition = selectedApproachPoint:GetWorldPosition()
  dockingTargetPosition.y = boatPosition.y
  if isDockPoint == false then
    local pluckLootOffset = selectedApproachPoint:GetWorldLeft()
    if 0 <= boatForward:Dot(pluckLootOffset) then
      dockingTargetPosition = dockingTargetPosition - pluckLootOffset
    else
      dockingTargetPosition = dockingTargetPosition + pluckLootOffset
    end
    isApproachPointPluckLoot = true
  else
    isApproachPointPluckLoot = false
  end
  return true, selectedApproachPoint
end
function FindBeachingPoint()
  beachingTargetPosition, beachingTargetForwardVector, beachApproach = game.Boat.GetBeachPoint(boat)
  if beachingTargetPosition == nil then
    mpicon.level.Off(boat, "WORLD_INTERACT")
    return false
  end
  if pad.CircleDown == false then
    mpicon.level.Create(boat, "WORLD_INTERACT")
    return false
  else
    mpicon.level.Off(boat, "WORLD_INTERACT")
    return true, beachingTargetPosition
  end
end
local beachApproach_Right = 0
local beachApproach_HeadOnTurnRight = 1
local beachApproach_HeadOnTurnLeft = 2
local beachApporach_Left = 3
function BeachDock(beachLocInfo)
  local BeachDockMove
  DirToEnvProbe = boat:GetAnimDriver("DirToEnvProbe")
  PosToEnvProbe = boat:GetAnimDriver("PosToEnvProbe")
  local isPaddleLeft = player:PickupIsAcquired("PaddleLeft")
  if player.GroundLevel == nil then
    return
  end
  local levelName = player.GroundLevel.Name
  DirToEnvProbe.ValueVec = beachingTargetForwardVector
  PosToEnvProbe.ValueVec = beachingTargetPosition
  if isPaddleLeft then
    if beachApproach == beachApporach_Left then
      BeachDockMove = "BoatExitToBeachLpaddleFromL"
    elseif beachApproach == beachApproach_HeadOnTurnLeft then
      BeachDockMove = "BoatExitToBeachLpaddleFromCenterL"
    elseif beachApproach == beachApproach_HeadOnTurnRight then
      BeachDockMove = "BoatExitToBeachRpaddleFromCenterR"
    else
      BeachDockMove = "BoatExitToBeachLpaddleFromR"
    end
  elseif beachApproach == beachApporach_Left then
    BeachDockMove = "BoatExitToBeachRpaddleFromL"
  elseif beachApproach == beachApproach_HeadOnTurnLeft then
    BeachDockMove = "BoatExitToBeachLpaddleFromCenterL"
  elseif beachApproach == beachApproach_HeadOnTurnRight then
    BeachDockMove = "BoatExitToBeachRpaddleFromCenterR"
  else
    BeachDockMove = "BoatExitToBeachRpaddleFromR"
  end
  if player:PickupIsAcquired("MimirHead") then
    BeachDockMove = BeachDockMove .. "Mimir"
  end
  boat:GetCreature():TriggerMoveEvent(BeachDockMove)
  print("BeachDock Move = ", BeachDockMove)
  NotifyDockSystem_PlayerExitingBoat("beach", beachLocInfo)
end
function OnBoatDockingStateChanged(prevState, newState)
  if prevState ~= nil then
    if prevState == dockingState_InBoat then
      PlayDockedMusic()
    elseif prevState == dockingState_Beached then
      StopBeachedSound()
    end
  end
  if newState == dockingState_Docked then
    PlayIdleSound()
  elseif newState == dockingState_InBoat then
    PlayIdleSound()
    inBoatEnter = false
  elseif newState == dockingState_Beached then
    PlayBeachedSound()
  end
  print(prevState, newState)
end
function RecordBoatBlackBoardInfo(dockingState)
end
function SetSon_BoatContextOn(turnOn)
  local son = game.AI.FindSon()
  if son ~= nil and son:IsAvailableInLevel() then
    if turnOn and not son:IsContextBehaviorNameEqualTo("BOAT_SICK_CONTEXT_CONFIG") then
      son:CallScript("EnterBehaviorContext", "BOAT_CONTEXT_CONFIG_NORMAL")
    else
      son:CallScript("ClearThisBehaviorContextIfActive", "BOAT_CONTEXT_CONFIG_NORMAL")
    end
  end
end
local soundEmitterCenter, soundEmitterBow
local soundEvents = {
  IdleBow = "SND_VEH_Boat_Water_LP",
  IdleBeached = "SND_VEH_Boat_Docked_Beach_LP"
}
function SoundInit()
  soundEmitterCenter = boat:FindSingleSoundEmitterByName("SNDCenter")
  soundEmitterBow = boat:FindSingleSoundEmitterByName("SNDBow")
end
function PlayBeachedSound()
  LD.PlaySound(soundEmitterBow, soundEvents.IdleBeached)
end
function PlayIdleSound()
  LD.PlaySound(soundEmitterCenter, soundEvents.IdleBow)
end
function StopBeachedSound()
  LD.StopSound(soundEmitterCenter, soundEvents.IdleBeached)
end
function StopDockedSound()
  LD.StopSound(soundEmitterCenter, soundEvents.IdleDockedCenter)
end
function PlayInBoatMusic()
  local musicToPlay
  local krato = game.Player.FindPlayer()
  if game.FindLevel("Riv925_FreyaCave") and game.Level.GetVariable("CompletedCineNumber") >= 165 and game.Level.GetVariable("CompletedCineNumber") < 180 then
    musicToPlay = "SND_MX_RIV_freya_cavern_explore2_out"
    LD.CallFunctionAfterDelay(musicBoatExploreIn, 0.5)
  elseif game.FindLevel("Cal820_SnakeBellyMid") then
    game.Audio.StartCheckpointedMusic("SND_MX_CAL_snake_belly_boat_in")
  elseif game.FindLevel("Stn100_Entrance") then
    LD.CallFunctionAfterDelay(musicBoatExploreIn, 10)
  else
    game.Audio.StartCheckpointedMusic("SND_MX_CAL_boat_explore1")
  end
  if musicToPlay ~= nil then
    game.Audio.StartMusic(musicToPlay)
  end
end
function PlayDockedMusic()
  local musicToPlay
  local son = game.AI.FindSon()
  if game.FindLevel("Cal050_Sound") and not son:IsPlayingMove("MOV_PluckLoot") and not game.FindLevel("Cal820_SnakeBellyMid") then
    LD.CallFunctionAfterDelay(musicBoatExploreOut, 0.5)
  elseif game.FindLevel("Cal050_Sound") and game.FindLevel("Cal820_SnakeBellyMid") then
    musicToPlay = "SND_MX_CAL_snake_belly_boat_out"
  end
  if musicToPlay ~= nil then
    game.Audio.StartMusic(musicToPlay)
  end
end
function musicBoatExploreIn()
  game.Audio.StartCheckpointedMusic("SND_MX_CAL_boat_explore1")
end
function musicBoatExploreOut()
  game.Audio.StartMusic("SND_MX_CAL_boat_explore1_out")
end
