local classlib = require("core.class")
local tablex = require("core.tablex")
local animationUtil = require("ui.animationUtil")
local colors = require("ui.colors")
local consts = require("ui.consts")
local fsm = require("ui.fsm")
local hudConsts = require("ui.hudConsts")
local meter = require("ui.meter")
local meterCooldown = require("ui.meterCooldown")
local pickupConsts = require("ui.pickupConsts")
local pickupUtil = require("ui.pickupUtil")
local resourceConsts = require("ui.resourceConsts")
local resourceUtil = require("ui.resourceUtil")
local uiCalls = require("ui.uicalls")
local util = require("ui.util")
local AI = game.AI
local FindPlayer = game.Player.FindPlayer
local Pickup = game.Pickup
local UI = game.UI
local Wallets = game.Wallets
local HealthMeter = classlib.Class("HealthMeter", fsm.UIState)
local HMOff = HealthMeter:StateClass("HMOff", fsm.UIState)
local HMOn = HealthMeter:StateClass("HMOn", fsm.UIState)
local healthMeter = HealthMeter.New("healthMeter", {HMOff, HMOn})
function HealthMeter:Setup()
end
function HealthMeter:Reset()
  self:Goto("HMOff")
  self:Enter()
end
function HealthMeter:Enter()
  self:Goto("HMOn")
end
function HealthMeter:EVT_HandleResourceAmountChange(args)
  if args.ResourceName == "MaxHealthUpgrade" then
    local hmOn = self:GetState("HMOn")
    hmOn.previousSizePercent = hmOn.sizePercent
    hmOn.isGrowing = true
  end
end
function HealthMeter:EVT_PAUSE_MENU_OPEN()
  if self.playingIncreaseSound then
    self.playingIncreaseSound = false
    game.Audio.StopSound("SND_UX_HUD_Health_Bar_Increase_LP")
  end
end
function HealthMeter:EVT_DM_UNPAUSE()
  local hmOn = self:GetState("HMOn")
  if hmOn.isGrowing then
    hmOn.healthMeter:SetPercent(hmOn.previousSizePercent)
    UI.Anim(hmOn.healthMeter:GetInstancedFillObject(), consts.AS_Forward, "health_bar_maxGrow", 0, hmOn.previousSizePercent)
    UI.Anim(hmOn.healthMeterFlourish, consts.AS_Forward, "health_growflourish_position", 0, hmOn.previousSizePercent)
    self:SendEventToUIFsm("mainHUD", "EVT_MAX_HEALTHRAGE")
    self:StartTimer("MaxHealthRageDelayHelper", 0.3, function()
      hmOn.isGrowing = false
      hmOn.healthMeter:SetPercent(hmOn.previousSizePercent)
      hmOn:UpdateHealthMeterSize(true)
      hmOn.healthMeter:SetPercent(hmOn.sizePercent, hmOn.sizePercent - hmOn.previousSizePercent)
      self:SendEventToUIFsm("mainHUD", "EVT_PlayHUDSound", "SND_UX_HUD_Health_Bar_Increase_LP", false)
      self.playingIncreaseSound = true
      self:StartTimer("MaxHealthLoopSoundEndHelper", 1, function()
        if self.playingIncreaseSound then
          self.playingIncreaseSound = false
          game.Audio.StopSound("SND_UX_HUD_Health_Bar_Increase_LP")
        end
      end)
    end)
  end
end
HealthMeter.EVT_GAME_START = HealthMeter.Reset
HealthMeter.EVT_Restart = HealthMeter.Reset
function HMOff:Enter()
end
function HMOff:Exit()
end
function HMOff:EVT_METER_CHANGED()
  local markerID = UI.GetEventArgInt(1)
  local idHash = UI.GetEventArgStringHash(2)
  local hmOn = self:GetState("HMOn")
  if hmOn.healthMeter ~= nil and hmOn.healthMeter:ShouldHandleMeterChangedEvent(markerID, idHash) then
    hmOn:UpdateHealth(true)
  end
end
function HMOn:Setup()
  local Kratos = FindPlayer()
  self.isGrowing = false
  self.sizePercent = 0
  self.previousSizePercent = 0
  self.healthMeterFlourish = util.GetUiObjByName("healthbarflourish")
  self.healthMeter = meter.Meter.New(self, {
    Name = "health_meter",
    MeterObject = util.GetUiObjByName("health_meter"),
    MeterInstanceChildName = "health_bar",
    FillAnimName = "health_bar_fill",
    FullSound = "SND_UX_Health_Kratos_Meter_Full",
    Percent = 0,
    MaxValue = hudConsts.METER_HEALTH_FINAL_MAX,
    IDHash = engine.Hash(hudConsts.METER_HEALTH),
    MarkerID = consts.OT_Player,
    OnMeterChanged = function(meter)
      self:UpdateHealth(true)
    end,
    OnGain = function(meter, pastPercent, currPercent)
      self:OnGain(meter, pastPercent, currPercent)
    end,
    OnLose = function(meter, pastPercent, currPercent)
      self:OnLose(meter, pastPercent, currPercent)
    end,
    OnFillAnimEnd = function(meter, senderObj, percentComplete)
      self:OnFillAnimEnd(meter, senderObj, percentComplete)
    end
  })
  self:UpdateHealthMeterSize()
  self:UpdateHealth(false)
end
function HMOn:Enter()
  util.Show("health_growflourish", "healthbarflourish")
  self:UpdateHealthMeterSize()
  self:UpdateHealth(false)
  self.healthMeter:Activate(true)
  if self:GetHealthSizePercent() * hudConsts.METER_HEALTH_CRITICAL_PERCENT > self.healthMeter:get_percent() then
    UI.Anim(self.healthMeter:GetInstancedFillObject(), consts.AS_Reverse, "health_bar_colorChange", 2)
  else
    UI.Anim(self.healthMeter:GetInstancedFillObject(), consts.AS_Forward, "health_bar_colorChange", 2, 1)
  end
  self:AnimateHealthBarLoop()
  self.parent = self:GetState("healthMeter")
end
function HMOn:Exit()
  self.healthMeter:Deactivate(true)
end
function HMOn:AnimateHealthBarLoop()
  local goHealthMeterChild = self.healthMeter:GetInstancedChildObject()
  local animRate = 0.4
  UI.Anim(goHealthMeterChild, consts.AS_ForwardCycle_NoReset, "health_bar_loop", animRate)
end
function HMOn:GetHealthSizePercent()
  local Kratos = FindPlayer()
  local currentMeterMax = Kratos:MeterGetMax(hudConsts.METER_HEALTH) / hudConsts.METER_HEALTH_FINAL_MAX - hudConsts.METER_HEALTH_BASE_MAX_RATIO
  local perceivedMeterMax = math.pow(currentMeterMax / (1 - hudConsts.METER_HEALTH_BASE_MAX_RATIO), hudConsts.METER_HEALTH_CURVE_FACTOR) * (1 - hudConsts.METER_HEALTH_BASE_PERCENT) + hudConsts.METER_HEALTH_BASE_PERCENT
  return perceivedMeterMax
end
function HMOn:UpdateHealthMeterSize(playAnim)
  if self.isGrowing then
    return
  end
  local sizePercent = self:GetHealthSizePercent()
  local animObj = self.healthMeter:GetInstancedFillObject()
  if sizePercent ~= sizePercent then
    return
  end
  self.sizePercent = sizePercent
  if playAnim then
    UI.Anim(animObj, consts.AS_Forward_NoReset, "health_bar_maxGrow", self.sizePercent - self.previousSizePercent, self.previousSizePercent, sizePercent)
    UI.Anim(self.healthMeterFlourish, consts.AS_Forward_NoReset, "health_growflourish_position", self.sizePercent - self.previousSizePercent, self.previousSizePercent, sizePercent)
    UI.Anim(self.healthMeterFlourish, consts.AS_Forward, "health_growflourish_flash", 1)
  else
    UI.Anim(animObj, consts.AS_Forward, "health_bar_maxGrow", 0, sizePercent)
    UI.Anim(self.healthMeterFlourish, consts.AS_Forward, "health_growflourish_position", 0, sizePercent)
    UI.Anim(self.healthMeterFlourish, consts.AS_Forward, "health_growflourish_flash", 0, 0, 0)
  end
