local profile = require("core.profile")
local SyncAndPlayBranch = function(ai, puppeteerObj, branch, useGOAsReference, jointName)
  local aiPuppeteer = game.Puppeteer.NewForce(puppeteerObj, "LD Lib - Sync And Play Branch", ai)
  if jointName ~= "" and jointName ~= nil then
    aiPuppeteer:Sync(branch, useGOAsReference, {}, jointName)
  else
    aiPuppeteer:Sync(branch, useGOAsReference, {})
  end
  aiPuppeteer:OnComplete(function()
    aiPuppeteer:Clear()
    aiPuppeteer = nil
  end)
end
local Advertise_SyncAndPlayBranch = function(ai, puppeteerObj, branch, useGOAsReference, jointName)
  local aiPuppeteer = game.Puppeteer.New(puppeteerObj, "LDLib - Advertise AI SyncAndPlayBranch", ai)
  local aiName = ai:GetName()
  aiName = string.gsub(aiName, "%d", "")
  aiName = string.upper(aiName)
  aiPuppeteer:Advertise(aiName .. ":AUTO")
  aiPuppeteer:OnEngaged(function()
    if jointName ~= "" and jointName ~= nil then
      aiPuppeteer:Sync(branch, useGOAsReference, {}, jointName)
    else
      aiPuppeteer:Sync(branch, useGOAsReference, {})
    end
    aiPuppeteer:OnComplete(function()
      aiPuppeteer:Clear()
      aiPuppeteer = nil
    end)
  end)
end
local GoToLocation = function(ai, puppeteerObj, runSpeed, jointName, aiPuppeteer)
  if aiPuppeteer == nil then
    aiPuppeteer = game.Puppeteer.NewForce(puppeteerObj, "LDLib - AI go to location", ai)
  end
  if jointName ~= "" and jointName ~= nil then
    aiPuppeteer:Approach({
      pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
      speed = runSpeed or 1,
      stop = true
    })
    aiPuppeteer:OnArrival(function()
      aiPuppeteer:Clear()
      aiPuppeteer = nil
    end, {
      pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
      radius = 1
    })
  else
    aiPuppeteer:Approach({
      pos = puppeteerObj:GetWorldPosition(),
      speed = runSpeed or 1,
      stop = true
    })
    aiPuppeteer:OnArrival(function()
      aiPuppeteer:Clear()
      aiPuppeteer = nil
    end, {
      pos = puppeteerObj:GetWorldPosition(),
      radius = 1
    })
  end
  return aiPuppeteer
end
local Advertise_GoToLocation = function(ai, puppeteerObj, runSpeed, jointName)
  local aiPuppeteer = game.Puppeteer.New(puppeteerObj, "LDLib - Advertise AI GoToLocation", ai)
  local aiName = ai:GetName()
  aiName = string.gsub(aiName, "%d", "")
  aiName = string.upper(aiName)
  aiPuppeteer:Advertise(aiName .. ":AUTO")
  aiPuppeteer:OnEngaged(function()
    if jointName ~= "" and jointName ~= nil then
      aiPuppeteer:Approach({
        pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
        speed = runSpeed or 1,
        stop = true
      })
      aiPuppeteer:OnArrival(function()
        aiPuppeteer:Clear()
        aiPuppeteer = nil
      end, {
        pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
        radius = 1
      })
    else
      aiPuppeteer:Approach({
        pos = puppeteerObj:GetWorldPosition(),
        speed = runSpeed or 1,
        stop = true
      })
      aiPuppeteer:OnArrival(function()
        aiPuppeteer:Clear()
        aiPuppeteer = nil
      end, {
        pos = puppeteerObj:GetWorldPosition(),
        radius = 1
      })
    end
  end)
end
local AF_branch, AF_aiPuppeteer, AF_useGOAsReference, AF_jointName
local ArrivalFunction = function()
  print("playing arrival function")
  if AF_jointName ~= nil then
    AF_aiPuppeteer:Sync(AF_branch, AF_useGOAsReference, {}, AF_jointName)
  else
    AF_aiPuppeteer:Sync(AF_branch, AF_useGOAsReference, {})
  end
  AF_aiPuppeteer:OnComplete(function()
    AF_aiPuppeteer:Clear()
    AF_aiPuppeteer = nil
  end)
