local LD = require("design.LevelDesignLibrary")
local timers = require("level.timer")
local thisObj, thisLevel, player
currentVelocity = nil
maxVelocity = nil
minVelocity = nil
decayRate = nil
local smallImpulse = 1
local largeImpulse = 1
local minFrame = 1
local maxFrame = -1
local previousFrame = 0
local refractoryTime = 1
local refractoryTimer
local bRefractoryActive = false
local bLocked = false
local bReachedLockFrame = false
local drivenObject
local drivenObjects = {}
local driverObjects = {}
local gearsObjects = {}
local lockFrames = {}
local OnUnlockCallbacks, OnReachedLockFrameCallbacks, OnAddImpulseCallbacks, OnRewindStartCallbacks, OnReachStartCallbacks, OnReachEndCallbacks, OnLockCallbacks
local PERM_LOCK = false
local lastThrowType
local direction = "idle"
local usingLockFrame = false
local IsAtEnd = function()
  return drivenObject.AnimFrame >= maxFrame
end
local IsAtStart = function()
  return drivenObject.AnimFrame <= minFrame
end
function OnScriptLoaded(level, obj)
  thisObj = obj
  thisLevel = level
  player = game.Player.FindPlayer()
  minVelocity = thisObj:GetLuaTableAttribute("minVelocity")
  maxVelocity = thisObj:GetLuaTableAttribute("maxVelocity")
  decayRate = thisObj:GetLuaTableAttribute("decayRate")
  decayRate = decayRate
  refractoryTime = thisObj:GetLuaTableAttribute("refractoryTime")
  smallImpulse = thisObj:GetLuaTableAttribute("smallImpulse")
  largeImpulse = thisObj:GetLuaTableAttribute("largeImpulse")
  lockFrames = LD.ConvertStringListToTable(thisObj:GetLuaTableAttribute("lockFrames"))
  for i = 1, #lockFrames do
    lockFrames[i] = tonumber(lockFrames[i])
  end
  table.sort(lockFrames, function(a, b)
    return b < a
  end)
  drivenObject = GameObjects[thisObj:GetLuaTableAttribute("drivenObject")]
  if drivenObject.IsRefNode then
    drivenObject = drivenObject.Child
  end
  if not drivenObject then
    engine.Warning("Invalid Spinnable System: " .. obj:GetName() .. " in level " .. level.Name .. " has invalid drivenObject.")
  end
  OnUnlockCallbacks = LD.ExtractCallbacksForEvent(level, obj, thisObj:GetLuaTableAttribute("OnUnlockCallbacks"))
  OnLockCallbacks = LD.ExtractCallbacksForEvent(level, obj, thisObj:FindLuaTableAttribute("OnLockCallbacks"))
  OnReachedLockFrameCallbacks = LD.ExtractCallbacksForEvent(level, obj, thisObj:GetLuaTableAttribute("OnReachedLockFrameCallbacks"))
  OnAddImpulseCallbacks = LD.ExtractCallbacksForEvent(level, obj, thisObj:GetLuaTableAttribute("OnAddImpulseCallbacks"))
  OnRewindStartCallbacks = LD.ExtractCallbacksForEvent(level, obj, thisObj:GetLuaTableAttribute("OnRewindStartCallbacks"))
  OnReachStartCallbacks = LD.ExtractCallbacksForEvent(level, obj, thisObj:FindLuaTableAttribute("OnReachStartCallbacks"))
  OnReachEndCallbacks = LD.ExtractCallbacksForEvent(level, obj, thisObj:FindLuaTableAttribute("OnReachEndCallbacks"))
  previousFrame = drivenObject.AnimFrame
end
function OnFirstStart(level, obj)
  minFrame = thisObj:GetLuaTableAttribute("initialMinFrame")
  maxFrame = thisObj:GetLuaTableAttribute("initialMaxFrame")
  if maxFrame < 0 then
    maxFrame = drivenObject.AnimLengthFrames
  end
  if maxFrame > drivenObject.AnimLengthFrames then
    engine.Warning("Spinnable System [object" .. drivenObject:GetName() .. "]: initialMaxFrame on object " .. obj:GetName() .. " in level " .. level.Name .. " is invalid. Clamping to drivenObject.AnimLengthFrames.")
    maxFrame = drivenObject.AnimLengthFrames
  end
  if minFrame < 0 then
    engine.Warning("Spinnable System [object" .. drivenObject:GetName() .. "]: initialMinFrame on object " .. obj:GetName() .. " in level " .. level.Name .. " is invalid. Clamping to 0.")
    minFrame = 0
  end