end
function HMOn:UpdateHealth(animate)
  local Kratos = FindPlayer()
  local perceivedMax = self:GetHealthSizePercent()
  local currHealth = Kratos:MeterGetValue(hudConsts.METER_HEALTH)
  local maxHealth = Kratos:MeterGetMax(hudConsts.METER_HEALTH)
  local animRate
  if animate then
    animRate = 1
  end
  self.healthMeter:SetPercent(currHealth / maxHealth * perceivedMax, animRate)
end
function HMOn:OnGain(meter, pastFinalMaxPercHealth, currFinalMaxPercHealth)
  local healthSizePercent = self:GetHealthSizePercent()
  local currLowPercent = healthSizePercent * hudConsts.METER_HEALTH_LOW_PERCENT
  local currCriticalPercent = healthSizePercent * hudConsts.METER_HEALTH_CRITICAL_PERCENT
  local thresholdPercent = 0.1
  local animObj = meter:GetInstancedFillObject()
  if pastFinalMaxPercHealth <= currLowPercent and currFinalMaxPercHealth > currLowPercent then
    self:SendEventToUIFsm("mainHUD", "EVT_LOW_HEALTH_END")
  end
  if pastFinalMaxPercHealth <= currCriticalPercent and currFinalMaxPercHealth > currCriticalPercent then
    UI.Anim(animObj, consts.AS_Forward, "health_bar_colorChange", 2, 1)
  end
  if thresholdPercent < currFinalMaxPercHealth - pastFinalMaxPercHealth then
    UI.Anim(animObj, consts.AS_Forward, "health_bar_gainLarge", 1)
  end
end
function HMOn:OnLose(meter, pastFinalMaxPercHealth, currFinalMaxPercHealth)
  local healthSizePercent = self:GetHealthSizePercent()
  local currLowPercent = healthSizePercent * hudConsts.METER_HEALTH_LOW_PERCENT
  local currCriticalPercent = healthSizePercent * hudConsts.METER_HEALTH_CRITICAL_PERCENT
  if pastFinalMaxPercHealth > currLowPercent and currFinalMaxPercHealth <= currLowPercent then
    self:SendEventToUIFsm("mainHUD", "EVT_LOW_HEALTH_START")
  end
  if pastFinalMaxPercHealth > currCriticalPercent and currFinalMaxPercHealth <= currCriticalPercent then
    local animObj = meter:GetInstancedFillObject()
    UI.Anim(animObj, consts.AS_Reverse, "health_bar_colorChange", 2)
  end
  if currFinalMaxPercHealth <= 0 then
    UI.Anim(self.healthMeter:GetInstancedFillObject(), consts.AS_Forward, "health_bar_colorChange", 2, 1)
  end
end
function HMOn:OnFillAnimEnd(meter, senderObj, percentComplete)
  if senderObj ~= nil and percentComplete ~= nil then
    local Kratos = FindPlayer()
    local healthSizePercent = self:GetHealthSizePercent()
    local closeEnoughMax = healthSizePercent - consts.FLOAT_COMPARISON_EPSILON
    local closeEnoughMin = consts.FLOAT_COMPARISON_EPSILON
    if percentComplete >= closeEnoughMax then
      meter:PlayFullSound()
    elseif percentComplete <= closeEnoughMin then
      local noop = true
    end
  end
end
function HealthMeter:EVT_REFRESH_HUD()
  local hmOn = self:GetState("HMOn")
  self:StartTimer("UpdateDelay", 0, function()
    hmOn:UpdateHealthMeterSize()
    hmOn:UpdateHealth(true)
  end)
end
function HealthMeter:EVT_Item_Acquired()
  local sender = UI.GetEventSenderGameObject()
  if sender ~= nil and sender.GetPlayer ~= nil and sender:GetPlayer() ~= nil then
    self:EVT_REFRESH_HUD()
  end
end
function HealthMeter:OnSaveCheckpoint(tab)
end
function HealthMeter:OnRestoreCheckpoint(tab)
end
local RageMeter = classlib.Class("RageMeter", fsm.UIState)
local RMOff = RageMeter:StateClass("RMOff", fsm.UIState)
local RMOn = RageMeter:StateClass("RMOn", fsm.UIState)
local rageMeter = RageMeter.New("rageMeter", {RMOff, RMOn})
function RageMeter:Setup()
end
function RageMeter:Enter()
  self:Goto("RMOn")
end
function RageMeter:Reset()
  self:Goto("RMOff")
  self:Enter()
end
function RageMeter:EVT_HandleResourceAmountChange(args)
  if args.ResourceName == "MaxRageUpgrade" then
    local rmOn = self:GetState("RMOn")
    rmOn.previousSizePercent = rmOn.sizePercent
    rmOn.isGrowing = true
  end
end
function RageMeter:EVT_PAUSE_MENU_OPEN()
  if self.playingIncreaseSound then
    self.playingIncreaseSound = false
    game.Audio.StopSound("SND_UX_HUD_Rage_Bar_Increase_LP")
  end
end
function RageMeter:EVT_DM_UNPAUSE()
  local rmOn = self:GetState("RMOn")
  if rmOn.isGrowing then
    self:SendEventToUIFsm("mainHUD", "EVT_MAX_HEALTHRAGE")
    self:StartTimer("MaxHealthRageDelayHelper", 0.3, function()
      rmOn.isGrowing = false
      rmOn.rageMeter:SetPercent(rmOn.previousSizePercent)
      rmOn:UpdateRageMeterSize(true)
      rmOn.rageMeter:SetPercent(rmOn.sizePercent, rmOn.sizePercent - rmOn.previousSizePercent)
      rmOn:UpdateRageMode(rmOn.rageMeter:get_percent())
      self:SendEventToUIFsm("mainHUD", "EVT_PlayHUDSound", "SND_UX_HUD_Rage_Bar_Increase_LP", false)
      self.playingIncreaseSound = true
      self:StartTimer("MaxRageLoopSoundEndHelper", 1, function()
        if self.playingIncreaseSound then
          self.playingIncreaseSound = false
          game.Audio.StopSound("SND_UX_HUD_Rage_Bar_Increase_LP")
        end
      end)
    end)
  end
end
RageMeter.EVT_GAME_START = RageMeter.Reset
RageMeter.EVT_Restart = RageMeter.Reset
function RMOff:Enter()
end
function RMOff:Exit()
end
function RMOff:EVT_METER_CHANGED()
  local markerID = UI.GetEventArgInt(1)
  local idHash = UI.GetEventArgStringHash(2)
  local rmOn = self:GetState("RMOn")
  if rmOn.rageMeter ~= nil and rmOn.rageMeter:ShouldHandleMeterChangedEvent(markerID, idHash) then
    rmOn:UpdateRage(true)
  end
end
function RMOn:Setup()
  self.inRageMode = false
  self.rageModePercent = 1
  self.flamesOn = false
  self.sizePercent = 0
  self.previousSizePercent = 0
  self.isGrowing = false
  self.rageMeterFlourish = util.GetUiObjByName("ragebarflourish")
  self.rageMeter = meter.Meter.New(self, {
    Name = "rage_meter",
    MeterObject = util.GetUiObjByName("rage_meter"),
    MeterInstanceChildName = "rage_bar",
    FillAnimName = "rage_bar_fill",
    FullSound = "SND_UX_Rage_Meter_Filled_02",
    Percent = 0,
    MaxValue = hudConsts.METER_BLOOD_FINAL_MAX,
    IDHash = engine.Hash(hudConsts.METER_BLOOD),
    MarkerID = consts.OT_Player,
    OnMeterChanged = function(meter)
      self:UpdateRage(true)
    end,
    OnGain = function(meter, pastPercent, currPercent)
      self:OnGain(meter, pastPercent, currPercent)
    end
  })
  self:UpdateRageMeterSize()
  self:UpdateRage(false)
  local fillObj = self.rageMeter:GetInstancedFillObject()
  UI.Anim(fillObj, consts.AS_ForwardCycle_NoReset, "rage_bar_loop", 0.1)