end
local SetArrivialFunctionParameters = function(branch, aiPuppeteer, useGOAsReference, jointName)
  AF_branch = branch
  AF_aiPuppeteer = aiPuppeteer
  AF_useGOAsReference = useGOAsReference
  AF_jointName = jointName
end
local AC_branch, AC_aiPuppeteer, AC_useGOAsReference, AC_jointName, AC_customCallback
local AlignAndArriveCompleteFunction = function()
  if AC_jointName ~= nil then
    AC_aiPuppeteer:Sync(AC_branch, AC_useGOAsReference, {}, AC_jointName)
  else
    AC_aiPuppeteer:Sync(AC_branch, AC_useGOAsReference, {})
  end
  AC_aiPuppeteer:OnComplete(function()
    AC_aiPuppeteer:Clear()
    AC_aiPuppeteer = nil
    if AC_customCallback and type(AC_customCallback) == "function" then
      AC_customCallback()
    end
  end)
end
local SetAlignCompleteFunction = function(puppeteer, puppeteerObj, branch, runSpeed, useGOAsReference, jointName, radius, customCallback)
  AC_branch = branch
  AC_aiPuppeteer = puppeteer
  AC_useGOAsReference = useGOAsReference
  AC_jointName = jointName
  AC_customCallback = customCallback
end
local MoveAndAlignToLocationSyncAndPlayBranch = function(ai, puppeteerObj, branch, runSpeed, useGOAsReference, jointName, customArrivalRadius, alignTime, customCallback)
  local arrivalRadius = 1
  if customArrivalRadius ~= nil then
    arrivalRadius = customArrivalRadius
  end
  local localAlignTime = 0.5
  if alignTime ~= nil then
    localAlignTime = alignTime
  end
  local puppetToPuppeteer, puppetToPuppeteerLength, puppetToPuppeteerLessRadius, alignPoint
  if jointName ~= nil and jointName ~= "" then
    alignPoint = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName))
    local tempY = alignPoint.y
    alignPoint.y = ai:GetWorldPosition().y
    puppetToPuppeteer = alignPoint - ai:GetWorldPosition()
    alignPoint.y = tempY
  else
    alignPoint = puppeteerObj:GetWorldPosition()
    puppetToPuppeteer = alignPoint - ai:GetWorldPosition()
  end
  puppetToPuppeteerLength = puppetToPuppeteer:Length()
  puppetToPuppeteer = puppetToPuppeteer:Normalized()
  puppetToPuppeteerLessRadius = ai:GetWorldPosition() + puppetToPuppeteer * (puppetToPuppeteerLength - arrivalRadius)
  local alignArgs = {
    pos = puppetToPuppeteerLessRadius,
    aligntime = localAlignTime,
    facing = puppetToPuppeteer
  }
  local aiPuppeteer = game.Puppeteer.NewForce(puppeteerObj, "LDLib starting align and move puppet", ai)
  SetAlignCompleteFunction(aiPuppeteer, puppeteerObj, branch, runSpeed, useGOAsReference, jointName, arrivalRadius, customCallback)
  aiPuppeteer:OnComplete(AlignAndArriveCompleteFunction)
  aiPuppeteer:Align(alignArgs)
