INFAMY_METER_WIDTH = 256 INFAMY_METER_HEIGHT = 128 INFAMY_METER_KEYBOARD_BAR_OFFSET_X = 14 INFAMY_METER_KEYBOARD_BAR_OFFSET_Y = 15 INFAMY_METER_GAMEPAD_BAR_OFFSET = 10 local INFAMY_METER_UPDATE_DELAY_SECONDS = 1 -- Forces the bar to be at least 3% full, in order to make it visible even at one or two bounty local MIN_BAR_PERCENTAGE = 0.03 local UPDATE_TYPE_TICK = 0 local UPDATE_TYPE_EVENT = 1 local INFAMY_METER_SLOW_FADE_TIME = 1400 -- in milliseconds local INFAMY_METER_SLOW_FADE_DELAY = 600 -- in milliseconds local INFAMY_METER_FADE_TIME = 200 -- in milliseconds local GREY_DAGGER_ICON = "EsoUI/Art/HUD/infamy_dagger-grey.dds" local RED_DAGGER_ICON = "EsoUI/Art/HUD/infamy_dagger-red.dds" local DAGGER_ICON_CUTOUT = "EsoUI/Art/HUD/infamy_dagger-cutout.dds" local RED_EYE_ICON = "EsoUI/Art/HUD/trespassing_eye-red.dds" local EYE_ICON_CUTOUT = "EsoUI/Art/HUD/trespassing_eye-cutout.dds" local ZO_HUDInfamyMeter = ZO_Object:Subclass() function ZO_HUDInfamyMeter:New(...) local object = ZO_Object.New(self) object:Initialize(...) return object end function ZO_HUDInfamyMeter:UpdateInfamyMeterState(infamy, bounty, isKOS, isTrespassing) self.infamyMeterState.infamy = infamy or GetInfamy() self.infamyMeterState.bounty = bounty or GetBounty() if isKOS ~= nil then self.infamyMeterState.isKOS = isKOS else self.infamyMeterState.isKOS = IsKillOnSight() end if isTrespassing ~= nil then self.infamyMeterState.isTrespassing = isTrespassing else self.infamyMeterState.isTrespassing = IsTrespassing() end end function ZO_HUDInfamyMeter:GetOldInfamyMeterState() return self.infamyMeterState.infamy, self.infamyMeterState.bounty, self.infamyMeterState.isKOS, self.infamyMeterState.isTrespassing end function ZO_HUDInfamyMeter:Initialize(control) -- Initialize state self.nextUpdateTime = 0 self.hiddenExternalRequest = false self.meterTotal = GetInfamyMeterSize() self.infamyMeterState = {} self:UpdateInfamyMeterState(0, 0, false, false) self.isInGamepadMode = IsInGamepadPreferredMode() self.currencyOptions = { showTooltips = true, customTooltip = SI_STATS_BOUNTY_LABEL, font = self.isInGamepadMode and "ZoFontGamepadHeaderDataValue" or "ZoFontGameLargeBold", overrideTexture = self.isInGamepadMode and ZO_Currency_GetGamepadCurrencyIcon(CURT_MONEY) or nil, iconSide = RIGHT, isGamepad = self.isInGamepadMode } -- Set up controls ApplyTemplateToControl(control, self.isInGamepadMode and "ZO_HUDInfamyMeter_GamepadTemplate" or "ZO_HUDInfamyMeter_KeyboardTemplate") self.control = control self.meterFrame = control:GetNamedChild("Frame") self.infamyBar = control:GetNamedChild("InfamyBar") self.bountyBar = control:GetNamedChild("BountyBar") self.centerIconAnimatingTexture = control:GetNamedChild("CenterIconAnimatingTexture") self.centerIconPersistentTexture = control:GetNamedChild("CenterIconPersistentTexture") self.bountyLabel = control:GetNamedChild("BountyDisplay") -- Set up fade in/out animations self.fadeAnim = ZO_AlphaAnimation:New(control) self.fadeAnim:SetMinMaxAlpha(0.0, 1.0) -- Initialize bar states and animations self.infamyBar.easeAnimation = ANIMATION_MANAGER:CreateTimelineFromVirtual("ZO_HUDInfamyMeterEasing") self.infamyBar.startPercent = 0 self.infamyBar.endPercent = self.infamyMeterState.infamy / self.meterTotal self.bountyBar.easeAnimation = ANIMATION_MANAGER:CreateTimelineFromVirtual("ZO_HUDInfamyMeterEasing") self.bountyBar.startPercent = 0 self.bountyBar.endPercent = self.infamyMeterState.bounty / self.meterTotal -- Initialize Center Icon and its animations self.centerIconCutoutInAnimation = ANIMATION_MANAGER:CreateTimelineFromVirtual("ZO_HUDInfamyMeterCenterIconCutoutIn") self.centerIconCutoutInAnimation:GetAnimation(1):SetAnimatedControl(self.centerIconAnimatingTexture) self.centerIconCutoutInAnimation:GetAnimation(2):SetAnimatedControl(self.centerIconAnimatingTexture) self.centerIconCutoutInAnimation:GetAnimation(3):SetAnimatedControl(self.centerIconAnimatingTexture) self.centerIconCutoutInAnimation:GetAnimation(4):SetAnimatedControl(self.centerIconPersistentTexture) self.centerIconScaleOutAnimation = ANIMATION_MANAGER:CreateTimelineFromVirtual("ZO_HUDInfamyMeterCenterIconScaleOut") self.centerIconScaleOutAnimation:GetAnimation(1):SetAnimatedControl(self.centerIconAnimatingTexture) self.centerIconScaleOutAnimation:GetAnimation(2):SetAnimatedControl(self.centerIconAnimatingTexture) self.centerIconScaleOutAnimation:GetAnimation(3):SetAnimatedControl(self.centerIconPersistentTexture) -- Register for events control:RegisterForEvent(EVENT_JUSTICE_INFAMY_UPDATED, function() if self:ShouldProcessUpdateEvent() then self:OnInfamyUpdated(UPDATE_TYPE_EVENT) end end) control:RegisterForEvent(EVENT_LEVEL_UPDATE, function() if self:ShouldProcessUpdateEvent() then self:OnInfamyUpdated(UPDATE_TYPE_EVENT) end end) control:RegisterForEvent(EVENT_PLAYER_ACTIVATED, function() if IsInJusticeEnabledZone() then if self:ShouldProcessUpdateEvent() then self:OnInfamyUpdated(UPDATE_TYPE_EVENT) end else self.control:SetHidden(true) self.control:SetAlpha(0) end end) end function ZO_HUDInfamyMeter:ShouldProcessUpdateEvent() local infamy = GetInfamy() local isTrespassing = IsTrespassing() return IsInJusticeEnabledZone() and not self.hiddenExternalRequest and ((infamy ~= 0 and infamy ~= self.infamyMeterState.infamy) or isTrespassing ~= self.infamyMeterState.isTrespassing) end function ZO_HUDInfamyMeter:Update(time) if self.nextUpdateTime <= time and not self.hiddenExternalRequest and IsInJusticeEnabledZone() then self.nextUpdateTime = time + INFAMY_METER_UPDATE_DELAY_SECONDS self:OnInfamyUpdated(UPDATE_TYPE_TICK) end end function ZO_HUDInfamyMeter:OnInfamyUpdated(updateType) local oldInfamy, oldBounty, wasKOS, wasTrespassing = self:GetOldInfamyMeterState() self:UpdateInfamyMeterState() local gamepadModeSwitchUpdate = IsInGamepadPreferredMode() ~= self.isInGamepadMode if oldInfamy ~= self.infamyMeterState.infamy or updateType == UPDATE_TYPE_EVENT or gamepadModeSwitchUpdate then -- Update frame and bars if we're switching between PC and console mode if IsInGamepadPreferredMode() and not self.isInGamepadMode then self.currencyOptions.font = "ZoFontGamepadHeaderDataValue" self.currencyOptions.isGamepad = true ApplyTemplateToControl(self.control, "ZO_HUDInfamyMeter_GamepadTemplate") self.isInGamepadMode = true elseif not IsInGamepadPreferredMode() and self.isInGamepadMode then self.currencyOptions.font = "ZoFontGameLargeBold" self.currencyOptions.isGamepad = false self.currencyOptions.iconSize = nil ApplyTemplateToControl(self.control, "ZO_HUDInfamyMeter_KeyboardTemplate") self.isInGamepadMode = false end -- Hide or show meter if self.infamyMeterState.infamy == 0 then self.fadeAnim:FadeOut(INFAMY_METER_SLOW_FADE_DELAY, INFAMY_METER_SLOW_FADE_TIME, ZO_ALPHA_ANIMATION_OPTION_FORCE_ALPHA, function() self.control:SetHidden(true) end) else self.fadeAnim:FadeIn(0, INFAMY_METER_FADE_TIME, ZO_ALPHA_ANIMATION_OPTION_USE_CURRENT_ALPHA) end -- Update bars self:UpdateBar(self.infamyBar, self.infamyMeterState.infamy, updateType) self:UpdateBar(self.bountyBar, self.infamyMeterState.bounty, updateType) -- Update trespassing/KOS icon self:AnimateCenterIcon(wasKOS, wasTrespassing) -- Update label ZO_CurrencyControl_SetSimpleCurrency(self.bountyLabel, CURT_MONEY, GetFullBountyPayoffAmount(), self.currencyOptions, CURRENCY_SHOW_ALL, true) -- Fire center-screen announcement if we updated below a threshold local infamyLevel = GetInfamyLevel(self.infamyMeterState.infamy) local oldInfamyLevel = GetInfamyLevel(oldInfamy) local messageParams -- Fire CSA if self.infamyMeterState.isTrespassing ~= wasTrespassing then local sound, primaryMessage, secondaryMessage messageParams = CENTER_SCREEN_ANNOUNCE:CreateMessageParams(CSA_CATEGORY_LARGE_TEXT) if wasTrespassing then messageParams:SetCSAType(CENTER_SCREEN_ANNOUNCE_TYPE_JUSTICE_NO_LONGER_KOS) sound = SOUNDS.JUSTICE_NO_LONGER_KOS primaryMessage = zo_strformat(SI_JUSTICE_NO_LONGER_TRESPASSING_PRIMARY) secondaryMessage = zo_strformat(SI_JUSTICE_NO_LONGER_TRESPASSING_SECONDARY) if self.infamyMeterState.bounty > 0 then TriggerTutorial(TUTORIAL_TRIGGER_TRESPASS_SUBZONE_EXITED_WITH_BOUNTY) end else messageParams:SetCSAType(CENTER_SCREEN_ANNOUNCE_TYPE_JUSTICE_NOW_KOS) TriggerTutorial(TUTORIAL_TRIGGER_TRESPASS_SUBZONE_ENTERED) sound = SOUNDS.JUSTICE_NOW_KOS primaryMessage = zo_strformat(SI_JUSTICE_NOW_TRESPASSING_PRIMARY) secondaryMessage = zo_strformat(SI_JUSTICE_NOW_TRESPASSING_SECONDARY) end messageParams:SetText(primaryMessage, secondaryMessage) messageParams:SetSound(sound) elseif infamyLevel ~= oldInfamyLevel then local sound, primaryMessage, secondaryMessage, icon messageParams = CENTER_SCREEN_ANNOUNCE:CreateMessageParams(CSA_CATEGORY_LARGE_TEXT) if oldInfamyLevel == INFAMY_THRESHOLD_FUGITIVE then messageParams:SetCSAType(CENTER_SCREEN_ANNOUNCE_TYPE_JUSTICE_NO_LONGER_KOS) sound = SOUNDS.JUSTICE_NO_LONGER_KOS primaryMessage = zo_strformat(SI_JUSTICE_INFAMY_LEVEL_CHANGED, GetString("SI_INFAMYTHRESHOLDSTYPE", infamyLevel)) secondaryMessage = zo_strformat(SI_JUSTICE_NO_LONGER_KOS) icon = "EsoUI/Art/Stats/infamy_KOS_icon-Notification.dds" elseif infamyLevel == INFAMY_THRESHOLD_FUGITIVE then messageParams:SetCSAType(CENTER_SCREEN_ANNOUNCE_TYPE_JUSTICE_NOW_KOS) TriggerTutorial(TUTORIAL_TRIGGER_FUGITIVE_REACHED) sound = SOUNDS.JUSTICE_NOW_KOS primaryMessage = zo_strformat(SI_JUSTICE_NOW_FUGITIVE) secondaryMessage = zo_strformat(SI_JUSTICE_NOW_KOS) icon = "EsoUI/Art/Stats/infamy_KOS_icon-Notification.dds" else messageParams:SetCSAType(CENTER_SCREEN_ANNOUNCE_TYPE_JUSTICE_INFAMY_CHANGED) if infamyLevel == INFAMY_THRESHOLD_DISREPUTABLE then TriggerTutorial(TUTORIAL_TRIGGER_DISREPUTABLE_REACHED) elseif infamyLevel == INFAMY_THRESHOLD_NOTORIOUS then TriggerTutorial(TUTORIAL_TRIGGER_NOTORIOUS_REACHED) end primaryMessage = zo_strformat(SI_JUSTICE_INFAMY_LEVEL_CHANGED, GetString("SI_INFAMYTHRESHOLDSTYPE", infamyLevel)) sound = SOUNDS.JUSTICE_STATE_CHANGED end messageParams:SetText(primaryMessage, secondaryMessage) messageParams:SetIconData(icon) messageParams:SetSound(sound) messageParams:MarkSuppressIconFrame() end if messageParams then CENTER_SCREEN_ANNOUNCE:AddMessageWithParams(messageParams) end end end function ZO_HUDInfamyMeter:AnimateCenterIcon(wasKOS, wasTrespassing) if self.infamyMeterState.isTrespassing then if not wasTrespassing then self.centerIconAnimatingTexture:SetTexture(EYE_ICON_CUTOUT) self.centerIconPersistentTexture:SetTexture(RED_EYE_ICON) self.centerIconPersistentTexture:SetAlpha(0) self.centerIconCutoutInAnimation:PlayFromStart() end elseif self.infamyMeterState.isKOS then if wasTrespassing then self.centerIconAnimatingTexture:SetTexture(RED_EYE_ICON) self.centerIconPersistentTexture:SetTexture(RED_DAGGER_ICON) self.centerIconScaleOutAnimation:PlayFromStart() elseif not wasKOS then self.centerIconAnimatingTexture:SetTexture(DAGGER_ICON_CUTOUT) self.centerIconPersistentTexture:SetTexture(RED_DAGGER_ICON) self.centerIconPersistentTexture:SetAlpha(0) self.centerIconCutoutInAnimation:PlayFromStart() end else if wasTrespassing or wasKOS then self.centerIconAnimatingTexture:SetTexture(self.centerIconPersistentTexture:GetTextureFileName()) self.centerIconPersistentTexture:SetTexture(GREY_DAGGER_ICON) self.centerIconScaleOutAnimation:PlayFromStart() end end end function ZO_HUDInfamyMeter:UpdateBar(bar, newValue, updateType) if not bar.easeAnimation:IsPlaying() or updateType == UPDATE_TYPE_EVENT then -- Update Values bar.startPercent = bar.endPercent bar.endPercent = newValue / self.meterTotal -- Manually set bar to its start percentage -- (we do this in case the bar has become out-of-date since it was last animated, for example by being hidden or paused) self:SetBarValue(bar, bar.startPercent) -- Start the animation bar.easeAnimation:PlayFromStart() end end function ZO_HUDInfamyMeter:AnimateMeter(progress) local infamyFillPercentage = zo_min((progress * (self.infamyBar.endPercent - self.infamyBar.startPercent)) + self.infamyBar.startPercent, 1) local bountyFillPercentage = zo_min((progress * (self.bountyBar.endPercent - self.bountyBar.startPercent)) + self.bountyBar.startPercent, 1) local infamyMinPercentage = self.infamyMeterState.infamy ~= 0 and MIN_BAR_PERCENTAGE or 0 local bountyMinPercentage = self.infamyMeterState.bounty ~= 0 and MIN_BAR_PERCENTAGE or 0 self:SetBarValue(self.infamyBar, zo_max(infamyFillPercentage, infamyMinPercentage)) self:SetBarValue(self.bountyBar, zo_max(bountyFillPercentage, bountyMinPercentage)) end function ZO_HUDInfamyMeter:SetBarValue(bar, percentFilled) local NO_LEADING_EDGE = false bar:StartFixedCooldown(percentFilled, CD_TYPE_RADIAL, CD_TIME_TYPE_TIME_REMAINING, NO_LEADING_EDGE) -- CD_TIME_TYPE_TIME_REMAINING causes clockwise scroll end function ZO_HUDInfamyMeter:RequestHidden(hidden) if hidden ~= self.hiddenExternalRequest then if hidden then self.fadeAnim:FadeOut(0, INFAMY_METER_FADE_TIME, ZO_ALPHA_ANIMATION_OPTION_USE_CURRENT_ALPHA, function() self.control:SetHidden(true) end, ZO_ALPHA_ANIMATION_OPTION_USE_CURRENT_SHOWN) elseif IsInJusticeEnabledZone() and (GetInfamy() ~= 0 or self.infamyMeterState.infamy ~= 0) then self.fadeAnim:FadeIn(0, INFAMY_METER_FADE_TIME, ZO_ALPHA_ANIMATION_OPTION_USE_CURRENT_ALPHA) end self.hiddenExternalRequest = hidden end end function ZO_HUDInfamyMeter_Initialize(control) HUD_INFAMY_METER = ZO_HUDInfamyMeter:New(control) end function ZO_HUDInfamyMeter_Update(time) HUD_INFAMY_METER:Update(time) end function ZO_HUDInfamyMeter_AnimateMeter(progress) if HUD_INFAMY_METER then HUD_INFAMY_METER:AnimateMeter(progress) end end