end
function RMOn:Enter()
  local Kratos = FindPlayer()
  local currRage = Kratos:MeterGetValue(hudConsts.METER_BLOOD) / hudConsts.METER_BLOOD_FINAL_MAX
  if currRage >= self:GetRageReadyPercent() then
    UI.Anim(self.rageMeter:GetInstancedFillObject(), consts.AS_Forward, "rage_bar_resetActive")
  else
    UI.Anim(self.rageMeter:GetInstancedFillObject(), consts.AS_Forward, "rage_bar_reset")
  end
  self:UpdateRageMeterSize()
  self:UpdateRage(false)
  self.rageMeter:Activate(true)
  self.parent = self:GetState("rageMeter")
end
function RMOn:Exit()
  self.rageMeter:Deactivate(true)
end
function RMOn:GetRageSizePercent()
  local Kratos = FindPlayer()
  return Kratos:MeterGetMax(hudConsts.METER_BLOOD) / hudConsts.METER_BLOOD_FINAL_MAX
end
function RMOn:GetRageReadyPercent()
  return hudConsts.METER_BLOOD_BASE_MAX / hudConsts.METER_BLOOD_FINAL_MAX
end
function RMOn:UpdateRageMeterSize(playAnim)
  if self.isGrowing then
    return
  end
  local sizePercent = self:GetRageSizePercent()
  local animObj = self.rageMeter:GetInstancedFillObject()
  self.sizePercent = sizePercent
  if playAnim then
    UI.Anim(animObj, consts.AS_Forward_NoReset, "rage_bar_maxGrow", self.sizePercent - self.previousSizePercent, self.previousSizePercent, sizePercent)
    UI.Anim(self.rageMeterFlourish, consts.AS_Forward_NoReset, "rage_growflourish_position", self.sizePercent - self.previousSizePercent, self.previousSizePercent, sizePercent)
    UI.Anim(self.rageMeterFlourish, consts.AS_Forward, "rage_growflourish_flash", 1)
  else
    UI.Anim(animObj, consts.AS_Forward, "rage_bar_maxGrow", 0, sizePercent)
    UI.Anim(self.rageMeterFlourish, consts.AS_Forward, "rage_growflourish_position", 0, sizePercent)
    UI.Anim(self.rageMeterFlourish, consts.AS_Forward, "rage_growflourish_flash", 0, 0, 0)
  end
end
function RMOn:UpdateRage(animate)
  local Kratos = FindPlayer()
  local currRage = Kratos:MeterGetValue(hudConsts.METER_BLOOD)
  local animRate
  if animate then
    animRate = 1
  end
  local animObj = self.rageMeter:GetInstancedFillObject()
  local val = 0
  if game.GetEnterRageModeOptionNumber ~= nil then
    val = game.GetEnterRageModeOptionNumber()
  end
  UI.Anim(animObj, consts.AS_Forward, "rage_bar_buttonPrompt", 0, val)
  self.rageMeter:SetPercentByValue(currRage, animRate)
  self:UpdateRageMode(self.rageMeter:get_percent())
end
function RMOn:UpdateRageMode(newPercent)
  local rageReadyPercent = self:GetRageReadyPercent()
  local animObj = self.rageMeter:GetInstancedFillObject()
  if not self.inRageMode then
    if self.flamesOn and newPercent < rageReadyPercent then
      UI.Anim(animObj, consts.AS_Forward, "rage_bar_resetActive", 0, 0, 0)
      UI.Anim(animObj, consts.AS_Forward, "rage_bar_reset")
      self.flamesOn = false
    elseif newPercent > rageReadyPercent and newPercent < self.rageModePercent then
      UI.Anim(animObj, consts.AS_Forward, "rage_bar_resetActive")
    end
  end
  self.rageModePercent = newPercent
end
function RMOn:UpdateRageButtonsVisibility()
  local animObj = self.rageMeter:GetInstancedFillObject()
  local Kratos = FindPlayer()
  if self.flamesOn and not Kratos:PickupIsAcquired("ForceRageModeDisabledLTW") then
    UI.Anim(animObj, consts.AS_Forward, "rage_bar_flamesOn", 0, 1)
    UI.Anim(animObj, consts.AS_Forward, "rage_bar_flourish")
  else
    UI.Anim(animObj, consts.AS_Forward, "rage_bar_flourish", 0, 0)
    UI.Anim(animObj, consts.AS_Forward, "rage_bar_flamesOn", 0, 0)
  end
end
function RMOn:OnGain(meter, pastFinalMaxPercRage, currFinalMaxPercRage)
  local thresholdPercent = 0.1
  local animObj = meter:GetInstancedFillObject()
  if thresholdPercent < currFinalMaxPercRage - pastFinalMaxPercRage then
    UI.Anim(animObj, consts.AS_Forward, "rage_bar_gainLarge", 1)
  end
  if not self.inRageMode and currFinalMaxPercRage >= self:GetRageReadyPercent() and not self.flamesOn then
    self:SendEventToUIFsm("mainHUD", "EVT_PlayHUDSound", "SND_UX_Rage_Meter_Filled_02", false)
    self.flamesOn = true
    local Kratos = FindPlayer()
    if not Kratos:PickupIsAcquired("ForceRageModeDisabledLTW") then
      UI.Anim(animObj, consts.AS_Forward, "rage_bar_flourish")
    end
  end
end
function RMOn:EVT_RAGE_START()
  self.inRageMode = true
  self:UpdateRage(true)
  local animObj = self.rageMeter:GetInstancedFillObject()
  UI.Anim(animObj, consts.AS_Forward, "rage_bar_activation", 1, 0, 1)
  self:SendEventToUIFsm("designerMessage", "EVT_TURN_OFF_DESIGNER_MESSAGE")
end
function RMOn:EVT_RAGE_END()
  self.inRageMode = false
  self:UpdateRage(true)
end
function RMOn:EVT_RAGE_GRAB()
end
function RageMeter:EVT_REFRESH_HUD()
  local rmOn = self:GetState("RMOn")
  self:StartTimer("UpdateDelay", 0, function()
    rmOn:UpdateRageMeterSize()
    rmOn:UpdateRage(true)
  end)
end
function RageMeter:EVT_Item_Acquired()
  local sender = UI.GetEventSenderGameObject()
  local pickupName = Pickup.GetName(UI.GetEventArgInt(1))
  if sender ~= nil and sender.GetPlayer ~= nil and sender:GetPlayer() ~= nil then
    self:EVT_REFRESH_HUD()
  end
  if pickupName == "ForceRageModeDisabledLTW" then
    local rmOn = self:GetState("RMOn")
    self:StartTimer("TimerUpdateDelay", 0, function()
      rmOn:UpdateRageButtonsVisibility()
    end)
  end
end
function RageMeter:EVT_Item_Lost()
  local sender = UI.GetEventSenderGameObject()
  local pickupName = Pickup.GetName(UI.GetEventArgInt(1))
  if sender ~= nil and sender.GetPlayer ~= nil and sender:GetPlayer() ~= nil and pickupName == "ForceRageModeDisabledLTW" then
    local rmOn = self:GetState("RMOn")
    self:StartTimer("TimerUpdateDelay", 0, function()
      rmOn:UpdateRageButtonsVisibility()
    end)
  end
end
function RageMeter:OnSaveCheckpoint(tab)
end
function RageMeter:OnRestoreCheckpoint(tab)
end
local SonHUD = classlib.Class("SonHUD", fsm.UIState)
local SonHUDOff = SonHUD:StateClass("SonHUDOff", fsm.UIState)
local SonHUDOn = SonHUD:StateClass("SonHUDOn", fsm.UIState)
local SonHUDGrabbed = SonHUD:StateClass("SonHUDGrabbed", fsm.UIState)
local SonHUDKnockedDown = SonHUD:StateClass("SonHUDKnockedDown", fsm.UIState)
local SonHUDBusy = SonHUD:StateClass("SonHUDBusy", fsm.UIState)
local sonHUD = SonHUD.New("sonHUD", {
  SonHUDOff,
  SonHUDOn,
  SonHUDGrabbed,
  SonHUDKnockedDown,
  SonHUDBusy
})
function SonHUD:Setup()
  local goCombatHUD = util.GetUiObjByName("CombatHUD")
  self.goSonMeters = goCombatHUD:FindSingleGOByName("SonMeters")
  self.goSonMetersBlur = goCombatHUD:FindSingleGOByName("SonMetersBlur")
  self.resStoneOn = false
  UI.Anim(self.goSonMeters, consts.AS_Forward, "ResStone_Reset")
  self.sonSpecialMeterObj = util.GetUiObjByName("SonSpecial_meter")
  UI.AlphaFade(self.sonSpecialMeterObj, 1, 0)