end
function OnSaveCheckpoint(level, obj)
  return {
    minFrame = minFrame,
    maxFrame = maxFrame,
    bLocked = bLocked,
    bRefractoryActive = bRefractoryActive,
    bReachedLockFrame = bReachedLockFrame,
    PERM_LOCK = PERM_LOCK
  }
end
function OnRestoreCheckpoint(level, obj, savedInfo)
  minFrame = savedInfo.minFrame
  maxFrame = savedInfo.maxFrame
  bLocked = savedInfo.bLocked or false
  bRefractoryActive = savedInfo.bRefractoryActive or false
  bReachedLockFrame = savedInfo.bReachedLockFrame or false
  PERM_LOCK = savedInfo.PERM_LOCK or false
end
function OnPreStart(level, obj)
  if bRefractoryActive then
    StartRefractory()
  end
end
function OnStart(level, obj)
  if PERM_LOCK == true then
    SetPermLock()
    return
  end
  if not bLocked then
    drivenObject:JumpAnimToFrame(FindClosestLockFrame() or minFrame)
    drivenObject:PauseAnim()
  end
  local tempString = thisObj:GetLuaTableAttribute("additionalDrivenObjects")
  if tempString then
    local tempDrivenObjects = LD.ConvertStringListToTable(tempString)
    for i = 1, #tempDrivenObjects do
      AddDrivenObject(tempDrivenObjects[i])
    end
  end
  local tempString2 = thisObj:GetLuaTableAttribute("driverObjects")
  local tempString3 = thisObj:GetLuaTableAttribute("driverObjectFactors")
  if tempString2 and tempString3 then
    local tempDriverObjects = LD.ConvertStringListToTable(tempString2)
    local tempDriverObjectFactors = LD.ConvertStringListToTable(tempString3)
    for i = 1, #tempDriverObjects do
      AddDriverObject(tempDriverObjects[i], tempDriverObjectFactors[i] or 1)
    end
  end
  local tempString4 = thisObj:GetLuaTableAttribute("gearsObjects")
  local tempString5 = thisObj:GetLuaTableAttribute("gearsObjectFactors")
  if tempString4 and tempString5 then
    local tempGearsObjects = LD.ConvertStringListToTable(tempString4)
    local tempGearsObjectFactors = LD.ConvertStringListToTable(tempString5)
    if #tempGearsObjects ~= #tempGearsObjectFactors then
      engine.Warning("Spinnable System [object" .. drivenObject:GetName() .. "]: gearsObjects and gearsObjectFactors length mismatch on " .. obj:GetName() .. " in level " .. level.Name .. ".")
    end
    for i = 1, #tempGearsObjects do
      AddGearsObject(tempGearsObjects[i], tempGearsObjectFactors[i] or 1)
    end
  end
  currentVelocity = minVelocity
  if not IsAnyGearPinned() then
    if bRefractoryActive then
      EndRefractory()
    end
    bLocked = false
    drivenObject:JumpAnimToFrame(minFrame)
    drivenObject:PauseAnim()
    for k, _ in pairs(drivenObjects) do
      GameObjects[k].Child:JumpAnimToFrame(minFrame)
      GameObjects[k].Child:PauseAnim()
    end
    for k, _ in pairs(driverObjects) do
      GameObjects[k].Child:JumpAnimToFrame(minFrame)
      GameObjects[k].Child:PauseAnim()
    end
  end
end
function SetPermLock()
  PERM_LOCK = true
  OnUpdate = nil
end
function UpdateThrowType()
  if player:IsPlayingMove("MOV_AxeTossHorizontalEnterToss") then
    lastThrowType = "small"
  elseif player:IsPlayingMove("MOV_AxeTossVerticalEnterTossCook") then
    lastThrowType = "large"
  end