end
local GoToLocationSyncAndPlayBranch = function(ai, puppeteerObj, branch, runSpeed, useGOAsReference, jointName01, jointName02, customArrivalRadius, stopOnArrival, foot)
  local aiPuppeteer = game.Puppeteer.NewForce(puppeteerObj, "LDLib - AI Go to location, sync and play branch", ai)
  local arrivalRadius = 1
  if customArrivalRadius ~= nil then
    arrivalRadius = customArrivalRadius
  end
  local bStopOnArrival = true
  if stopOnArrival ~= nil then
    bStopOnArrival = stopOnArrival
  end
  if jointName02 ~= "" and jointName02 ~= nil and branch ~= "" and branch ~= nil then
    aiPuppeteer:Approach({
      branch_name = branch,
      joint_name = jointName02,
      speed = runSpeed or 1,
      stop = bStopOnArrival,
      complete_radius = arrivalRadius
    })
    SetArrivialFunctionParameters(branch, aiPuppeteer, useGOAsReference, jointName02)
    if bStopOnArrival == false then
      aiPuppeteer:OnArrival(ArrivalFunction, {
        branch_name = branch,
        joint_name = jointName02,
        radius = arrivalRadius,
        foot = foot
      })
    else
      aiPuppeteer:OnComplete(ArrivalFunction)
    end
  elseif jointName01 ~= "" and jointName01 ~= nil then
    aiPuppeteer:Approach({
      pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName01)),
      speed = runSpeed or 1,
      stop = bStopOnArrival,
      complete_radius = arrivalRadius
    })
    SetArrivialFunctionParameters(branch, aiPuppeteer, useGOAsReference, jointName01)
    if bStopOnArrival == false then
      aiPuppeteer:OnArrival(ArrivalFunction, {
        pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName01)),
        radius = arrivalRadius,
        foot = foot
      })
    else
      aiPuppeteer:OnComplete(ArrivalFunction)
    end
  else
    aiPuppeteer:Approach({
      pos = puppeteerObj:GetWorldPosition(),
      speed = runSpeed or 1,
      stop = bStopOnArrival,
      complete_radius = arrivalRadius
    })
    SetArrivialFunctionParameters(branch, aiPuppeteer, useGOAsReference, nil)
    if bStopOnArrival == false then
      aiPuppeteer:OnArrival(ArrivalFunction, {
        pos = puppeteerObj:GetWorldPosition(),
        radius = arrivalRadius,
        foot = foot
      })
    else
      aiPuppeteer:OnComplete(ArrivalFunction)
    end
  end
  return aiPuppeteer
end
local Advertise_GoToLocationSyncAndPlayBranch = function(ai, puppeteerObj, branch, runSpeed, useGOAsReference, jointName01, jointName02, customArrivalRadius, stopOnArrival)
  local aiPuppeteer = game.Puppeteer.New(puppeteerObj, "LDLib - Advertise AI GoToLocationSyncAndPlayBranch", ai)
  local aiName = ai:GetName()
  aiName = string.gsub(aiName, "%d", "")
  aiName = string.upper(aiName)
  local arrivalRadius = 1
  if customArrivalRadius ~= nil then
    arrivalRadius = customArrivalRadius
  end
  local bStopOnArrival = true
  if stopOnArrival ~= nil then
    bStopOnArrival = stopOnArrival
  end
  aiPuppeteer:Advertise(aiName .. ":AUTO")
  aiPuppeteer:OnEngaged(function()
    if jointName01 ~= "" and jointName01 ~= nil then
      if jointName02 ~= "" and jointName02 ~= nil then
        aiPuppeteer:Approach({
          pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName01)),
          speed = runSpeed or 1,
          stop = bStopOnArrival
        })
        aiPuppeteer:OnArrival(function()
          aiPuppeteer:Sync(branch, useGOAsReference, {}, jointName02)
          aiPuppeteer:OnComplete(function()
            aiPuppeteer:Clear()
            aiPuppeteer = nil
          end)
        end, {
          pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName01)),
          radius = arrivalRadius
        })
      else
        aiPuppeteer:Approach({
          pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName01)),
          speed = runSpeed or 1,
          stop = bStopOnArrival
        })
        aiPuppeteer:OnArrival(function()
          aiPuppeteer:Sync(branch, useGOAsReference, {}, jointName01)
          aiPuppeteer:OnComplete(function()
            aiPuppeteer:Clear()
            aiPuppeteer = nil
          end)
        end, {
          pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName01)),
          radius = arrivalRadius
        })
      end
    else
      aiPuppeteer:Approach({
        pos = puppeteerObj:GetWorldPosition(),
        speed = runSpeed or 1,
        stop = bStopOnArrival
      })
      aiPuppeteer:OnArrival(function()
        aiPuppeteer:Sync(branch, useGOAsReference, {})
        aiPuppeteer:OnComplete(function()
          aiPuppeteer:Clear()
          aiPuppeteer = nil
        end)
      end, {
        pos = puppeteerObj:GetWorldPosition(),
        radius = arrivalRadius
      })
    end
  end)