end
function SonHUD:Enter()
  local son = AI.FindSon()
  if son ~= nil and not self.grabbed then
    local sonCtx = son:GetContext()
    local incapCtx = engine.Hash("INCAPACITATED")
    local extraBusyState = son:HasMarker("TweakPOIResOnly")
    if sonCtx == incapCtx then
      self:Goto("SonHUDKnockedDown")
    elseif util.SonUI_ShouldDisable() or extraBusyState then
      self:Goto("SonHUDBusy")
    else
      self:Goto("SonHUDOn")
    end
  end
  UI.SetGOScreenEdge("SonHUD")
end
function SonHUD:EVT_UPDATE_WINDOW_SIZE()
  UI.SetGOScreenEdge("SonHUD")
end
function SonHUD:Reset()
  if not self.grabbed then
    self:Goto("SonHUDOff")
    self:Enter()
  end
end
function SonHUD:EVT_UI_AvailabilityChanged()
  local isSon = UI.GetEventArgBool(1)
  if isSon then
    self:SendEventUI("EVT_REFRESH_HUD")
    self:Enter()
  end
end
function SonHUD:EVT_SonGrabStart()
  self.grabbed = true
  self:Goto("SonHUDGrabbed")
end
function SonHUD:EVT_SonGrabEnd()
  self.grabbed = false
  self:Reset()
end
function SonHUD:EVT_REFRESH_HUD()
  self:Enter()
  self:UpdateResurrectionStone()
  local son = AI.FindSon()
  if son and son:HasMarker("DisableSummon") then
    UI.AlphaFade(self.sonSpecialMeterObj, 0.5, 0)
  else
    UI.AlphaFade(self.sonSpecialMeterObj, 1, 0)
  end
end
function SonHUD:GetResurrectionStoneValue(resourceName)
  local value = -1
  if resourceUtil.IsValidResourceName(resourceName) then
    value = Wallets.GetResourceValue(resourceConsts.WALLET_KRATOS, resourceName)
    if value <= 0 and resourceUtil.HasFlag(resourceName, resourceConsts.RESOURCE_FLAG_SAVEONLY) then
      value = Wallets.GetResourceValue(resourceConsts.WALLET_KRATOS_SAVEONLY, resourceName)
    end
  end
  return value
end
function SonHUD:UpdateResurrectionStone()
  local Kratos = FindPlayer()
  local valueA = self:GetResurrectionStoneValue("ResurrectionRuneStoneA")
  local valueB = self:GetResurrectionStoneValue("ResurrectionRuneStoneB")
  local valueC = self:GetResurrectionStoneValue("ResurrectionRuneStoneC")
  local shown = -1 < valueA or -1 < valueB or -1 < valueC or Kratos:PickupIsAcquired("ResurrectionStoneFlag")
  local on = 0 < valueA or 0 < valueB or 0 < valueC
  if shown then
    if on then
      if not self.resStoneOn then
        UI.Anim(self.goSonMeters, consts.AS_Forward, "ResStone_Equipped")
      end
      if 0 < valueA then
        UI.Anim(self.goSonMeters, consts.AS_Forward, "ResStone_ColorChange", 0, 0)
      elseif 0 < valueB then
        UI.Anim(self.goSonMeters, consts.AS_Forward, "ResStone_ColorChange", 0, 0.36666667)
      else
        UI.Anim(self.goSonMeters, consts.AS_Forward, "ResStone_ColorChange", 0, 0.7)
      end
    else
      UI.Anim(self.goSonMeters, consts.AS_Forward, "ResStone_Reset")
    end
  else
    UI.Anim(self.goSonMeters, consts.AS_Forward, "ResStone_OFF")
  end
  self.resStoneOn = on
end
function SonHUD:EVT_RES_DEATH_FAIL()
  self:UpdateResurrectionStone()
end
function SonHUD:EVT_RES_DEATH_END()
  self:UpdateResurrectionStone()
end
function SonHUD:EVT_GAME_START()
  self:Reset()
  self:UpdateResurrectionStone()
end
SonHUD.EVT_Restart = SonHUD.Reset
function SonHUDOff:Enter()
end
function SonHUDOff:Exit()
end
function SonHUDOn:Setup()
  local son = AI.FindSon()
  local goCombatHUD = util.GetUiObjByName("CombatHUD")
  self.goSonMeters = goCombatHUD:FindSingleGOByName("SonMeters")
  self.goSonMetersBlur = goCombatHUD:FindSingleGOByName("SonMetersBlur")
  self.goIcon = self.goSonMeters:FindSingleGOByName("icon")
  self.inVendor = false
  self.vendorPickup = nil
  self.vendorPickupCooldown = nil
  self.flourishGOs = {
    [pickupConsts.SonArrow_Light] = self.goSonMeters:FindSingleGOByName("SonArrow_Light_Flourish"),
    [pickupConsts.SonArrow_Shock] = self.goSonMeters:FindSingleGOByName("SonArrow_Shock_Flourish")
  }
  self.currArrow = pickupConsts.SonArrow_Regular
  self.currArrowCount = 0
  self.arrowMeter = meter.Meter.New(self, {
    Name = "son_arrow_meter",
    MeterObject = util.GetUiObjByName("son_arrow_meter"),
    MeterInstanceChildName = "special",
    FillAnimName = "",
    MinValue = 0,
    MaxValue = 1,
    Percent = 1,
    IDHash = engine.Hash(hudConsts.METER_ARROWS),
    MarkerID = consts.OT_Son,
    OnMeterChanged = function(meter)
      self:UpdateArrowMeter()
    end,
    OnFillAnimEnd = function(meter, senderObj, percentComplete)
      self:OnFillAnimEnd(meter, senderObj, percentComplete)
    end
  })
  local goArrowMeterChild = self.arrowMeter:GetInstancedChildObject()
  self.thArrowCount = util.GetTextHandle(goArrowMeterChild, "text")
  self:UpdateArrowMeterMax()
  self.sonSpecialMeter = meterCooldown.MeterCooldown.New(self, {
    Name = "SonSpecial_meter",
    MeterObject = util.GetUiObjByName("SonSpecial_meter"),
    MeterInstanceChildName = "special",
    FillAnimName = "special_fill",
    FlourishAnimName = "special_flourish",
    ReadyAnimName = "special_readyLoop",
    ActivateAnimName = "special_activate",
    SlotName = pickupConsts.Slot_SonSpecial,
    DeactivateOnLoss = false,
    PlayerOnly = true,
    OnCooldownEnd = function(pickup)
      self:SendEventToUIFsm("mainHUD", "EVT_PlayHUDSound", "SND_UX_HUD_Arrows_Son_Special_Ready", true)
    end,
    OnFlourishAnimEnd = function(meter, senderObj, percentComplete)
      if self:IsActive() then
        self:HandleFlourishAnimEnd(meter, senderObj, percentComplete)
      end
    end
  })
  self:UpdateIconsBasedOnCurrentArrow()
  self.arrowMeter:Deactivate(true)
  self.sonSpecialMeter:Activate(false)
  self.sonSpecialMeter:Hide()
end
function SonHUDOn:Enter()
  self.goIcon:Show()
  self.arrowMeter:Activate(true)
  self.sonSpecialMeter:Show()
  self:UpdateSpecialMeter()
  self:UpdateArrowMeterMax()
  self:UpdateArrowMeter()
  local son = AI.FindSon()
  local pickupInSlot = pickupUtil.GetPickupNameInSlot(son, pickupConsts.Slot_SonArrow)
  if pickupInSlot ~= nil then
    self.currArrow = pickupInSlot
  end
  self:UpdateIconsBasedOnCurrentArrow()
end
function SonHUDOn:Exit()
  self.arrowMeter:Deactivate(true)
  self.sonSpecialMeter:Hide()
  self.goIcon:Hide()
end
function SonHUDOn:GetCurrentArrowCount(creature)
  return util.Truncate(creature:MeterGetValue(hudConsts.METER_ARROWS) / creature:MeterGetSegmentSize(hudConsts.METER_ARROWS))