end
function OnUpdate(level, obj)
  UpdateThrowType()
  local rewindCheckPlayRate = currentVelocity
  local playRate = currentVelocity
  local playRateDrivers = currentVelocity
  local bShouldDecay = true
  local bInRefractory = false
  if bRefractoryActive then
    bInRefractory = true
    currentVelocity = 0
    playRate = 0
    playRateDrivers = 0
    bShouldDecay = false
  end
  if bLocked and not bInRefractory then
    local lockFrame = FindClosestLockFrame()
    if lockFrame ~= nil and drivenObject.AnimFrame == lockFrame then
      playRate = 0
      currentVelocity = 0
      bShouldDecay = false
      if not bReachedLockFrame then
        LD.ExecuteCallbacksForEvent(thisLevel, thisObj, OnReachedLockFrameCallbacks, "Spinnable System [object" .. drivenObject:GetName() .. "]: " .. thisObj:GetName() .. " in level " .. thisLevel.Name .. ", Firing OnReachedLockFrameCallbacks", direction, lockFrame)
        bReachedLockFrame = true
        if direction == "forward" then
          StopSoundOnDriverObjects_Forward()
        elseif direction == "backward" then
          StopSoundOnDriverObjects_Backward()
        end
        direction = "idle"
      end
    elseif lockFrame == nil then
      playRate = 0
      currentVelocity = 0
      bShouldDecay = false
    end
  end
  if drivenObject.AnimFrame <= minFrame and playRate <= 0 then
    playRate = 0
    playRateDrivers = 0
    bShouldDecay = false
    currentVelocity = 0
    if previousFrame > drivenObject.AnimFrame then
      LD.ExecuteCallbacksForEvent(thisLevel, thisObj, OnReachStartCallbacks, "Spinnable System [object" .. drivenObject:GetName() .. "]: " .. thisObj:GetName() .. " in level " .. thisLevel.Name .. ", Firing OnReachStartCallbacks")
      direction = "idle"
      StopSoundOnDriverObjects_Forward()
      StopSoundOnDriverObjects_Backward()
      PlaySoundOnDriverObjects_OnReachStart()
    end
  end
  if drivenObject.AnimFrame >= maxFrame and 0 < playRate then
    StartRefractory()
    currentVelocity = 0
    bShouldDecay = false
    playRate = 0
    playRateDrivers = 0
    if previousFrame < drivenObject.AnimFrame then
      LD.ExecuteCallbacksForEvent(thisLevel, thisObj, OnReachEndCallbacks, "Spinnable System [object" .. drivenObject:GetName() .. "]: " .. thisObj:GetName() .. " in level " .. thisLevel.Name .. ", Firing OnReachEndCallbacks")
      direction = "idle"
      StopSoundOnDriverObjects_Forward()
      PlaySoundOnDriverObjects_OnReachEnd()
    end
  end
  drivenObject:PlayAnimToEnd(playRate)
  if playRate < 0 and direction ~= "backward" then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, OnRewindStartCallbacks, "Spinnable System [object" .. drivenObject:GetName() .. "]: " .. thisObj:GetName() .. " in level " .. thisLevel.Name .. ", Firing OnRewindStartCallbacks", direction)
    direction = "backward"
    StopSoundOnDriverObjects_Forward()
    PlaySoundOnDriverObjects_Backward()
  elseif 0 < playRate and direction ~= "forward" then
    direction = "forward"
  end
  for additionalDrivenObjName, drivenObjPlayRate in pairs(drivenObjects) do
    GameObjects[additionalDrivenObjName]:PlayAnimToEnd(playRate * drivenObjPlayRate)
  end
  for driverObjName, driverPlayRate in pairs(driverObjects) do
    GameObjects[driverObjName].Child:PlayAnimCycle(playRateDrivers * driverPlayRate)
  end
  local backGear, extraGears
  for gearName, gearPlayRate in pairs(gearsObjects) do
    GameObjects[gearName].Child.LuaObjectScript.SetAnimationSpeed("PlayAnimCycle", playRateDrivers * gearPlayRate)
    extraGears = GameObjects[gearName].Child.LuaObjectScript.FindExtraGears()
    backGear = GameObjects[gearName].Child.LuaObjectScript.FindBackGear()
    if extraGears then
      extraGears:PlayAnimCycle(playRateDrivers * gearPlayRate)
    end
    if backGear then
      backGear.LuaObjectScript.SetAnimationSpeed("PlayAnimCycle", playRateDrivers * gearPlayRate)
    end
  end
  if bShouldDecay then
    currentVelocity = currentVelocity - decayRate * player:GetUnitTime()
    if currentVelocity < minVelocity then
      currentVelocity = minVelocity
    end
  end
  if engine.IsDebug() and LD.IsPositionInFrontOfObject(thisObj:GetWorldPosition(), player) and LD.GetDistanceBetweenTwoObjects(player, thisObj) < 20 then
    local dt = {}
    table.insert(dt, {
      "drivenObject",
      drivenObject:GetName()
    })
    table.insert(dt, {
      "currentFrame",
      drivenObject.AnimFrame
    })
    table.insert(dt, {
      "additionalDrivenObjects",
      #drivenObjects
    })
    table.insert(dt, {
      "driverObjects",
      #driverObjects
    })
    table.insert(dt, {
      "gearsObjects",
      #gearsObjects
    })
    table.insert(dt, {"bLocked", bLocked})
    table.insert(dt, {
      "currentVelocity",
      currentVelocity
    })
    table.insert(dt, {"playRate", playRate})
    table.insert(dt, {
      "playRateDrivers",
      playRateDrivers
    })
    table.insert(dt, {
      "minVelocity",
      minVelocity
    })
    table.insert(dt, {
      "maxVelocity",
      maxVelocity
    })
    table.insert(dt, {"minFrame", minFrame})
    table.insert(dt, {"maxFrame", maxFrame})
    table.insert(dt, {
      "Next Stop Frame",
      FindClosestLockFrame() or minFrame
    })
    table.insert(dt, {
      "lastThrowType",
      lastThrowType
    })
    table.insert(dt, {"direction", direction})
    dt.TitleColor = engine.Vector.New(33, 33, 128)
    dt.TitleAlpha = 220
    dt.Title = thisObj:GetName() .. " Spinnable System Debug"
    dt.X, dt.Y = 180, 2
    engine.DrawDebugTable(dt)
  end
  previousFrame = drivenObject.AnimFrame