end
local GoToLocationAlignAndWait = function(ai, puppeteerObj, runSpeed, jointName, aiPuppeteer)
  if aiPuppeteer == nil then
    aiPuppeteer = game.Puppeteer.NewForce(puppeteerObj, "LDLib - AI GoToLocationAlignAndWait", ai)
  end
  if jointName ~= "" and jointName ~= nil then
    aiPuppeteer:Approach({
      pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
      speed = runSpeed or 1,
      stop = true
    })
    aiPuppeteer:OnArrival(function()
      aiPuppeteer:Sync("BRA_SetIdle", true, {}, "synchJoint")
      aiPuppeteer:OnComplete(function()
      end)
    end, {
      pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
      radius = 2
    })
  else
    aiPuppeteer:Approach({
      pos = puppeteerObj:GetWorldPosition(),
      speed = runSpeed or 1,
      stop = true
    })
    aiPuppeteer:OnArrival(function()
      aiPuppeteer:Sync("BRA_SetIdle", true, {})
      aiPuppeteer:OnComplete(function()
      end)
    end, {
      pos = puppeteerObj:GetWorldPosition(),
      radius = 1.2
    })
  end
  return aiPuppeteer
end
local Advertise_GoToLocationAlignAndWait = function(ai, puppeteerObj, runSpeed, jointName, aiPuppeteer)
  if aiPuppeteer == nil then
    aiPuppeteer = game.Puppeteer.New(puppeteerObj, "LDLib - Advertise AI GoToLocationAlignAndWait", ai)
  end
  local aiName = ai:GetName()
  aiName = string.gsub(aiName, "%d", "")
  aiName = string.upper(aiName)
  aiPuppeteer:Advertise(aiName .. ":AUTO")
  aiPuppeteer:OnEngaged(function()
    if jointName ~= "" and jointName ~= nil then
      aiPuppeteer:Approach({
        pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
        speed = runSpeed or 1,
        stop = true
      })
      aiPuppeteer:OnArrival(function()
        aiPuppeteer:Sync("BRA_SetIdle", true, {}, "synchJoint")
        aiPuppeteer:OnComplete(function()
        end)
      end, {
        pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
        radius = 1
      })
    else
      aiPuppeteer:Approach({
        pos = puppeteerObj:GetWorldPosition(),
        speed = runSpeed or 1,
        stop = true
      })
      aiPuppeteer:OnArrival(function()
        aiPuppeteer:Sync("BRA_SetIdle", true, {})
        aiPuppeteer:OnComplete(function()
        end)
      end, {
        pos = puppeteerObj:GetWorldPosition(),
        radius = 1
      })
    end
  end)
  return aiPuppeteer
end
local GetGlobalApproachData = function()
  return {
    speed = 3,
    stop = false,
    complete_radius = 0.2,
    close_range_distance = 1.5,
    focus_end_direction = true,
    disallow_very_close_behavior = false,
    strafe_distance = 1.5,
    acceleration_override = 5.5,
    deceleration_override = 4.5
  }
end
local PlayGroupSync = function(syncObj, slave_MoveTable, animNameObject, useGOAsReference, syncObjJointName)
  local debugStringNames = ""
  for i = 2, #slave_MoveTable do
    debugStringNames = debugStringNames .. ", " .. tostring(slave_MoveTable[i][1]:GetName())
  end
  local debugStringFull = "Starting Sync With Sync Object: " .. tostring(syncObj:GetName()) .. " and Puppets: (master) " .. tostring(slave_MoveTable[1][1]:GetName()) .. debugStringNames
  local masterPuppeteer = game.Puppeteer.NewForce(syncObj, debugStringFull, slave_MoveTable[1][1])
  local slavePuppeteers = {}
  for i = 2, #slave_MoveTable do
    if slave_MoveTable[i][2] ~= nil and string.find(slave_MoveTable[i][2], "BRA") ~= nil then
      local newSlavePup = game.Puppeteer.NewForce(syncObj, debugStringFull, slave_MoveTable[i][1])
      newSlavePup:AcceptSync()
      slavePuppeteers[#slavePuppeteers + 1] = {
        Slave = newSlavePup,
        Branch = slave_MoveTable[i][2]
      }
    elseif animNameObject ~= "" and animNameObject ~= nil then
      slavePuppeteers[#slavePuppeteers + 1] = {
        Slave = slave_MoveTable[i][1],
        Anim = animNameObject
      }
    else
      slavePuppeteers[#slavePuppeteers + 1] = {
        Slave = slave_MoveTable[i][1]
      }
    end
  end
  masterPuppeteer:Sync(slave_MoveTable[1][2], useGOAsReference, slavePuppeteers, syncObjJointName)
  masterPuppeteer:OnComplete(function()
    masterPuppeteer = nil
  end)
  if animNameObject ~= "" and animNameObject ~= nil then
    syncObj:StartAnim(animNameObject)
  else
    syncObj:JumpAnimToFrame(0)
    syncObj:PlayAnimToEnd()
  end
  for i = 1, #slavePuppeteers do
    if slave_MoveTable[i + 1][1]:IsCreature() then
      slavePuppeteers[i].Slave:OnComplete(function()
        slavePuppeteers[i].Slave = nil
      end)
    end
  end
  return masterPuppeteer