end
function SonHUDOn:GetCurrentMaxArrowCount(creature)
  return creature:MeterGetMax(hudConsts.METER_ARROWS) / creature:MeterGetSegmentSize(hudConsts.METER_ARROWS)
end
function SonHUDOn:GetFinalMaxArrowCount(creature)
  return hudConsts.METER_ARROWS_FINAL_MAX / creature:MeterGetSegmentSize(hudConsts.METER_ARROWS)
end
function SonHUDOn:ShouldHandlePickup(pickupName)
  local shouldHandle = false
  if pickupUtil.IsValidName(pickupName) and pickupUtil.GetSlotName(pickupName) == pickupConsts.Slot_SonArrow then
    shouldHandle = true
  end
  return shouldHandle
end
function SonHUDOn:UpdateArrowCount(creature)
  local arrowCountText = ""
  if creature ~= nil then
    self.currArrowCount = self:GetCurrentArrowCount(creature)
    arrowCountText = self.currArrowCount
  end
  UI.SetText(self.thArrowCount, arrowCountText)
end
function SonHUDOn:UpdateArrowMeterMax()
  local son = AI.FindSon()
  if son ~= nil then
    local segmentSize = son:MeterGetSegmentSize(hudConsts.METER_ARROWS)
    self.arrowMeter:SetMaxValue(segmentSize)
    self:UpdateArrowCount(son)
  end
end
function SonHUDOn:UpdateArrowMeter()
  local Son = AI.FindSon()
  if Son ~= nil then
    local arrowCost = Son:MeterGetSegmentSize(hudConsts.METER_ARROWS)
    local currArrowMeterValue = Son:MeterGetValue(hudConsts.METER_ARROWS)
    local highestMinValue = Son:MeterGetMax(hudConsts.METER_ARROWS) - arrowCost
    local currMeterMinValue = util.Clamp(self.currArrowCount * arrowCost, 0, highestMinValue)
    local currMeterValue = currArrowMeterValue - currMeterMinValue
    self.arrowMeter:SetPercentByValue(currMeterValue, 100)
    self:UpdateArrowCount(Son)
  end
end
function SonHUDOn:UpdateSpecialMeter()
  local Kratos = FindPlayer()
  local pickupName = self.sonSpecialMeter:GetPickupInSlot(Kratos)
  if pickupUtil.IsValidName(pickupName) then
    self.sonSpecialMeter:Activate(true)
    self.sonSpecialMeter:Show()
    self.sonSpecialMeter:Update(pickupName)
  else
    self.sonSpecialMeter:Hide()
  end
end
function SonHUDOn:OnFillAnimEnd(meter, senderObj, percentComplete)
  if senderObj ~= nil and percentComplete ~= nil then
    local closeEnoughMax = 1 - consts.FLOAT_COMPARISON_EPSILON
    local closeEnoughMin = consts.FLOAT_COMPARISON_EPSILON
    if percentComplete >= closeEnoughMax then
      meter:PlayFullSound()
    end
  end
end
function SonHUDOn:HandleFlourishAnimEnd(meter, senderObj, percentComplete)
  if senderObj ~= nil and percentComplete ~= nil then
    local closeEnoughMax = 1 - consts.FLOAT_COMPARISON_EPSILON
    local closeEnoughMin = consts.FLOAT_COMPARISON_EPSILON
    if percentComplete >= closeEnoughMax then
      meter:StartReadyAnim()
    end
  end
end
function SonHUDOn:EVT_Item_Acquired()
  local pickupName = Pickup.GetName(UI.GetEventArgInt(1))
  if self:ShouldHandlePickup(pickupName) then
    if pickupName ~= self.currArrow then
      self.currArrow = pickupName
      self:UpdateIconsBasedOnCurrentArrow()
      if self.currArrow ~= pickupConsts.SonArrow_Regular then
        UI.Anim(self.flourishGOs[self.currArrow], consts.AS_Forward, "", 1, 0, 1)
      end
      self:SendEventToUIFsm("mainHUD", "EVT_TEMP_ON", true)
    end
  elseif not self.inVendor then
    local sender = UI.GetEventSenderGameObject()
    if sender ~= nil and sender.GetPlayer ~= nil and sender:GetPlayer() ~= nil and self.sonSpecialMeter:ShouldHandlePickup(pickupName) then
      self.sonSpecialMeter:Update(pickupName)
      self.sonSpecialMeter:Activate(true)
      self.sonSpecialMeter:Show()
    end
  elseif self.sonSpecialMeter:ShouldHandlePickup(pickupName) then
    self.sonSpecialMeter:SetPercent(1)
    self.sonSpecialMeter:Update(pickupName)
    self.sonSpecialMeter:Activate(true)
    self.sonSpecialMeter:Show()
  end
end
function SonHUDOn:EVT_VENDOR_MENU_OPEN()
  self.inVendor = true
  self.vendorPickup = nil
  self.vendorPickupCooldown = nil
  local Kratos = FindPlayer()
  if Kratos ~= nil and Kratos.PickupGetCooldown ~= nil then
    self.vendorPickup = pickupUtil.GetPickupNameInSlot(Kratos, pickupConsts.Slot_SonSpecial)
    if self.vendorPickup ~= nil then
      self.vendorPickupCooldown = {
        Kratos:PickupGetCooldown(self.vendorPickup)
      }
    end
  end
end
function SonHUDOn:EVT_VENDOR_MENU_CLOSED()
  self.inVendor = false
  local Kratos = FindPlayer()
  if Kratos ~= nil and Kratos.PickupGetCooldown ~= nil and pickupUtil.IsValidName(self.vendorPickup) and Kratos:PickupIsAcquired(self.vendorPickup) then
    local cooldownTime, cooldownMax = unpack(self.vendorPickupCooldown)
    Kratos:PickupSetCooldown(self.vendorPickup, cooldownTime)
    self.sonSpecialMeter:StopReadyAnim()
    self.sonSpecialMeter:SetPercent((cooldownMax - cooldownTime) / cooldownMax)
  end
  self.vendorPickup = nil
  self.vendorPickupCooldown = nil
  self:UpdateSpecialMeter()
end
function SonHUDOn:EVT_CHANGE_SON_MODE(mode)
end
function SonHUDOn:EVT_SON_SHOOT_ARROW()
  self:SendEventToUIFsm("mainHUD", "EVT_TEMP_ON", true)
end
function SonHUDOn:UpdateIconsBasedOnCurrentArrow()
  local childObj = self.sonSpecialMeter:GetInstancedChildObject()
  if pickupConsts.HUDSonCooldownMeterColorTimes[self.currArrow] ~= nil then
    UI.Anim(childObj, consts.AS_Forward, "special_colorchange", 0, pickupConsts.HUDSonCooldownMeterColorTimes[self.currArrow])
  end
  self.goIcon:SetMaterialSwap(self.currArrow)
end
function SonHUDGrabbed:Setup()
  local goCombatHUD = util.GetUiObjByName("CombatHUD")
  self.goSonMeters = goCombatHUD:FindSingleGOByName("SonMeters")
  self.goSonMetersBlur = goCombatHUD:FindSingleGOByName("SonMetersBlur")
  self.inDanger = self.goSonMeters:FindSingleGOByName("InDanger")
  self.inDanger:Hide()
  self.grabbedMeter = meterCooldown.MeterCooldown.New(self, {
    Name = "GrabbedMeter",
    MeterObject = self.inDanger,
    MeterInstanceChildName = "root",
    FillAnimName = "sonStatus_InDanger_Fill",
    FlourishAnimName = "sonStatus_InDanger_Flourish",
    ListenForTimer = true,
    SlotName = pickupConsts.Slot_TalismanCooldown,
    PickupName = {
      "Son_EnemyGrabTimer",
      "Son_FlyerGrabTimer",
      "MuspelheimSonGrabTimer"
    },
    DeactivateOnLoss = false,
    FlourishAnimStyle = consts.AS_ForwardCycle,
    FlourishNeedsInit = false,
    OnCooldownEnd = function()
      self:SendEventToUIFsm("mainHUD", "EVT_PlayHUDSound", "SND_UX_HUD_Arrows_Son_Unavailable_Knocked_Out", true)
    end
  })
