local TEXTURE_WIDTH = 1680 local TEXTURE_HEIGHT = 1050 local TEXTURE_ASPECT_RATIO = TEXTURE_WIDTH / TEXTURE_HEIGHT local LOADING_TIP_PERCENTAGE = 0.8 local TARGET_FRAMERATE = 60 local MAX_FRAMES_PER_UPDATE = 5 local MAX_ROTATION = math.pi * 2 local ROTATION_PER_FRAME = -MAX_ROTATION * 0.01 local MINIMUM_TIME_TO_HOLD_LOADING_TIP_MS = 15000 -- Difficulty icons ------------------------------ local CHALLENGE_DIFFICULTY_ICONS = { [OVERLAND_DIFFICULTY_TYPE_BASEGAME] = "EsoUI/Art/ChallengeDifficulty/Gamepad/gp_challengeDifficulty_basegame.dds", [OVERLAND_DIFFICULTY_TYPE_JOURNEYMAN] = "EsoUI/Art/ChallengeDifficulty/Gamepad/gp_challengeDifficulty_journeyman.dds", [OVERLAND_DIFFICULTY_TYPE_ADVENTURER] = "EsoUI/Art/ChallengeDifficulty/Gamepad/gp_challengeDifficulty_adventurer.dds", [OVERLAND_DIFFICULTY_TYPE_VETERAN] = "EsoUI/Art/ChallengeDifficulty/Gamepad/gp_challengeDifficulty_veteran.dds", } -- Instance type icons ------------------------------ local ZONE_DISPLAY_TYPE_ICONS = { [ZONE_DISPLAY_TYPE_SOLO] = "EsoUI/Art/loadingTips/loadingTip_soloInstance.dds", [ZONE_DISPLAY_TYPE_DUNGEON] = "EsoUI/Art/loadingTips/loadingTip_groupInstance.dds", [ZONE_DISPLAY_TYPE_RAID] = "EsoUI/Art/loadingTips/loadingTip_raidDungeon.dds", [ZONE_DISPLAY_TYPE_GROUP_DELVE] = "EsoUI/Art/loadingTips/loadingTip_groupDelve.dds", [ZONE_DISPLAY_TYPE_GROUP_AREA] = "EsoUI/Art/Icons/mapKey/mapKey_groupArea.dds", [ZONE_DISPLAY_TYPE_PUBLIC_DUNGEON] = "EsoUI/Art/loadingTips/loadingTip_dungeon.dds", [ZONE_DISPLAY_TYPE_DELVE] = "EsoUI/Art/loadingTips/loadingTip_delve.dds", [ZONE_DISPLAY_TYPE_HOUSING] = "EsoUI/Art/Icons/mapKey/mapKey_housing.dds", [ZONE_DISPLAY_TYPE_ZONE_STORY] = "EsoUI/Art/Icons/mapKey/mapKey_zoneStory.dds", [ZONE_DISPLAY_TYPE_ENDLESS_DUNGEON] = "EsoUI/Art/Icons/mapKey/mapKey_endlessDungeon.dds", [ZONE_DISPLAY_TYPE_ADVENTURE_ZONE] = "EsoUI/Art/Icons/mapKey/mapKey_adventureZone.dds", } function ZO_GetZoneDisplayTypeIcon(zoneDisplayType) return ZONE_DISPLAY_TYPE_ICONS[zoneDisplayType] end --Local implementation of object pool for key edge file ------------------------------ local g_keyEdgefileFreeList = {} local g_keyEdgefileActiveList = {} local function GetOrCreateKeyEdgefile(control) local keyEdgeFile = next(g_keyEdgefileFreeList) or GetWindowManager():CreateControlFromVirtual("", control, "ZO_LoadingScreen_KeyBackdrop") --If we already have something in the pool, we need to set the parent. keyEdgeFile:SetParent(control) g_keyEdgefileFreeList[keyEdgeFile] = nil g_keyEdgefileActiveList[keyEdgeFile] = true return keyEdgeFile end local function ReleaseAllKeyEdgeFiles() for keyEdgeFile in pairs(g_keyEdgefileActiveList) do g_keyEdgefileActiveList[keyEdgeFile] = nil g_keyEdgefileFreeList[keyEdgeFile] = true keyEdgeFile:SetHidden(true) end end --Loading Screen Object -- Note: Must provide an InitializeAnimations function in derived classes ------------------------------ LoadingScreen_Base = {} function LoadingScreen_Base:Log(text) local gamepadMode = IsInGamepadPreferredMode() if gamepadMode and self == GamepadLoadingScreen or not gamepadMode and self == LoadingScreen then WriteToInterfaceLog(text) end end function LoadingScreen_Base:Initialize() self.seenZones = {} self.currentRotation = 0 self.lastUpdate = GetFrameTimeMilliseconds() self.animations = nil local zoneInfoContainer = self:GetNamedChild("ZoneInfoContainer") self.bgTexture = self:GetNamedChild("Bg") self.art = self:GetNamedChild("Art") self.zoneName = zoneInfoContainer:GetNamedChild("ZoneName") self.zoneDescription = self:GetNamedChild("ZoneDescription") self.descriptionBg = self:GetNamedChild("DescriptionBg") self.instanceTypeIcon = zoneInfoContainer:GetNamedChild("InstanceTypeIcon") self.instanceType = zoneInfoContainer:GetNamedChild("InstanceType") self.difficultyLabel = self:GetNamedChild("Difficulty") self.spinner = self:GetNamedChild("Spinner") EVENT_MANAGER:RegisterForEvent(self:GetSystemName(), EVENT_AREA_LOAD_STARTED, function(...) self:OnAreaLoadStarted(...) end) EVENT_MANAGER:RegisterForEvent(self:GetSystemName(), EVENT_SCREEN_RESIZED, function(...) self:SizeLoadingTexture(...) end) EVENT_MANAGER:RegisterForEvent(self:GetSystemName(), EVENT_PREPARE_FOR_JUMP, function(...) self:OnPrepareForJump(...) end) EVENT_MANAGER:RegisterForEvent(self:GetSystemName(), EVENT_JUMP_FAILED, function(...) self:OnJumpFailed(...) end) EVENT_MANAGER:RegisterForEvent(self:GetSystemName(), EVENT_DISCONNECTED_FROM_SERVER, function(...) self:OnDisconnectedFromServer(...) end) EVENT_MANAGER:RegisterForEvent(self:GetSystemName(), EVENT_RESUME_FROM_SUSPEND, function(...) self:OnResumeFromSuspend(...) end) EVENT_MANAGER:RegisterForEvent(self:GetSystemName(), EVENT_OVERLAND_DIFFICULTY_CHANGED, function(...) self:UpdateOverlandDifficultyLabel(...) end) local function OnSubsystemLoadStateChanged(eventCode, system, isComplete) if not isComplete then return end self:Log(string.format("Load Screen - %s Complete", GetLoadingSystemName(system))) if GetNumTotalSubsystemsToLoad() == GetNumLoadedSubsystems() then if not IsWaitingForTeleport() then --If the last systems we were waiting on all finish in the same frame we could call Hide several times self:Log("Load Screen - Systems Loaded And Not Waiting For Teleport") self:Hide() else self:Log("Load Screen - Systems Loaded But Waiting For Teleport") end else local remainingText = "Load Screen - Waiting On: " for i = LOADING_SYSTEM_ITERATION_BEGIN, LOADING_SYSTEM_ITERATION_END do if not IsSystemLoaded(i) then remainingText = remainingText .. GetLoadingSystemName(i) .. ", " end end self:Log(remainingText) end end EVENT_MANAGER:RegisterForEvent(self:GetSystemName(), EVENT_SUBSYSTEM_LOAD_STATE_CHANGED, OnSubsystemLoadStateChanged) self:SizeLoadingTexture() self:InitializeAnimations() end function LoadingScreen_Base:SizeLoadingTexture() local screenWidth, screenHeight = GuiRoot:GetDimensions() local screenAspectRatio = screenWidth / screenHeight if TEXTURE_ASPECT_RATIO > screenAspectRatio then local scale = screenHeight / TEXTURE_HEIGHT self.art:SetDimensions(TEXTURE_WIDTH * scale, screenHeight) else local scale = screenWidth / TEXTURE_WIDTH self.art:SetDimensions(screenWidth, TEXTURE_HEIGHT * scale) end end function LoadingScreen_Base:OnAreaLoadStarted(evt, worldId, instanceNum, zoneName, zoneDescription, loadingTexture, zoneDisplayType) self:Log(string.format("Load Screen - OnAreaLoadStarted - (%d) %s", worldId, zoneName == "" and "Unknown Zone" or zoneName)) self:UpdateBattlegroundId(zoneDisplayType) self:UpdateOverlandDifficultyLabel() self:Show(zoneName, zoneDescription, loadingTexture, zoneDisplayType) end function LoadingScreen_Base:OnPrepareForJump(evt, zoneName, zoneDescription, loadingTexture, zoneDisplayType) self:Log(string.format("Load Screen - OnPrepareForJump - %s", zoneName == "" and "Unknown Zone" or zoneName)) self:UpdateBattlegroundId(zoneDisplayType) self:UpdateOverlandDifficultyLabel() self:Show(zoneName, zoneDescription, loadingTexture, zoneDisplayType) end function LoadingScreen_Base:OnJumpFailed() self:Log("Load Screen - OnJumpFailed") self:Hide() end function LoadingScreen_Base:OnDisconnectedFromServer() self:Log("Load Screen - OnDisconnectedFromServer") self:Hide() --Hack to run to code that would execute on the hide animation complete immediately self.animations.control:SetHidden(true) SetGuiHidden("app", true) RemoveActionLayerByNameApp("LoadingScreen") end function LoadingScreen_Base:OnResumeFromSuspend(evt) self:Log("Load Screen - OnResumeFromSuspend") self:Show("", "", "", ZONE_DISPLAY_TYPE_NONE) end local BATTLEGROUND_TEAM_TEXTURES = { [BATTLEGROUND_TEAM_FIRE_DRAKES] = "EsoUI/Art/Battlegrounds/battlegrounds_teamIcon_orange.dds", [BATTLEGROUND_TEAM_STORM_LORDS] = "EsoUI/Art/Battlegrounds/battlegrounds_teamIcon_purple.dds", [BATTLEGROUND_TEAM_PIT_DAEMONS] = "EsoUI/Art/Battlegrounds/battlegrounds_teamIcon_green.dds", } local GAMEPAD_BATTLEGROUND_TEAM_TEXTURES = { [BATTLEGROUND_TEAM_FIRE_DRAKES] = "EsoUI/Art/Battlegrounds/Gamepad/gp_battlegrounds_teamIcon_orange.dds", [BATTLEGROUND_TEAM_STORM_LORDS] = "EsoUI/Art/Battlegrounds/Gamepad/gp_battlegrounds_teamIcon_purple.dds", [BATTLEGROUND_TEAM_PIT_DAEMONS] = "EsoUI/Art/Battlegrounds/Gamepad/gp_battlegrounds_teamIcon_green.dds", } internalassert(BATTLEGROUND_TEAM_MAX_VALUE == 3, "Update load screen battleground team textures") function LoadingScreen_Base:Show(zoneName, zoneDescription, loadingTexture, zoneDisplayType, isOverlandDifficultyDisabled) if self:IsHidden() and self:IsPreferredScreen() then self:Log("Load Screen - Show") self.lastUpdate = GetFrameTimeMilliseconds() --Some of the logic in this file can cause use to briefly hide the load screen, so we set this to make sure we dont clear narration due to a hide while this function is running self.dontClearNarration = true local wasAppGuiHidden = GetGuiHidden("app") --If the app gui was previously hidden, mark the narration dirty self.isNarrationDirty = wasAppGuiHidden --First configure the visuals self:SizeLoadingTexture() local isDefaultTexture = "" == loadingTexture if isDefaultTexture then if not self.randomLoadingTexture then self.randomLoadingTexture = GetRandomLoadingScreenTexture() end loadingTexture = self.randomLoadingTexture end self.art:SetTexture(loadingTexture) self.zoneName:SetHidden(isDefaultTexture) self.zoneDescription:SetHidden(isDefaultTexture) if self.descriptionBg then self.descriptionBg:SetHidden(isDefaultTexture) end local showZoneDisplayType = zoneDisplayType ~= ZONE_DISPLAY_TYPE_NONE and (zoneDisplayType ~= ZONE_DISPLAY_TYPE_BATTLEGROUND or self.battlegroundId ~= 0) self.instanceTypeIcon:SetHidden(not showZoneDisplayType) self.instanceType:SetHidden(not showZoneDisplayType) self:UpdateOverlandDifficultyLabel() if not isDefaultTexture then local shouldHideName = false if self.battlegroundId ~= 0 then local gameType = GetBattlegroundGameType(self.battlegroundId) local gameTypeString = GetString("SI_BATTLEGROUNDGAMETYPE", gameType) local battlegroundDescription = GetBattlegroundDescription(self.battlegroundId) local zoneNameText = LocalizeString("<>", gameTypeString) --If the zone name text changed, mark narration dirty if zoneNameText ~= self.zoneNameText then self.zoneNameText = zoneNameText self.isNarrationDirty = true end self.zoneName:SetText(self.zoneNameText) self:SetZoneDescription(LocalizeString("<<1>>", battlegroundDescription)) local activityTeam = GetLatestActivityTeamId() if activityTeam ~= BATTLEGROUND_TEAM_INVALID then local r, g, b, a = GetInterfaceColor(INTERFACE_COLOR_TYPE_BATTLEGROUND_TEAM, activityTeam) local battlegroundTeamName = ZO_ColorizeString(r, g, b, GetString("SI_BATTLEGROUNDTEAM", activityTeam)) local teamIcon if IsInGamepadPreferredMode() then teamIcon = GAMEPAD_BATTLEGROUND_TEAM_TEXTURES[activityTeam] else teamIcon = BATTLEGROUND_TEAM_TEXTURES[activityTeam] end local instanceTypeText = LocalizeString("<<1>>", battlegroundTeamName) --If the instance type text changed, mark narration dirty if instanceTypeText ~= self.instanceTypeText then self.instanceTypeText = instanceTypeText self.isNarrationDirty = true end self.instanceType:SetText(self.instanceTypeText) self.instanceTypeIcon:SetTexture(teamIcon) end else if showZoneDisplayType then self.instanceTypeIcon:SetTexture(ZO_GetZoneDisplayTypeIcon(zoneDisplayType)) local instanceTypeText = GetString("SI_ZONEDISPLAYTYPE", zoneDisplayType) --If the instance type text changed (changed type or new language chosen), mark narration dirty if instanceTypeText ~= self.instanceTypeText then self.instanceTypeText = instanceTypeText self.isNarrationDirty = true end self.instanceType:SetText(self.instanceTypeText) -- Hacky stop gap solution to the Zone and the feature having the same name -- Design asked us to suppress the zone name and let the zone display type do the work shouldHideName = zoneDisplayType == ZONE_DISPLAY_TYPE_ENDLESS_DUNGEON end -- Hacky stop gap solution to the Zone and the feature having the same name -- Design asked us to suppress the zone name and let the zone display type do the work if not shouldHideName then local zoneNameText = LocalizeString("<>", zoneName) --If the zone name text changed, mark narration dirty if zoneNameText ~= self.zoneNameText then self.zoneNameText = zoneNameText self.isNarrationDirty = true end self.zoneName:SetText(self.zoneNameText) end --Only do this random roll once when the load screen is first brought up, not everytime the info changes if wasAppGuiHidden then self.preferTipOverZoneDescriptionIfZoneHasBeenSeen = math.random() <= LOADING_TIP_PERCENTAGE end --Only update this on a new zone if self.lastZoneName ~= zoneName then local showTipInsteadOfZoneDescription if self.seenZones[zoneName] then showTipInsteadOfZoneDescription = self.preferTipOverZoneDescriptionIfZoneHasBeenSeen else showTipInsteadOfZoneDescription = false end self.lastZoneName = zoneName self.seenZones[zoneName] = true if showTipInsteadOfZoneDescription then if not self.tip then self.tip = GetLoadingTip() end if self.tip ~= "" then self:SetZoneDescription(self.tip) else self:SetZoneDescription(LocalizeString("<<1>>", zoneDescription)) end else self:SetZoneDescription(LocalizeString("<<1>>", zoneDescription)) end end end if shouldHideName then self.zoneNameText = "" self.zoneName:SetHidden(true) if showZoneDisplayType then self.instanceTypeIcon:SetAnchor(LEFT) end else self.instanceTypeIcon:SetAnchor(LEFT, self.zoneName, RIGHT, 60) end end --Then if we are presently hiding the UI then stop that and reset it to the start. This will trigger the actions on the --animation finishing causing it to hide the load screen and remove the keybinds so this needs to be done before we show --the load screen and add the keybinds if self.animations:IsPlaying() then self.animations:PlayInstantlyToStart() end --Here we begin showing the load screen. --First show the whole GUI, this needs to be done immediately to show anything SetGuiHidden("app", false) --also show the loadscreen top level self:SetHidden(false) --also show the solid black texture that blocks out the world self.bgTexture:SetHidden(false) --also add the keybinds if not IsActionLayerActiveByNameApp("LoadingScreen") then PushActionLayerByNameApp("LoadingScreen") end --also fade in the spinner self.spinnerFadeAnimation:PlayForward() --For the main animation that brings in the art (as well as some other things) we are waiting until the texture is loaded in memory. This --is checked continuously in update self.loadScreenTextureLoaded = false --Now that we are done showing the load screen, allow clearing the narration again self.dontClearNarration = false end end function LoadingScreen_Base:Hide() self:Log("Load Screen - Hide") --if it is hidden or already hiding then return if self:IsHidden() or (self.animations:IsPlaying() and self.animations:IsPlayingBackward()) or (self.spinnerFadeAnimation:IsPlaying() and self.spinnerFadeAnimation:IsPlayingBackward()) then return end self:Log("Load Screen - Hide - Wasn't Already Hiding") --Hide the black BG on the start of hiding so the load screen fades with the world self.bgTexture:SetHidden(true) self.animations:PlayBackward() self.spinnerFadeAnimation:PlayBackward() self:ClearBattlegroundId() --State for controlling the description or loading tip decision self.preferTipOverZoneDescriptionIfZoneHasBeenSeen = nil self.tip = nil self.lastZoneName = nil --State for controlling the load screen texture when it isn't set by a def self.randomLoadingTexture = nil end function LoadingScreen_Base:Update() -- hold on other animations till background art is fully loaded if not self.loadScreenTextureLoaded and self.art:IsTextureLoaded() then self.loadScreenTextureLoaded = true self.animations:PlayForward() end if self.lastUpdate then local now = GetFrameTimeMilliseconds() local delta = now - self.lastUpdate local numFramesToIncrease = delta / TARGET_FRAMERATE if numFramesToIncrease == 0 then return elseif numFramesToIncrease > MAX_FRAMES_PER_UPDATE then numFramesToIncrease = MAX_FRAMES_PER_UPDATE end self.lastUpdate = now self.currentRotation = (self.currentRotation + numFramesToIncrease * ROTATION_PER_FRAME) % MAX_ROTATION self.spinner:SetTextureRotation(self.currentRotation) end end function LoadingScreen_Base:OnZoneDescriptionNewUserAreaCreated(control, areaData, areaText, left, right, top, bottom) if areaData == "key" then local keyEdgeFile = GetOrCreateKeyEdgefile(control) keyEdgeFile:SetAnchor(TOPLEFT, control, TOPLEFT, left + 2, top - 1) keyEdgeFile:SetAnchor(BOTTOMRIGHT, control, TOPLEFT, right - 2, bottom + 1) keyEdgeFile:SetHidden(false) end end function LoadingScreen_Base:SetZoneDescription(tip) --If the zone description text changed, mark narration dirty if tip ~= self.zoneDescriptionText then self.zoneDescriptionText = tip self.isNarrationDirty = true end self.zoneDescription:SetText(tip) ReleaseAllKeyEdgeFiles() end function LoadingScreen_Base:UpdateOverlandDifficultyLabel() local difficulty = GetSelectedOverlandDifficulty() if difficulty then local difficultyName = GetString("SI_OVERLANDDIFFICULTYTYPE", difficulty) local difficultyIcon = string.format("|t%s:%s:%s|t", "100%", "100%", CHALLENGE_DIFFICULTY_ICONS[difficulty]) local difficultyString = string.format("%s %s", difficultyIcon, difficultyName) self.difficultyLabel:SetText(difficultyString) self.difficultyLabel:SetHidden(false) else self.difficultyLabel:SetHidden(true) end end function LoadingScreen_Base:UpdateBattlegroundId(zoneDisplayType) if zoneDisplayType == ZONE_DISPLAY_TYPE_BATTLEGROUND then self.battlegroundId = GetActivityBattlegroundId(GetCurrentLFGActivityId()) else self:ClearBattlegroundId() end end function LoadingScreen_Base:ClearBattlegroundId() self.battlegroundId = 0 end function LoadingScreen_Base:LoadingCompleteAnimation_OnStop(timeline) --We finally get rid of the load screen entirely when it is animated out self:Log("Load Screen - Show/Hide - Animation Complete") if timeline:IsPlayingBackward() then self:Log("Load Screen - Hide - Animation Complete") timeline.control:SetHidden(true) SetGuiHidden("app", true) RemoveActionLayerByNameApp("LoadingScreen") end end