local ARROW_REGENERATION_TEMPLATE = "ZO_ArrowRegeneration" ZO_UnitVisualizer_ArrowRegenerationModule = ZO_UnitAttributeVisualizerModuleBase:Subclass() function ZO_UnitVisualizer_ArrowRegenerationModule:New(...) return ZO_UnitAttributeVisualizerModuleBase.New(self, ...) end local g_numModulesCreated = 0 local g_numArrowsCreated = 0 function ZO_UnitVisualizer_ArrowRegenerationModule:Initialize() local function OnArrowAnimationStopped(timeline) if timeline.hasControlOfBar then self:GetOwner():NotifyEndingControlOf(timeline.bar) timeline.hasControlOfBar = false end self.arrowPool:ReleaseObject(timeline.key) end local function CreateArrow() g_numArrowsCreated = g_numArrowsCreated + 1 local arrow = CreateControlFromVirtual(ARROW_REGENERATION_TEMPLATE, GuiRoot, ARROW_REGENERATION_TEMPLATE, g_numArrowsCreated) arrow.animation = ANIMATION_MANAGER:CreateTimelineFromVirtual("ArrowRegenerationAnimation", arrow) arrow.animation:SetHandler("OnStop", OnArrowAnimationStopped) return arrow end local function ResetArrow(arrow) arrow:SetHidden(true) arrow:SetParent(nil) arrow:ClearAnchors() end self.arrowPool = ZO_ObjectPool:New(CreateArrow, ResetArrow) end function ZO_UnitVisualizer_ArrowRegenerationModule:GetInitialStatValue(stat, attribute, powerType) return self:GetInitialValueAndMarkMostRecent(ATTRIBUTE_VISUAL_INCREASED_REGEN_POWER, stat, attribute, powerType) + self:GetInitialValueAndMarkMostRecent(ATTRIBUTE_VISUAL_DECREASED_REGEN_POWER, stat, attribute, powerType) end function ZO_UnitVisualizer_ArrowRegenerationModule:OnAdded(healthBarControl, magickaBarControl, staminaBarControl) self.barControls = { [STAT_HEALTH_REGEN_COMBAT] = healthBarControl, [STAT_MAGICKA_REGEN_COMBAT] = magickaBarControl, [STAT_STAMINA_REGEN_COMBAT] = staminaBarControl, } if IsPlayerActivated() then self:InitializeBarValues() end EVENT_MANAGER:RegisterForEvent("ZO_UnitVisualizer_ArrowRegenerationModule" .. self:GetModuleId(), EVENT_PLAYER_ACTIVATED, function() self:InitializeBarValues() end) end function ZO_UnitVisualizer_ArrowRegenerationModule:InitializeBarValues() if not self.barInfo then EVENT_MANAGER:RegisterForUpdate("ZO_UnitVisualizer_ArrowRegenerationModule" .. self:GetModuleId(), 85, function() self:Pulse() end) end local healthBarControl = self.barControls[STAT_HEALTH_REGEN_COMBAT] local magickaBarControl = self.barControls[STAT_MAGICKA_REGEN_COMBAT] local staminaBarControl = self.barControls[STAT_STAMINA_REGEN_COMBAT] self.barInfo = { [STAT_HEALTH_REGEN_COMBAT] = healthBarControl and { value = self:GetInitialStatValue(STAT_HEALTH_REGEN_COMBAT, ATTRIBUTE_HEALTH, COMBAT_MECHANIC_FLAGS_HEALTH), arrowsRemaining = 0, tickDelay = 1 } or nil, [STAT_MAGICKA_REGEN_COMBAT] = magickaBarControl and { value = self:GetInitialStatValue(STAT_MAGICKA_REGEN_COMBAT, ATTRIBUTE_MAGICKA, COMBAT_MECHANIC_FLAGS_MAGICKA), arrowsRemaining = 0, tickDelay = 0 } or nil, [STAT_STAMINA_REGEN_COMBAT] = staminaBarControl and { value = self:GetInitialStatValue(STAT_STAMINA_REGEN_COMBAT, ATTRIBUTE_STAMINA, COMBAT_MECHANIC_FLAGS_STAMINA), arrowsRemaining = 0, tickDelay = 0 } or nil, } end function ZO_UnitVisualizer_ArrowRegenerationModule:OnUnitChanged() self:InitializeBarValues() end function ZO_UnitVisualizer_ArrowRegenerationModule:IsUnitVisualRelevant(visualType, stat, attribute, powerType) if visualType == ATTRIBUTE_VISUAL_INCREASED_REGEN_POWER or visualType == ATTRIBUTE_VISUAL_DECREASED_REGEN_POWER then return self.barInfo and self.barInfo[stat] ~= nil end return false end function ZO_UnitVisualizer_ArrowRegenerationModule:OnUnitAttributeVisualAdded(visualType, stat, attribute, powerType, value) local lastValue = self.barInfo[stat].value self.barInfo[stat].value = lastValue + value self:OnValueChanged(self.barInfo[stat], self.barControls[stat], lastValue, stat) end function ZO_UnitVisualizer_ArrowRegenerationModule:OnUnitAttributeVisualUpdated(visualType, stat, attribute, powerType, oldValue, newValue) local lastValue = self.barInfo[stat].value self.barInfo[stat].value = lastValue + (newValue - oldValue) self:OnValueChanged(self.barInfo[stat], self.barControls[stat], lastValue, stat) end function ZO_UnitVisualizer_ArrowRegenerationModule:OnUnitAttributeVisualRemoved(visualType, stat, attribute, powerType, value) local lastValue = self.barInfo[stat].value self.barInfo[stat].value = lastValue - value self:OnValueChanged(self.barInfo[stat], self.barControls[stat], lastValue, stat) end function ZO_UnitVisualizer_ArrowRegenerationModule:PlaySound(stat, increasing, hadOppositeEffect, noEffects) if hadOppositeEffect then if increasing then self.owner:PlaySoundFromStat(stat, STAT_STATE_DECREASE_LOST) else self.owner:PlaySoundFromStat(stat, STAT_STATE_INCREASE_LOST) end end if not noEffects then if increasing then self.owner:PlaySoundFromStat(stat, STAT_STATE_INCREASE_GAINED) TriggerTutorial(TUTORIAL_TRIGGER_COMBAT_STATUS_EFFECT) else self.owner:PlaySoundFromStat(stat, STAT_STATE_DECREASE_GAINED) TriggerTutorial(TUTORIAL_TRIGGER_COMBAT_STATUS_EFFECT) end end end function ZO_UnitVisualizer_ArrowRegenerationModule:OnValueChanged(info, bar, oldValue, stat) if info.value < 0 and oldValue >= 0 then info.takeControl = true local DECREASING = false self:PlaySound(stat, DECREASING, oldValue ~= 0) elseif info.value > 0 and oldValue <= 0 then info.takeControl = true local INCREASING = true self:PlaySound(stat, INCREASING, oldValue ~= 0) elseif info.value == 0 and oldValue ~= 0 then local HAD_OPPOSITE_EFFECT = true local NO_EFFECTS = true self:PlaySound(stat, oldValue < 0, HAD_OPPOSITE_EFFECT, NO_EFFECTS) end end local MAX_ARROWS = 3 local NUM_TICKS_PER_MAJOR = MAX_ARROWS * 4 function ZO_UnitVisualizer_ArrowRegenerationModule:Pulse() self.ticks = (self.ticks or 0) + 1 local isMajorTick = self.ticks % NUM_TICKS_PER_MAJOR == 0 for stat, bar in pairs(self.barControls) do local info = self.barInfo[stat] if isMajorTick then if info.value ~= 0 and info.arrowsRemaining == 0 then self:PlayArrow(bar, stat, info.value, info.takeControl) info.takeControl = false info.currentTickDelay = info.tickDelay info.arrowsRemaining = self:GetNumArrowsByStat(stat, info.value) - 1 if info.arrowsRemaining > 0 then info.lastValue = info.value end end elseif info.arrowsRemaining > 0 then if info.currentTickDelay == 0 then self:PlayArrow(bar, stat, info.lastValue) info.arrowsRemaining = info.arrowsRemaining - 1 info.currentTickDelay = info.tickDelay else info.currentTickDelay = info.currentTickDelay - 1 end end end end local STAT_TO_COMBAT_MECHANIC = { [STAT_HEALTH_REGEN_COMBAT] = COMBAT_MECHANIC_FLAGS_HEALTH, [STAT_MAGICKA_REGEN_COMBAT] = COMBAT_MECHANIC_FLAGS_MAGICKA, [STAT_STAMINA_REGEN_COMBAT] = COMBAT_MECHANIC_FLAGS_STAMINA, } function ZO_UnitVisualizer_ArrowRegenerationModule:GetNumArrowsByStat(stat, value) local _, maxPower = GetUnitPower("player", STAT_TO_COMBAT_MECHANIC[stat]) local percentRegen = zo_abs(value) / maxPower return zo_clamp(zo_round(percentRegen * 20), 1, MAX_ARROWS) end function ZO_UnitVisualizer_ArrowRegenerationModule:AcquireArrow(bar) local arrow, key = self.arrowPool:AcquireObject() ApplyTemplateToControl(arrow, ZO_GetPlatformTemplate(ARROW_REGENERATION_TEMPLATE)) arrow.animation.key = key arrow.animation.bar = bar arrow:SetHidden(false) arrow:SetParent(bar) return arrow end local OFFSET_Y = 1 local END_X_PADDING = 3 local ARROW_TEXTURE_WIDTH = 16 function ZO_UnitVisualizer_ArrowRegenerationModule:PlayLeftArrow(bar, forward, widthModifier, takeControl) local width = bar:GetWidth() * widthModifier local offsetX = width - ARROW_TEXTURE_WIDTH local arrow = self:AcquireArrow(bar) arrow:SetAnchor(LEFT, bar, LEFT, offsetX, OFFSET_Y) arrow.animation:GetFirstAnimation():SetTranslateDeltas(-offsetX - END_X_PADDING, 0) if forward then arrow:SetTextureCoords(0, 1, 0, 1) arrow.animation:PlayFromStart() else arrow:SetTextureCoords(1, 0, 0, 1) arrow.animation:PlayFromEnd() end if takeControl then self:GetOwner():NotifyTakingControlOf(bar) arrow.animation.hasControlOfBar = true end end function ZO_UnitVisualizer_ArrowRegenerationModule:PlayRightArrow(bar, forward, widthModifier, takeControl) local width = bar:GetWidth() * widthModifier local offsetX = width - ARROW_TEXTURE_WIDTH local arrow = self:AcquireArrow(bar) arrow:SetAnchor(RIGHT, bar, RIGHT, -offsetX, OFFSET_Y) arrow.animation:GetFirstAnimation():SetTranslateDeltas(offsetX - END_X_PADDING, 0) if forward then arrow:SetTextureCoords(1, 0, 0, 1) arrow.animation:PlayFromStart() else arrow:SetTextureCoords(0, 1, 0, 1) arrow.animation:PlayFromEnd() end if takeControl then self:GetOwner():NotifyTakingControlOf(bar) arrow.animation.hasControlOfBar = true end end function ZO_UnitVisualizer_ArrowRegenerationModule:PlayArrow(bar, stat, value, takeControl) local forward = value > 0 if stat == STAT_HEALTH_REGEN_COMBAT then self:PlayLeftArrow(bar, forward, .5, takeControl) self:PlayRightArrow(bar, forward, .5, takeControl) else if stat == STAT_MAGICKA_REGEN_COMBAT then self:PlayLeftArrow(bar, forward, 1.0, takeControl) elseif stat == STAT_STAMINA_REGEN_COMBAT then self:PlayRightArrow(bar, forward, 1.0, takeControl) end end end function ZO_UnitVisualizer_ArrowRegenerationModule:ApplyPlatformStyle() local activeArrows = self.arrowPool:GetActiveObjects() for _, control in pairs(activeArrows) do ApplyTemplateToControl(control, ZO_GetPlatformTemplate(ARROW_REGENERATION_TEMPLATE)) end end