end
function StartRefractory()
  bRefractoryActive = true
  refractoryTimer = timers.StartLevelTimer(refractoryTime, EndRefractory)
end
function EndRefractory()
  bRefractoryActive = false
  if refractoryTimer then
    refractoryTimer:Stop()
  end
  StopSoundOnDriverObjects_Forward()
  PlaySoundOnDriverObjects_Backward()
end
function IsAnyGearPinned()
  for k, _ in pairs(gearsObjects) do
    if GameObjects[k].LuaObjectScript.IsAnyGearPinned() then
      return true
    end
  end
  return false
end
function Lock()
  bLocked = true
  if FindClosestLockFrame() == nil then
    currentVelocity = 0
  end
  bReachedLockFrame = false
  if refractoryTimer and refractoryTimer.running then
    refractoryTimer:Pause()
  end
  LD.ExecuteCallbacksForEvent(thisLevel, thisObj, OnLockCallbacks, "Spinnable System [object" .. drivenObject:GetName() .. "]: " .. thisObj:GetName() .. " in level " .. thisLevel.Name .. ", Firing OnLockCallbacks", usingLockFrame, direction)
  if FindClosestLockFrame() == nil then
    direction = "idle"
  end
  StopSoundOnDriverObjects_Forward()
  StopSoundOnDriverObjects_Backward()
end
function Unlock()
  bLocked = false
  currentVelocity = 0
  if refractoryTimer and not refractoryTimer.running then
    refractoryTimer:Unpause()
  end
  if OnUnlockCallbacks then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, OnUnlockCallbacks, "Spinnable System [object" .. drivenObject:GetName() .. "]: " .. thisObj:GetName() .. " in level " .. thisLevel.Name .. ", Firing OnUnlockCallbacks", direction, IsAtStart())
  end
  if not bRefractoryActive then
    PlaySoundOnDriverObjects_Backward()
  end
end
function AddDrivenObject(drivenObjectName)
  drivenObjects[drivenObjectName] = GameObjects[drivenObjectName].AnimLengthFrames / drivenObject.AnimLengthFrames
end
function AddDriverObject(driverObjectName, driverObjectFactor)
  driverObjects[driverObjectName] = driverObjectFactor
  GameObjects[driverObjectName].Child.LuaObjectScript.RegisterEmbedCallback(AddImpulse)