end
local PlayApproach_GroupSync = function(syncObj, slave_MoveTable, objAnimName, useGOAsReference, jointName, optionalArgs)
  if optionalArgs == nil then
    optionalArgs = {}
  end
  local debugStringNames = ""
  for i = 2, #slave_MoveTable do
    debugStringNames = debugStringNames .. ", " .. tostring(slave_MoveTable[i][1]:GetName())
  end
  local debugStringFull = "Starting Sync With Sync Object: " .. tostring(syncObj:GetName()) .. " and Puppets: (master) " .. tostring(slave_MoveTable[1][1]:GetName()) .. debugStringNames
  local masterAI = slave_MoveTable[1][1]
  local masterPuppeteer = game.Puppeteer.NewForce(syncObj, debugStringFull, masterAI)
  if masterAI == game.Player.FindPlayer() then
    if optionalArgs.completion_percentage then
      masterPuppeteer:WeaponEquip({
        weaponMode = "Bare",
        use_completion_percentage = true,
        completion_percentage = optionalArgs.completion_percentage
      })
    else
      masterPuppeteer:WeaponEquip({weaponMode = "Bare"})
    end
  end
  local slavePuppeteers = {}
  local approachData = GetGlobalApproachData()
  approachData.branch_name = slave_MoveTable[1][2]
  approachData.joint_name = jointName
  masterAI:SetAccelerationOverride(approachData.acceleration_override)
  masterAI:SetDecelerationOverride(approachData.deceleration_override)
  masterPuppeteer:Approach(approachData)
  masterPuppeteer:OnComplete(function()
    masterAI:ClearFocus()
    masterAI:ClearDecelerationOverride()
    masterAI:ClearAccelerationOverride()
    for i = 2, #slave_MoveTable do
      if slave_MoveTable[i][2] ~= nil and string.find(slave_MoveTable[i][2], "BRA") ~= nil then
        local newSlavePup = game.Puppeteer.NewForce(syncObj, debugStringFull, slave_MoveTable[i][1])
        newSlavePup:AcceptSync()
        if slave_MoveTable[i][3] ~= nil and slave_MoveTable[i][3].keepEvaluatingPath ~= nil and slave_MoveTable[i][3].keepEvaluatingPath == true then
          newSlavePup:SetType("KeepEvaluatingPath")
        end
        slavePuppeteers[#slavePuppeteers + 1] = {
          Slave = newSlavePup,
          Branch = slave_MoveTable[i][2]
        }
      elseif objAnimName ~= nil and objAnimName ~= "" then
        slavePuppeteers[#slavePuppeteers + 1] = {
          Slave = slave_MoveTable[i][1],
          Anim = objAnimName
        }
      else
        slavePuppeteers[#slavePuppeteers + 1] = {
          Slave = slave_MoveTable[i][1]
        }
      end
    end
    masterPuppeteer:Sync(slave_MoveTable[1][2], useGOAsReference, slavePuppeteers, jointName)
    masterPuppeteer:OnComplete(function()
      masterPuppeteer = nil
    end)
    if objAnimName ~= "" and objAnimName ~= nil then
      syncObj:StartAnim(objAnimName)
    else
      syncObj:JumpAnimToFrame(0)
      syncObj:PlayAnimToEnd()
    end
    for i = 1, #slavePuppeteers do
      if slave_MoveTable[i + 1][1]:IsCreature() then
        slavePuppeteers[i].Slave:OnComplete(function()
          slavePuppeteers[i].Slave = nil
        end)
      end
    end
  end)