end
function SonHUDGrabbed:Enter()
  self.grabbedMeter:Activate(true)
  local animObj = self.grabbedMeter:GetInstancedChildObject()
  UI.Anim(animObj, consts.AS_ForwardCycle, "sonStatus_InDanger_IconFlash")
  self:SendEventToUIFsm("mainHUD", "EVT_PlayHUDSound", "SND_UX_HUD_Arrows_Son_Injured", true)
end
function SonHUDGrabbed:Exit()
  local animObj = self.grabbedMeter:GetInstancedChildObject()
  UI.Anim(animObj, consts.AS_Forward, self.grabbedMeter._flourishAnimName, 0, 0)
  UI.Anim(animObj, consts.AS_Forward, "sonStatus_InDanger_Reset")
  self.grabbedMeter:Deactivate(true)
end
function SonHUDGrabbed:Update()
  local son = AI.FindSon()
  local sonCtx = son:GetContext()
  local enemyHoldCtx = engine.Hash("SONREACT_ENEMYHOLD")
  local incapCtx = engine.Hash("INCAPACITATED")
  if sonCtx ~= enemyHoldCtx and sonCtx ~= incapCtx then
    self:SendEventUI("EVT_SonGrabEnd")
  end
end
function SonHUDKnockedDown:Setup()
  local goCombatHUD = util.GetUiObjByName("CombatHUD")
  self.goSonMeters = goCombatHUD:FindSingleGOByName("SonMeters")
  self.knockedDown = self.goSonMeters:FindSingleGOByName("KnockedDown")
  self.knockedDown:Hide()
end
function SonHUDKnockedDown:Enter()
  self.knockedDown:Show()
  self:SendEventToUIFsm("mainHUD", "EVT_PlayHUDSound", "SND_UX_HUD_Arrows_Son_Unavailable_Knocked_Down", true)
end
function SonHUDKnockedDown:Exit()
  self.knockedDown:Hide()
end
function SonHUDBusy:Setup()
  local goCombatHUD = util.GetUiObjByName("CombatHUD")
  self.goSonMeters = goCombatHUD:FindSingleGOByName("SonMeters")
  self.busy = self.goSonMeters:FindSingleGOByName("Busy")
  self.busy:Hide()
end
function SonHUDBusy:Enter()
  self.busy:Show()
  UI.Anim(self.busy, consts.AS_ForwardCycle_NoReset, "", 0.1)
  self:SendEventToUIFsm("mainHUD", "EVT_PlayHUDSound", "SND_UX_HUD_Arrows_Son_Unavailable_Disabled", true)
end
function SonHUDBusy:Exit()
  self.busy:Hide()
end
function SonHUD:OnSaveCheckpoint(tab)
end
function SonHUD:OnRestoreCheckpoint(tab)
end
local SpecialMeters = classlib.Class("SpecialMeters", fsm.UIState)
local SMOff = SpecialMeters:StateClass("SMOff", fsm.UIState)
local SMOn = SpecialMeters:StateClass("SMOn", fsm.UIState)
local specialMeters = SpecialMeters.New("specialMeters", {SMOff, SMOn})
function SpecialMeters:Enter()
  self:WantPadEvents(true)
  self:Goto("SMOn")
end
function SpecialMeters:Reset()
  self:Goto("SMOff")
  self:Enter()
end
SpecialMeters.EVT_GAME_START = SpecialMeters.Reset
SpecialMeters.EVT_Restart = SpecialMeters.Reset
function SMOff:Enter()
end
function SMOff:Exit()
end
function SMOn:Setup()
  self.currentWeapon = pickupConsts.Bare
  self.stowedWeapon = pickupConsts.Axe
  self.isBlocking = false
  self.isAiming = false
  self.canAxeBeRecalled = false
  self.inRageMode = false
  self.inVendor = false
  local goCombatHUD = util.GetUiObjByName("CombatHUD")
  self.goCornerHUDBlur = goCombatHUD:FindSingleGOByName("CornerHUD_Blur")
  self.cooldownMeters = {}
  self.goButtonPrompts = {}
  self.vendorLastCooldowns = {}
  local goSpecialMeters = util.GetUiObjByName("SpecialMeters")
  for _, pickupSlotName in ipairs(pickupConsts.HUDCooldownMeterSlots) do
    self:CreateCooldownMeter(goSpecialMeters, pickupSlotName)
  end
  for _, buttonPromptName in ipairs(pickupConsts.HUDSpecialMetersButtonPrompts) do
    self.goButtonPrompts[buttonPromptName] = goSpecialMeters:FindSingleGOByName("WeaponSpecial_" .. buttonPromptName)
  end
  self.goWeaponIcon = goSpecialMeters:FindSingleGOByName("WeaponIcon")
  self.goWeaponIcon:Show()
  self.goWeaponIcon:SetMaterialSwap(self.currentWeapon)
  self.momentumMeters = {}
  self.momentumIDToWeapon = {
    [hudConsts.METER_MOMENTUM] = pickupConsts.Axe,
    [hudConsts.METER_MOMENTUM_BLADES] = pickupConsts.Blades
  }
  self:CreateMomentumMeter(goSpecialMeters, hudConsts.METER_MOMENTUM)
  self:CreateMomentumMeter(goSpecialMeters, hudConsts.METER_MOMENTUM_BLADES)
  self.talismanOfBetrayalMeter = meter.Meter.New(self, {
    Name = "TalismanOfBetrayalMeter",
    MeterObject = util.GetUiObjByName("talismanOfBetrayalMeter"),
    MeterInstanceChildName = "meter",
    FillAnimName = "",
    Percent = 1,
    IDHash = engine.Hash("Focus"),
    MarkerID = consts.OT_Player,
    OnMeterChanged = function(meter)
      self:TalismanOfBetrayal_OnMeterChanged()
    end
  })
end
function SMOn:Enter()
  self.goCornerHUDBlur:Show()
  self:UpdateWeapon()
  self:UpdateCooldownMeters()
  self:UpdateMomentumMeters()
  UI.SetGOScreenEdge("CornerHUD")
end
function SMOn:EVT_UPDATE_WINDOW_SIZE()
  UI.SetGOScreenEdge("CornerHUD")
end
function SMOn:Exit()
  self.goCornerHUDBlur:Hide()
  for _, cooldownMeter in pairs(self.cooldownMeters) do
    cooldownMeter:Deactivate(true)
  end
  for _, momentumMeter in pairs(self.momentumMeters) do
    momentumMeter:Deactivate(true)
  end
end
function SMOn:CreateCooldownMeter(goSpecialMeters, pickupSlotName)
  local meterName = pickupSlotName .. "_meter"
  self.cooldownMeters[pickupSlotName] = meterCooldown.MeterCooldown.New(self, {
    Name = meterName,
    MeterObject = goSpecialMeters:FindSingleGOByName(meterName),
    MeterInstanceChildName = "special",
    FillAnimName = "special_fill",
    FlourishAnimName = "special_flourish",
    ReadyAnimName = "special_readyLoop",
    ActivateAnimName = "special_activate",
    SlotName = pickupSlotName,
    PlayerOnly = true,
    DeactivateOnLoss = false,
    OnFillAnimEnd = function(meter, senderObj, percentComplete)
      self:Cooldown_OnFillAnimEnd(meter, senderObj, percentComplete)
    end,
    OnFlourishAnimEnd = function(meter, senderObj, percentComplete)
      self:Cooldown_HandleFlourishAnimEnd(meter, senderObj, percentComplete)
    end,
    OnCooldownEnd = function(pickup)
      self:SendEventToUIFsm("mainHUD", "EVT_PlayHUDSound", pickupConsts.HudCooldownMeterFullSounds[pickupSlotName], false)
    end
  })
end
function SMOn:CreateMomentumMeter(goSpecialMeters, meterID)
  local meterName = meterID .. "_meter"
  self.momentumMeters[meterID] = meter.Meter.New(self, {
    Name = meterName,
    MeterObject = goSpecialMeters:FindSingleGOByName(meterName),
    MeterInstanceChildName = "meter",
    FillAnimName = "",
    FullSound = "SND_UX_HUD_Momentum_Meter_Fill",
    Percent = 0,
    MaxValue = hudConsts.METER_MOMENTUM_FINAL_MAX,
    IDHash = engine.Hash(meterID),
    MarkerID = consts.OT_Player,
    OnMeterChanged = function(meter)
      self:Momentum_OnMeterChanged(meterID, true)
    end,
    OnGain = function(meter, prevPercent, newPercent)
      self:Momentum_OnGain(meter, prevPercent, newPercent)
    end,
    OnLose = function(meter, prevPercent, newPercent)
      self:Momentum_OnLose(meter, prevPercent, newPercent)
    end
  })
  local goMeterChild = self.momentumMeters[meterID]:GetInstancedChildObject()
  local goMomentumFlourish = goMeterChild:FindSingleGOByName("MomentumFlourish")
  UI.Anim(goMomentumFlourish, consts.AS_Forward, "", 0, 0)
  self:Momentum_OnMeterChanged(meterID, false)