end
function AddGearsObject(gearsObjectName, gearsObjectFactor)
  gearsObjects[gearsObjectName] = gearsObjectFactor
  GameObjects[gearsObjectName].LuaObjectScript.RegisterEmbedCallback(Lock)
  GameObjects[gearsObjectName].LuaObjectScript.RegisterUnembedCallback(Unlock)
end
function FindClosestLockFrame()
  if not lockFrames then
    usingLockFrame = false
    return nil
  end
  for i = 1, #lockFrames do
    if lockFrames[i] <= drivenObject.AnimFrame then
      usingLockFrame = true
      return lockFrames[i]
    end
  end
  return nil
end
function AddImpulse(level, go, attacker, weapon)
  if weapon:GetName() ~= "axe00" or bLocked == true then
    return
  end
  LD.ExecuteCallbacksForEvent(thisLevel, thisObj, OnAddImpulseCallbacks, "Spinnable System [object" .. drivenObject:GetName() .. "]: " .. thisObj:GetName() .. " in level " .. thisLevel.Name .. ", Firing OnAddImpulseCallbacks", direction, IsAtEnd())
  local impulseAmount = smallImpulse
  if lastThrowType == "large" then
    impulseAmount = largeImpulse
  end
  lastThrowType = nil
  if bRefractoryActive then
    refractoryTimer:Restart()
  end
  if currentVelocity < 0 then
    currentVelocity = 0
  end
  currentVelocity = currentVelocity + impulseAmount
  if currentVelocity > maxVelocity then
    currentVelocity = maxVelocity
  end
  if bRefractoryActive then
    PlaySoundOnDriverObjects_OnEmbed()
  else
    StopSoundOnDriverObjects_Backward()
    PlaySoundOnDriverObjects_Forward()
    PlaySoundOnDriverObjects_OnEmbed()
  end
end
function SetMinFrame(newMinFrame)
  if newMinFrame == nil then
    newMinFrame = thisObj:GetLuaTableAttribute("initialMinFrame")
  end
  if newMinFrame > drivenObject.AnimFrame then
    engine.Warning("Spinnable System [object" .. drivenObject:GetName() .. "]: Failed attempt to set minFrame lower than current frame on " .. thisObj:GetName() .. " in level " .. thisLevel.Name .. ".")
    return
  end
  minFrame = newMinFrame
end
function SetMaxFrame(newMaxFrame)
  if newMaxFrame == nil then
    newMaxFrame = thisObj:GetLuaTableAttribute("initialMaxFrame")
  end
  if newMaxFrame < drivenObject.AnimFrame then
    engine.Warning("Spinnable System [object" .. drivenObject:GetName() .. "]: Failed attempt to set maxFrame lower than current frame on " .. thisObj:GetName() .. " in level " .. thisLevel.Name .. ".")
    return
  end
  maxFrame = newMaxFrame
end
function GetMinFrame()
  return minFrame
end
function GetMaxFrame()
  return maxFrame
end
function SetMinVelocity(newMinVelocity)
  if newMinVelocity == nil then
    newMinVelocity = thisObj:GetLuaTableAttribute("minVelocity")
  end
  minVelocity = newMinVelocity
  if bLocked or bRefractoryActive then
    return
  end
  if currentVelocity < minVelocity then
    currentVelocity = minVelocity
  end
end
function SetMaxVelocity(newMaxVelocity)
  if newMaxVelocity == nil then
    newMaxVelocity = thisObj:GetLuaTableAttribute("maxVelocity")
  end
  maxVelocity = newMaxVelocity
  if bLocked or bRefractoryActive then
    return
  end
  if currentVelocity > maxVelocity then
    currentVelocity = maxVelocity
  end
end
function SetDecayRate(newDecayRate)
  if newDecayRate == nil then
    newDecayRate = thisObj:GetLuaTableAttribute("decayRate")
  end
  decayRate = newDecayRate
end
function SetRefractoryTime(time)
  if time == nil then
    time = thisObj:GetLuaTableAttribute("refractoryTime")
  end
  if type(time) ~= "number" then
    engine.Error("Attempting to set refractory time to a non number value")
  end
  refractoryTime = time
  if bLocked and bRefractoryActive then
    refractoryTimer = timers.StartLevelTimer(refractoryTime, EndRefractory)
    refractoryTimer:Pause()
  end