end
local PlaySingleSynchMove_KratosObject = function(obj, syncObjJointName, interactionName, heroBranch, animNameObject, interactZone, disableAfterUse, weaponType, optionalArgs)
  local slaveTable = {}
  local synchObj = obj
  if optionalArgs == nil then
    optionalArgs = {}
  end
  if type(obj) == "table" then
    synchObj = synchObj[1]
    if type(animNameObject) == "table" then
      if #obj == #animNameObject then
        for i = 1, #obj do
          slaveTable[#slaveTable + 1] = {
            Slave = obj[i],
            Anim = animNameObject[i]
          }
        end
      else
        engine.Warning("PlaySingleSynchMove_KratosObject - there must be a single animation (string) OR an equal number of objects and animations (table) passed")
      end
    else
      for i = 1, #obj do
        slaveTable[#slaveTable + 1] = {
          Slave = obj[i],
          Anim = animNameObject
        }
      end
    end
  elseif animNameObject ~= "" then
    slaveTable[#slaveTable + 1] = {Slave = synchObj, Anim = animNameObject}
  end
  print("Starting single synch move between Kratos and Object '" .. synchObj:GetName() .. "'")
  local player = game.Player:FindPlayer()
  local heroPuppeteer = game.Puppeteer.NewForce(synchObj, interactionName .. "Hero", game.Player:FindPlayer())
  weaponType = weaponType or "Bare"
  if weaponType ~= "Any" then
    if optionalArgs.completion_percentage then
      heroPuppeteer:WeaponEquip({
        weaponMode = weaponType,
        use_completion_percentage = true,
        completion_percentage = optionalArgs.completion_percentage
      })
    else
      heroPuppeteer:WeaponEquip({weaponMode = weaponType})
    end
  end
  local approachData = GetGlobalApproachData()
  approachData.branch_name = heroBranch
  approachData.joint_name = syncObjJointName
  player:SetAccelerationOverride(approachData.acceleration_override)
  player:SetDecelerationOverride(approachData.deceleration_override)
  heroPuppeteer:Approach(approachData)
  heroPuppeteer:OnComplete(function()
    if optionalArgs.OnApproachComplete then
      optionalArgs.OnApproachComplete()
    end
    player:ClearFocus()
    player:ClearDecelerationOverride()
    player:ClearAccelerationOverride()
    if syncObjJointName == "" or syncObjJointName == nil then
      heroPuppeteer:Sync(heroBranch, true, slaveTable)
    else
      heroPuppeteer:Sync(heroBranch, true, slaveTable, syncObjJointName)
    end
    heroPuppeteer:OnComplete(function()
      if optionalArgs.OnSyncComplete then
        optionalArgs.OnSyncComplete()
      end
      heroPuppeteer:Clear()
      heroPuppeteer:DetachPuppet()
      heroPuppeteer = nil
      if interactZone ~= nil then
        if interactZone.hasMultiple then
          for _, iz in ipairs(interactZone) do
            if type(iz) == "table" and disableAfterUse then
              iz:Disable()
            end
          end
        elseif disableAfterUse then
          interactZone:Disable()
        end
      end
    end)
    if animNameObject == "" then
      synchObj:JumpAnimToFrame(0)
      synchObj:PlayAnimToEnd()
    end
  end)
end
local PlaySingleSynchMove_KratosSonObject = function(obj, syncObjJointName, interactionName, heroBranch, sonBranch, animNameObject, interactZone, disableAfterUse, sonCleanupHack, weaponType, optionalArgs)
  print("Starting single synch move between Kratos, Son, and Object '" .. obj:GetName() .. "'")
  local player = game.Player:FindPlayer()
  local heroPuppeteer = game.Puppeteer.NewForce(obj, interactionName .. "Hero", player)
  if optionalArgs == nil then
    optionalArgs = {}
  end
  weaponType = weaponType or "Bare"
  if optionalArgs.completion_percentage then
    heroPuppeteer:WeaponEquip({
      weaponMode = weaponType,
      use_completion_percentage = true,
      completion_percentage = optionalArgs.completion_percentage
    })
  else
    heroPuppeteer:WeaponEquip({weaponMode = weaponType})
  end
  local approachData = GetGlobalApproachData()
  approachData.branch_name = heroBranch
  approachData.joint_name = syncObjJointName
  player:SetAccelerationOverride(approachData.acceleration_override)
  player:SetDecelerationOverride(approachData.deceleration_override)
  heroPuppeteer:Approach(approachData)
  heroPuppeteer:OnComplete(function()
    if optionalArgs.OnApproachComplete then
      optionalArgs.OnApproachComplete()
    end
    print("-- on complete --")
    player:ClearFocus()
    player:ClearDecelerationOverride()
    player:ClearAccelerationOverride()
    local sonPuppeteer = game.Puppeteer.NewForce(obj, interactionName .. "Son", game.AI.FindSon())
    sonPuppeteer:AcceptSync()
    if syncObjJointName == "" or syncObjJointName == nil then
      if animNameObject ~= "" then
        heroPuppeteer:Sync(heroBranch, true, {
          {Slave = sonPuppeteer, Branch = sonBranch},
          {Slave = obj, Anim = animNameObject}
        })
      else
        heroPuppeteer:Sync(heroBranch, true, {
          {Slave = sonPuppeteer, Branch = sonBranch}
        })
      end
    elseif animNameObject ~= "" then
      heroPuppeteer:Sync(heroBranch, true, {
        {Slave = sonPuppeteer, Branch = sonBranch},
        {Slave = obj, Anim = animNameObject}
      }, syncObjJointName)
    else
      heroPuppeteer:Sync(heroBranch, true, {
        {Slave = sonPuppeteer, Branch = sonBranch}
      }, syncObjJointName)
    end
    heroPuppeteer:OnComplete(function()
      if optionalArgs.OnSyncComplete then
        optionalArgs.OnSyncComplete()
      end
      heroPuppeteer:Clear()
      heroPuppeteer:DetachPuppet()
      heroPuppeteer = nil
      if interactZone ~= nil then
        if interactZone.hasMultiple then
          for _, iz in ipairs(interactZone) do
            if type(iz) == "table" and disableAfterUse then
              iz:Disable()
            end
          end
        elseif disableAfterUse then
          interactZone:Disable()
        end
      end
      if sonPuppeteer ~= nil and sonCleanupHack then
        sonPuppeteer:DetachPuppet()
        sonPuppeteer = nil
      end
    end)
    sonPuppeteer:OnComplete(function()
      if sonPuppeteer ~= nil then
        sonPuppeteer:DetachPuppet()
        sonPuppeteer = nil
      end
    end)
    if animNameObject == "" then
      obj:JumpAnimToFrame(0)
      obj:PlayAnimToEnd()
    end
  end)
end
return profile.WrapLibrary({
  SyncAndPlayBranch = SyncAndPlayBranch,
  Advertise_SyncAndPlayBranch = Advertise_SyncAndPlayBranch,
  GoToLocation = GoToLocation,
  Advertise_GoToLocation = Advertise_GoToLocation,
  GoToLocationSyncAndPlayBranch = GoToLocationSyncAndPlayBranch,
  Advertise_GoToLocationSyncAndPlayBranch = Advertise_GoToLocationSyncAndPlayBranch,
  GoToLocationAlignAndWait = GoToLocationAlignAndWait,
  Advertise_GoToLocationAlignAndWait = Advertise_GoToLocationAlignAndWait,
  PlayApproach_GroupSync = PlayApproach_GroupSync,
  PlayGroupSync = PlayGroupSync,
  GetGlobalApproachData = GetGlobalApproachData,
  PlaySingleSynchMove_KratosObject = PlaySingleSynchMove_KratosObject,
  PlaySingleSynchMove_KratosSonObject = PlaySingleSynchMove_KratosSonObject,
  MoveAndAlignToLocationSyncAndPlayBranch = MoveAndAlignToLocationSyncAndPlayBranch
})