end
function SMOn:UpdateWeapon()
  local Kratos = FindPlayer()
  if self.stowedWeapon ~= self.currentWeapon then
    self.stowedWeapon = self.currentWeapon
  end
  self.currentWeapon = Kratos:GetCurrentWeapon()
  self.goWeaponIcon:SetMaterialSwap(self.currentWeapon)
  self:HideButtonPrompt("AxeRecallButton")
  if not self.inRageMode and self.canAxeBeRecalled == true then
    self:ShowButtonPrompt("AxeRecallButton")
  end
end
function SMOn:ShouldUpdateCooldownMeter(pickupSlotName, currentWeapon)
  return pickupSlotName == pickupConsts.Slot_TalismanCooldown or pickupUtil.WeaponSpecialSlotDisplayForWeapon(pickupSlotName, currentWeapon) == true
end
function SMOn:ShouldFadeOutCooldownMeter(pickupSlotName, currentWeapon)
  return self.inRageMode or pickupSlotName ~= pickupConsts.Slot_TalismanCooldown and pickupUtil.WeaponSpecialSlotBelongsToWeapon(pickupSlotName, currentWeapon) == false
end
function SMOn:GetCooldownMeterColorTime(cooldownSlotName, pickupName)
  if cooldownSlotName ~= pickupConsts.Slot_TalismanCooldown then
    return pickupConsts.HUDCooldownMeterColorTimes[cooldownSlotName]
  end
  local rarityColor
  local rarity, rarityIndex = resourceUtil.GetRarity(pickupName)
  rarityIndex = rarityIndex - 2
  if 0 <= rarityIndex then
    rarityColor = rarityIndex * 1 / 3
  end
  return rarityColor
end
function SMOn:UpdateCooldownMeter(cooldownMeter, cooldownSlotName, pickupName)
  cooldownMeter:Activate(false)
  local weaponToShowStuffFor = self.currentWeapon ~= pickupConsts.Bare and self.currentWeapon or self.stowedWeapon
  if self:ShouldUpdateCooldownMeter(cooldownSlotName, weaponToShowStuffFor) then
    cooldownMeter:Activate(true)
    cooldownMeter:Update(pickupName)
    local childObj = cooldownMeter:GetInstancedChildObject()
    local colorTime = self:GetCooldownMeterColorTime(cooldownSlotName, pickupName)
    if colorTime ~= nil then
      UI.Anim(childObj, consts.AS_Forward, "special_colorchange", 0, colorTime)
    end
    if self:ShouldFadeOutCooldownMeter(cooldownSlotName, self.currentWeapon) then
      UI.AlphaFade(cooldownMeter:get_gameObject(), 0.5, 0)
    else
      UI.AlphaFade(cooldownMeter:get_gameObject(), 1, 0)
    end
    cooldownMeter:Show()
  else
    cooldownMeter:Hide()
  end
end
function SMOn:UpdateCooldownMeters()
  local Kratos = FindPlayer()
  for cooldownSlotName, cooldownMeter in pairs(self.cooldownMeters) do
    local pickupName = cooldownMeter:GetPickupInSlot(Kratos)
    if cooldownSlotName == pickupConsts.Slot_TalismanCooldown then
      pickupName = pickupUtil.GetPickupNameInSlot(Kratos, pickupConsts.Slot_ArmorTrinket)
    end
    if pickupUtil.IsValidName(pickupName) then
      self:UpdateCooldownMeter(cooldownMeter, cooldownSlotName, pickupName)
    else
      cooldownMeter:Deactivate(true)
    end
  end
  self:UpdateWeaponSpecialButtonPrompts()
end
function SMOn:ShouldUpdateMomentumMeter(weaponFromID)
  return weaponFromID == self.currentWeapon
end
function SMOn:UpdateMomentumMeter(momentumMeter, weaponFromID)
  momentumMeter:Activate(false)
  if self:ShouldUpdateMomentumMeter(weaponFromID) then
    momentumMeter:Show()
  else
    momentumMeter:Hide()
  end
end
function SMOn:UpdateMomentumMeters()
  for momentumMeterID, momentumMeter in pairs(self.momentumMeters) do
    local weaponFromID = self.momentumIDToWeapon[momentumMeterID]
    if pickupUtil.IsValidName(weaponFromID) then
      self:UpdateMomentumMeter(momentumMeter, weaponFromID)
    else
      momentumMeter:Deactivate(true)
    end
  end
end
function SMOn:HideButtonPrompt(buttonPromptName)
  local goButtonPrompt = self.goButtonPrompts[buttonPromptName]
  if goButtonPrompt ~= nil then
    local animTime = 0.1
    UI.AlphaFade(goButtonPrompt, 0, animTime)
  end
end
function SMOn:ShowButtonPrompt(buttonPromptName)
  local goButtonPrompt = self.goButtonPrompts[buttonPromptName]
  if goButtonPrompt ~= nil then
    local animTime = 0.1
    UI.AlphaFade(goButtonPrompt, 1, animTime)
    goButtonPrompt:Show()
  end
end
function SMOn:IsSpecialWeaponButtonPromptVisible(pickupSlots)
  local Kratos = FindPlayer()
  local isButtonPromptVisible = false
  for _, pickupSlotName in ipairs(pickupSlots) do
    if pickupSlotName == pickupConsts.Slot_TalismanCooldown and self.cooldownMeters[pickupSlotName] ~= nil then
      local talismanPickup = pickupUtil.GetPickupNameInSlot(Kratos, pickupConsts.Slot_ArmorTrinket)
      if talismanPickup ~= nil and not pickupUtil.HasTag(talismanPickup, pickupConsts.TAG_PICKUP_TALISMAN_PASSIVE) and not pickupUtil.HasTag(talismanPickup, pickupConsts.TAG_PICKUP_TALISMAN_ACTIVATION_REACTRECOVER) and talismanPickup ~= "Perk_Utility_Focus_Mode" and talismanPickup ~= "Perk_Utility_Focus_Mode_NGP" then
        isButtonPromptVisible = true
        break
      end
    end
    if self:ShouldUpdateCooldownMeter(pickupSlotName, self.currentWeapon) and pickupUtil.WeaponSpecialSlotBelongsToWeapon(pickupSlotName, self.currentWeapon) then
      local cooldownMeter = self.cooldownMeters[pickupSlotName]
      if cooldownMeter ~= nil then
        local pickupInSlot = cooldownMeter:GetPickupInSlot(Kratos)
        if pickupUtil.IsValidName(pickupInSlot) then
          isButtonPromptVisible = true
          break
        end
      end
    end
  end
  return isButtonPromptVisible
end
function SMOn:TalismanOfBetrayalDisplay()
  local Kratos = FindPlayer()
  local hasTalismanOfBetrayal = Kratos ~= nil and (Kratos:PickupIsAcquired("Perk_Utility_Focus_Mode") or Kratos:PickupIsAcquired("Perk_Utility_Focus_Mode_NGP"))
  if hasTalismanOfBetrayal then
    local prevActive = self.talismanOfBetrayalMeter:get_active()
    self.talismanOfBetrayalMeter:Activate(true)
    if not prevActive then
      UI.AlphaFade(self.talismanOfBetrayalMeter:get_gameObject(), 0, 0)
    end
  else
    self.talismanOfBetrayalMeter:Deactivate(true)
  end
  local shouldShow = false
  if self.isAiming and hasTalismanOfBetrayal and self.currentWeapon ~= pickupConsts.Bare then
    shouldShow = true
  end
  if shouldShow then
    self:ShowButtonPrompt("TalismanOfBetrayalR3")
  else
    self:HideButtonPrompt("TalismanOfBetrayalR3")
  end
  self:TalismanOfBetrayal_OnMeterChanged()