end
function GetRefractoryTime()
  return refractoryTime
end
local mediumImpulse
function RiverPassOverride(smallImpulseAmount, mediumImpulseAmount, largeImpulseAmount)
  smallImpulse = smallImpulseAmount
  mediumImpulse = mediumImpulseAmount
  largeImpulse = largeImpulseAmount
  AddImpulse = AddImpulse_RiverPass
  UpdateThrowType = UpdateThrowType_RiverPass
end
function AddImpulse_RiverPass(level, go, attacker, weapon)
  LD.ExecuteCallbacksForEvent(thisLevel, thisObj, OnAddImpulseCallbacks, "Spinnable System [object" .. drivenObject:GetName() .. "]: " .. thisObj:GetName() .. " in level " .. thisLevel.Name .. ", Firing OnAddImpulseCallbacks", direction, IsAtEnd())
  local impulseAmount = smallImpulse
  if lastThrowType == "large" then
    impulseAmount = largeImpulse
  elseif lastThrowType == "medium" then
    impulseAmount = mediumImpulse
  end
  lastThrowType = nil
  if bRefractoryActive then
    refractoryTimer:Restart()
  end
  if currentVelocity < 0 then
    currentVelocity = 0
  end
  currentVelocity = currentVelocity + impulseAmount
  if currentVelocity > maxVelocity then
    currentVelocity = maxVelocity
  end
  if bRefractoryActive then
    PlaySoundOnDriverObjects_OnEmbed()
  else
    StopSoundOnDriverObjects_Backward()
    PlaySoundOnDriverObjects_Forward()
    PlaySoundOnDriverObjects_OnEmbed()
  end
end
function UpdateThrowType_RiverPass()
  if player:IsPlayingMove("MOV_AxeTossHorizontalEnterToss") then
    lastThrowType = "small"
  elseif player:IsPlayingMove("MOV_AxeTossHorizontalEnterChargeHoldLoop") then
    lastThrowType = "medium"
  elseif player:IsPlayingMove("MOV_AxeTossVerticalEnterToss") then
    lastThrowType = "medium"
  elseif player:IsPlayingMove("MOV_AxeTossVerticalEnterTossCook") then
    lastThrowType = "large"
  end
end
local sound_bAllowForwardSFX = true
local sound_bAllowBackwardSFX = true
function PlaySoundOnDriverObjects_Forward()
  if not IsAtEnd() and not IsAnyGearPinned() and sound_bAllowForwardSFX then
    sound_bAllowForwardSFX = false
    for object, _ in pairs(driverObjects) do
      GameObjects[object].Child.LuaObjectScript.PlaySpinningForwardSound()
    end
  end
end
function StopSoundOnDriverObjects_Forward()
  sound_bAllowForwardSFX = true
  for object, _ in pairs(driverObjects) do
    GameObjects[object].Child.LuaObjectScript.StopSpinningForwardSound()
  end
end
function PlaySoundOnDriverObjects_Backward()
  if not IsAtStart() and sound_bAllowBackwardSFX then
    sound_bAllowBackwardSFX = false
    for object, _ in pairs(driverObjects) do
      GameObjects[object].Child.LuaObjectScript.PlaySpinningBackwardSound()
    end
  end
end
function StopSoundOnDriverObjects_Backward()
  sound_bAllowBackwardSFX = true
  for object, _ in pairs(driverObjects) do
    GameObjects[object].Child.LuaObjectScript.StopSpinningBackwardSound()
  end
end
function PlaySoundOnDriverObjects_OnEmbed()
  if not bRefractoryActive and drivenObject.AnimFrame < maxFrame then
    for object, _ in pairs(driverObjects) do
      GameObjects[object].Child.LuaObjectScript.PlayOnEmbedSound()
    end
  end
end
function PlaySoundOnDriverObjects_OnReachStart()
  for object, _ in pairs(driverObjects) do
    GameObjects[object].Child.LuaObjectScript.PlayOnReachStartSound()
  end
end
function PlaySoundOnDriverObjects_OnReachEnd()
  for object, _ in pairs(driverObjects) do
    GameObjects[object].Child.LuaObjectScript.PlayOnReachEndSound()
  end
end
function IsLocked()
  return bLocked
end
function IsInRefractory()
  return bRefractoryActive
end