end
function SMOn:UpdateWeaponSpecialButtonPrompts()
  for buttonPromptName, pickupSlotsTable in pairs(pickupConsts.HUDCooldownMeterSlotsPerButtonPrompt) do
    if self.isBlocking == true and self:IsSpecialWeaponButtonPromptVisible(pickupSlotsTable) then
      self:ShowButtonPrompt(buttonPromptName)
    else
      self:HideButtonPrompt(buttonPromptName)
    end
  end
  self:TalismanOfBetrayalDisplay()
end
function SMOn:Cooldown_OnFillAnimEnd(meter, senderObj, percentComplete)
  if senderObj ~= nil and percentComplete ~= nil then
    local closeEnoughMax = 1 - consts.FLOAT_COMPARISON_EPSILON
    local closeEnoughMin = consts.FLOAT_COMPARISON_EPSILON
    if percentComplete >= closeEnoughMax then
      meter:PlayFullSound()
    elseif percentComplete <= closeEnoughMin then
      local noop = true
    end
  end
end
function SMOn:Cooldown_HandleFlourishAnimEnd(meter, senderObj, percentComplete)
  if senderObj ~= nil and percentComplete ~= nil then
    local closeEnoughMax = 1 - consts.FLOAT_COMPARISON_EPSILON
    local closeEnoughMin = consts.FLOAT_COMPARISON_EPSILON
    if percentComplete >= closeEnoughMax then
      meter:StartReadyAnim()
    elseif percentComplete <= closeEnoughMin then
      local noop = true
    end
  end
end
function SMOn:Momentum_OnMeterChanged(meterID, animate)
  local Kratos = FindPlayer()
  local currMeter = self.momentumMeters[meterID]
  local prevMomentum = currMeter:GetValue()
  local currMomentum = 0
  local animRate
  if Kratos:HasMeter(meterID) then
    currMomentum = Kratos:MeterGetValue(meterID)
  end
  if animate and prevMomentum < currMomentum then
    animRate = 1
  end
  currMeter:SetPercentByValue(currMomentum, animRate)
end
function SMOn:TalismanOfBetrayal_OnMeterChanged()
  if self.talismanOfBetrayalMeter:get_active() then
    local Kratos = FindPlayer()
    if Kratos and Kratos:HasMeter("Focus") then
      local value = Kratos:MeterGetValue("Focus")
      local max = Kratos:MeterGetMax("Focus")
      local percent = value / max
      self.talismanOfBetrayalMeter:SetPercent(percent)
      if self.isAiming and percent < 1 then
        UI.AlphaFade(self.talismanOfBetrayalMeter:get_gameObject(), 1, 0.3)
      else
        UI.AlphaFade(self.talismanOfBetrayalMeter:get_gameObject(), 0, 0.3)
      end
    end
  end
end
function SMOn:Momentum_OnGain(meter, prevMomentum, currMomentum)
  if 1 <= currMomentum then
    meter:PlayFullSound()
    local goMeterChild = meter:GetInstancedChildObject()
    local goMomentumFlourish = goMeterChild:FindSingleGOByName("MomentumFlourish")
    UI.Anim(goMomentumFlourish, consts.AS_Forward, "", 1, 0, 1)
  end
end
function SMOn:Momentum_OnLose(meter, prevMomentum, currMomentum)
  if 1 <= prevMomentum then
    local goMeterChild = meter:GetInstancedChildObject()
    local goMomentumFlourish = goMeterChild:FindSingleGOByName("MomentumFlourish")
    UI.Anim(goMomentumFlourish, consts.AS_Forward, "", 0, 0)
  end
end
function SMOn:IsGOInHand(gameObject)
  local throwOutStatus = gameObject.ThrowOutStatus
  return throwOutStatus == tweaks.tThrowOutStatus.eThrownWeaponStatus.kTOSInHand or throwOutStatus == tweaks.tThrowOutStatus.eThrownWeaponStatus.kTOSInHandThrowStart or throwOutStatus == tweaks.tThrowOutStatus.eThrownWeaponStatus.kTOSStowed
end
function SMOn:EVT_Item_Acquired()
  local pickupName = Pickup.GetName(UI.GetEventArgInt(1))
  local slotName = pickupUtil.GetSlotName(pickupName)
  if not self.inVendor then
    local sender = UI.GetEventSenderGameObject()
    if sender ~= nil and sender.GetPlayer ~= nil and sender:GetPlayer() ~= nil then
      for cooldownSlotName, cooldownMeter in pairs(self.cooldownMeters) do
        local isTalisman = cooldownSlotName == pickupConsts.Slot_TalismanCooldown and (slotName == pickupConsts.Slot_ArmorTrinket or slotName == pickupConsts.Slot_TalismanCooldown)
        if isTalisman or cooldownMeter:get_active() == false and cooldownMeter:ShouldHandlePickup(pickupName) then
          self:UpdateCooldownMeter(cooldownMeter, cooldownSlotName, pickupName)
        end
      end
      self:UpdateWeaponSpecialButtonPrompts()
    end
  else
    local meter = self.cooldownMeters[slotName]
    if meter ~= nil and meter:ShouldHandlePickup(pickupName) then
      meter:SetPercent(1)
      self:UpdateCooldownMeter(meter, slotName, pickupName)
    end
  end
end
function SMOn:EVT_OnPlayerWeaponChanged(weaponName)
  self:UpdateWeapon()
  self:UpdateCooldownMeters()
  self:UpdateMomentumMeters()
end
function SMOn:EVT_OnPlayerWeaponEvent(goWeapon, eventName)
  if goWeapon ~= nil then
    local weaponGOName = goWeapon:GetName()
    if weaponGOName == consts.CREATURE_NAME_AXE then
      self.canAxeBeRecalled = not self:IsGOInHand(goWeapon)
      self:UpdateWeapon()
    end
  end
end
function SMOn:EVT_BLOCK_START()
  self.isBlocking = true
  self:UpdateWeaponSpecialButtonPrompts()
end
function SMOn:EVT_BLOCK_END()
  self.isBlocking = false
  self:UpdateWeaponSpecialButtonPrompts()
end
function SMOn:EVT_AIM_START()
  self.isAiming = true
  self:UpdateWeaponSpecialButtonPrompts()
end
function SMOn:EVT_AIM_END()
  self.isAiming = false
  self:UpdateWeaponSpecialButtonPrompts()
end
function SMOn:EVT_RAGE_START()
  self.inRageMode = true
  self:UpdateWeapon()
end
function SMOn:EVT_RAGE_END()
  self.inRageMode = false
  self:UpdateWeapon()
end
function SMOn:EVT_VENDOR_MENU_OPEN()
  self.inVendor = true
  tablex.Clear(self.vendorLastCooldowns)
  local Kratos = FindPlayer()
  if Kratos ~= nil and Kratos.PickupGetCooldown ~= nil then
    for _, pickupSlotName in ipairs(pickupConsts.HUDCooldownMeterSlots) do
      local pickupName = pickupUtil.GetPickupNameInSlot(Kratos, pickupSlotName)
      if pickupUtil.IsValidName(pickupName) then
        self.vendorLastCooldowns[pickupName] = {
          Kratos:PickupGetCooldown(pickupName)
        }
      end
    end
  end
end
function SMOn:EVT_VENDOR_MENU_CLOSED()
  self.inVendor = false
  local Kratos = FindPlayer()
  if Kratos ~= nil and Kratos.PickupSetCooldown ~= nil then
    for pickupName, cooldownData in pairs(self.vendorLastCooldowns) do
      if Kratos:PickupIsAcquired(pickupName) then
        local cooldownTime, cooldownMax = unpack(cooldownData)
        Kratos:PickupSetCooldown(pickupName, cooldownTime)
        local slot = pickupUtil.GetSlotName(pickupName)
        local meter = self.cooldownMeters[slot]
        if meter ~= nil then
          meter:StopReadyAnim()
          meter:SetPercent((cooldownMax - cooldownTime) / cooldownMax)
        end
      end
    end
  end
  self:UpdateCooldownMeters()
  tablex.Clear(self.vendorLastCooldowns)
end
function SpecialMeters:OnSaveCheckpoint(tab)
end
function SpecialMeters:OnRestoreCheckpoint(tab)
end
