------------------------------------------------------------------- -- Main Table ------------------------------------------------------------------- local LoadingScreensInfo = { -- Vanilla [1] = { weight = 10, requiredEventFlag = "0" }, -- Limgrave: Third Church of Marika [2] = { weight = 10, requiredEventFlag = "0" }, -- Limgrave: View of the Erdtree [3] = { weight = 10, requiredEventFlag = "0" }, -- Stormveil: Throneroom Statue of Godfrey [4] = { weight = 10, requiredEventFlag = "0" }, -- Farum Azula: Maliketh Bossroom [5] = { weight = 10, requiredEventFlag = "0" }, -- Limgrave: Divine Tower of Limgrave [6] = { weight = 10, requiredEventFlag = "0" }, -- Limgrave: Nomadic Merchant [7] = { weight = 10, requiredEventFlag = "0" }, -- Raya Lucaria: Debate Parlor [8] = { weight = 10, requiredEventFlag = "0" }, -- Mountaintop of the Giants: Giant's Corpse [9] = { weight = 10, requiredEventFlag = "0" }, -- Roundtable Hold: The Round Table [10] = { weight = 10, requiredEventFlag = "0" }, -- Limgrave: Waypoint Ruins with a Stake of Marika [11] = { weight = 10, requiredEventFlag = "0" }, -- Limgrave: Blaidd [12] = { weight = 10, requiredEventFlag = "0" }, -- Mt. Gelmir: Abductor Virgins [13] = { weight = 10, requiredEventFlag = "0" }, -- Leyndell, Royal Capital: The Queens Bedchamber [14] = { weight = 10, requiredEventFlag = "0" }, -- Weeping Peninsula: Walking Mausoleum [15] = { weight = 10, requiredEventFlag = "0" }, -- Redmane Caste: Castalian Jerren [16] = { weight = 10, requiredEventFlag = "0" }, -- Liurnia: Wraithcaller Rider [17] = { weight = 10, requiredEventFlag = "0" }, -- Stormveil: Main Gate [18] = { weight = 10, requiredEventFlag = "0" }, -- Jarburg: Living Jars [19] = { weight = 10, requiredEventFlag = "0" }, -- Nokstella: Giant Skeleton [20] = { weight = 10, requiredEventFlag = "0" }, -- Volcano Manor: Lady Tanith [21] = { weight = 10, requiredEventFlag = "0" }, -- Limgrave: Caravan [22] = { weight = 10, requiredEventFlag = "0" }, -- Limgrave: Guardian Golem [23] = { weight = 10, requiredEventFlag = "0" }, -- Catacombs: Erdtree Roots --[24] = { weight = 10, requiredEventFlag = "82002" }, -- Abyssal Woods: Midra's Manse [25] = { weight = 10, requiredEventFlag = "82002" }, -- Belurat: Main Gate [26] = { weight = 10, requiredEventFlag = "82002" }, -- Cathedral of Manus Metyr: Emyr's Throne --[27] = { weight = 10, requiredEventFlag = "82002" }, -- Jagged Peak: Jagged Peak Drake [28] = { weight = 10, requiredEventFlag = "82002" }, -- Ruins of Rauh: Pond [29] = { weight = 10, requiredEventFlag = "82002" }, -- Black Keep: Burning Boats [30] = { weight = 10, requiredEventFlag = "82002" }, -- Gravesite Plains: View of the Scadutree [31] = { weight = 10, requiredEventFlag = "82002" }, -- Midra's Manse: Family Portrait [32] = { weight = 10, requiredEventFlag = "82002" }, -- Scadu Altus: Furnace Golem [33] = { weight = 10, requiredEventFlag = "82002" }, -- Cerulean Coast: Spirit Eel [34] = { weight = 10, requiredEventFlag = "82002" }, -- Gravesite Plains: Miquella's Cross -- Modded [35] = { weight = 11, requiredEventFlag = "76490" }, -- Bestial Sanctum: Bestial Undercroft - Unlocks when opening the Bestial Santum Grace [36] = { weight = 11, requiredEventFlag = "71233" }, -- Deeproot Depths: Bridge - Unlocks when opening the Deeproot Depths Grace [37] = { weight = 11, requiredEventFlag = "71235" }, -- Deeproot Depths: Knight - Unlocks when opening the Acrosst the Roots Grace [38] = { weight = 11, requiredEventFlag = "12030393" }, -- Deeproot Depths: Nox Shrine - Unlocks when defeating Aled, knight of Godwyn [39] = { weight = 11, requiredEventFlag = "17000801" }, -- Noxumbra: Great Hall - Unlocks when encountering Daegraf [40] = { weight = 11, requiredEventFlag = "73019" }, -- Santum of Eiglay: Walkway - Unlocks when opening the Santum of Eiglay Grace [41] = { weight = 11, requiredEventFlag = "30190161" }, -- Santum of Eiglay: Petting Golem - Unlocked after unlocking the nearby gate [42] = { weight = 11, requiredEventFlag = "30190181" }, -- Santum of Eiglar: Abductor Virgin - Unlocked after opening the bridge [43] = { weight = 11, requiredEventFlag = "0" }, -- Minor Erdtree: Erdtree Network Portal - Unlocked by default [44] = { weight = 11, requiredEventFlag = "9131" }, -- Forge of the Giants: Portal - Unlocked defeating the Fire Giant [45] = { weight = 11, requiredEventFlag = "10019800" }, -- Lake of Rot: Grand Cloister Statue - Unlocked after encoutering Scion of the Sealed God [46] = { weight = 11, requiredEventFlag = "16009800" }, -- Volcano Manor: Bossroom - Unlocked after encountering Rykard [47] = { weight = 11, requiredEventFlag = "0" }, -- Volcano Manor: Hanging Cages - Unlocked by default [48] = { weight = 11, requiredEventFlag = "12059850" }, -- Mohgwyn Palace: Bird Wall Mount - Unlocked after encountering Konrad [49] = { weight = 11, requiredEventFlag = "71251" }, -- Moghwyn Palace: Ruins Gate - Unlocked after activating the Palace Approach Ledge-Road site of grace [50] = { weight = 11, requiredEventFlag = "0" }, -- Mohgwyn Palace: Pots - Unlocked by default [51] = { weight = 11, requiredEventFlag = "12057790" }, -- Mohgwyn Palace: Abberant Campfire - Unlocked after obtaining the Crucifix of Eochaid [52] = { weight = 11, requiredEventFlag = "12059800" }, -- Mohgwyn Palace: Statue of Mogh - Unlocked after encountering Mogh [53] = { weight = 11, requiredEventFlag = "0" }, -- Sanctuary of Lost Knowledge: Halls - Unlocked by default [54] = { weight = 11, requiredEventFlag = "0" }, -- Sanctuary of Lost Knowledge: Statue - Unlocked by default [55] = { weight = 11, requiredEventFlag = "71227" }, -- Spiritshaper Grounds: Woods - Unlocked after unlocking the Aqueduct-Facing Cliffs Grace [56] = { weight = 11, requiredEventFlag = "12020439" }, -- Spiritshaper Grounds: Howling Wolf - Unlocked after triggering the first wolf event [57] = { weight = 11, requiredEventFlag = "76938" }, -- Finger Ruins of Dheo: Approach - Unlocked after unlocking the Finger Ruins of Dheo Grace [58] = { weight = 11, requiredEventFlag = "30209010" }, -- Vulgar Militia Stronghold: Spike Traps - Unlocked after lowering the final drawbridge [59] = { weight = 11, requiredEventFlag = "30209011" }, -- Vulgar Militia Stronghold: Camp - Unlocked after lowering the first drawbridge [60] = { weight = 11, requiredEventFlag = "71608" }, -- Volcano Manor: Lava Pool Bridge - Unlocked after unlocking the Manor Passage site of grace [61] = { weight = 11, requiredEventFlag = "76419" }, -- Redmane Castle: Bridge Gate - Unlocked after unlocking the Redmane Castle Plaza Grace [62] = { weight = 11, requiredEventFlag = "18009800" }, -- Fringe Folk: Staircase - Unlocked after meeting the boss of the area for the first time [63] = { weight = 11, requiredEventFlag = "73208" }, -- Sellia: Overlook - Unlocked after unlocking the Sellia Crystal Tunnel Grace [64] = { weight = 11, requiredEventFlag = "76214" }, -- Caria Manor: Approach - Unlocked after unlocking the Caria Manor Grace [65] = { weight = 11, requiredEventFlag = "318300" }, -- Raya Crystalia: Start - Unlocked after unlocking the Raya Crystalia Grace [66] = { weight = 11, requiredEventFlag = "40008808" }, -- Fog Rift Catacombs: Godskin Noble Bridge - Unlocked after defeating the Godskin Noble [67] = { weight = 11, requiredEventFlag = "40001414" }, -- Fog Rift Catacombs: Grand Hall - Unlocked after opening the door to the Grand Hall [68] = { weight = 11, requiredEventFlag = "18009850" }, -- Stranded Graveyard: Praying Guilty - Unlocked after meeting the boss of the area for the first time [69] = { weight = 11, requiredEventFlag = "71313" }, -- Farum Azula: Farum Ruins Overview - Unlocked after unlocking the Dragonlord's Annex Grace [70] = { weight = 11, requiredEventFlag = "71504" }, -- Miquella's Haligtree: Drainage Channel - Unlocked after unlocking the Haligtree Roots Grace [71] = { weight = 11, requiredEventFlag = "2049439850" }, -- Fort of Reprimand: Overview - Unlocked after meeting the boss of the area for the first time [72] = { weight = 11, requiredEventFlag = "76968" }, -- Sarrulat, Basin Town - Unlocked after unlocking the Sarrulat, Basin Town Grace [73] = { weight = 11, requiredEventFlag = "76158" }, -- Castle Morne Town - Unlocked after unlocking the Castle Morne Lift Grace [74] = { weight = 11, requiredEventFlag = "1043310003" }, -- Castle Morne Habibi Approach - Unlocked after Castle Morne Town Grace [75] = { weight = 11, requiredEventFlag = "31820800" }, -- Habibi Hall - Unlocked after defeating Habibi [76] = { weight = 11, requiredEventFlag = "1041320691" }, -- Turtle Sea - Unlocked after doing the turtle puzzle [77] = { weight = 11, requiredEventFlag = "15000754" }, -- HaligTree Sealed Gate - Unlocked after Defeating Rotten Sentry [78] = { weight = 11, requiredEventFlag = "1036450001" }, -- Carian Nexus - Unlocked after grace [79] = { weight = 11, requiredEventFlag = "1051569206" }, -- StarGazer Ruins - Unlocked after Jellyfish Puzzle [80] = { weight = 11, requiredEventFlag = "0" }, -- Roundtable MapMaker - Unlocked by default } ------------------------------------------------------------------- -- Code ------------------------------------------------------------------- local LoadingScreensPopulated = false local MENU_LOAD_ENTRY_COUNT = 34 local MENU_LOAD_INDEX_MAX = 33 local function IsValidLoadingScreenId(id) return type(id) == "number" and LoadingScreensInfo[id] ~= nil end local function SetMenuLoadEntryIfValid(menuIndex, loadingScreenId) if not IsValidLoadingScreenId(loadingScreenId) then Log("Skipping invalid loading screen id", loadingScreenId, "for menu index", menuIndex) return false end SetPointerValue(GAME_BASE.CS_NOW_LOADING_HELPER.MENU_LOAD_ENTRIES(menuIndex), loadingScreenId) return true end function LoadingScreenHandler() -- SetNextLoadingScreenToPrevPlusOne() if LoadingScreensPopulated then MonitorLoadingScreenEventFlags() return end LoadingScreensPopulated = true if GetPointerValue(GAME_BASE.CS_NOW_LOADING_HELPER.CURRENT_MENU_LOAD_INDEX) ~= 1 then LimitLoadingScreenAmountHandler() return end PopulateLoadingScreens() end function PopulateLoadingScreens() local currentLoadingScreenTable = GetCurrentLoadingScreenTable() -- Get the last 5 played loading screens -- First loading screen on startup is on index 1 -- In Lua, indices start at 1 instead of 0, meaning in this table we include index 2 local lastFiveOld = { [currentLoadingScreenTable[31]] = true, [currentLoadingScreenTable[32]] = true, [currentLoadingScreenTable[33]] = true, [currentLoadingScreenTable[1]] = true, [currentLoadingScreenTable[2]] = true } local availableIndices = {} for index, info in pairs(LoadingScreensInfo) do if info.weight > 0 and (info.requiredEventFlag == "0" or env(GetEventFlag, info.requiredEventFlag) == TRUE) then table.insert(availableIndices, index) end end if #availableIndices == 0 then act(10001, "[WARNING!] Not enough available loading screens to populate table. At least 1 need to be possible. Loading screens were not updated.") return end local used = {} for i = 0, 33 do -- Neccessary evil for making it so high weight loading screens dont get placed in index 0 or index 1 -- If not the weight based priority system has no use because the high weight items will be at an index that -- require a full rotation before they show up. local index = i + 2 if index > 33 then index = index - 34 end local candidates = {} for _, idx in ipairs(availableIndices) do if not used[idx] then -- Exclude already added loading screens if i > 4 or lastFiveOld[idx] == nil then -- For first 5, exclude lastFiveOld table.insert(candidates, idx) end end end -- If constraints are too strict, relax them in stages to avoid assigning invalid id 0. if #candidates == 0 then for _, idx in ipairs(availableIndices) do if not used[idx] then table.insert(candidates, idx) end end end if #candidates == 0 then for _, idx in ipairs(availableIndices) do table.insert(candidates, idx) end end local selectedIdx = nil if #candidates ~= 0 then -- Calculating total weight local totalWeight = 0 for _, idx in ipairs(candidates) do totalWeight = totalWeight + LoadingScreensInfo[idx].weight end -- Weighted random selection local rand = math.random() * totalWeight local cumulative = 0 for _, idx in ipairs(candidates) do cumulative = cumulative + LoadingScreensInfo[idx].weight if rand <= cumulative then selectedIdx = idx break end end -- Floating-point rounding can rarely skip every branch; force a valid fallback. if selectedIdx == nil then selectedIdx = candidates[#candidates] end used[selectedIdx] = true end if selectedIdx ~= nil then SetMenuLoadEntryIfValid(index, selectedIdx) end end -- local _table = GetCurrentLoadingScreenTable() -- table.sort(_table) -- Log(unpack(_table)) end function SetNextLoadingScreenToPrevPlusOne() local currentIndex = GetPointerValue(GAME_BASE.CS_NOW_LOADING_HELPER.CURRENT_MENU_LOAD_INDEX) -- Range 0 - 33 local prevMenuLoadIndex = currentIndex local prevID = GetPointerValue(GAME_BASE.CS_NOW_LOADING_HELPER.MENU_LOAD_ENTRIES(prevMenuLoadIndex)) or 0 if prevID == 0 or prevID == nil or not IsValidLoadingScreenId(prevID) then return end local maxIdx = 0 for idx, _ in pairs(LoadingScreensInfo) do if idx > maxIdx then maxIdx = idx end end local nextID = prevID local attempts = 0 repeat nextID = nextID + 1 if maxIdx > 0 and nextID > maxIdx then nextID = 1 end attempts = attempts + 1 until attempts > maxIdx or IsValidLoadingScreenId(nextID) if not IsValidLoadingScreenId(nextID) then return end local nextMenuLoadIndex = currentIndex + 1 if nextMenuLoadIndex > MENU_LOAD_INDEX_MAX then nextMenuLoadIndex = 0 end SetMenuLoadEntryIfValid(nextMenuLoadIndex, nextID) Log("[" .. nextMenuLoadIndex .. "]", prevID, "->", nextID) end function GetCurrentLoadingScreenTable() local _table = {} for i = 0, 33, 1 do table.insert(_table, GetPointerValue(GAME_BASE.CS_NOW_LOADING_HELPER.MENU_LOAD_ENTRIES(i))) end return _table end local monitoredFlags = nil function MonitorLoadingScreenEventFlags() -- Collect all loading screens that require a flag and are currently locked. if monitoredFlags == nil then monitoredFlags = {} for index, info in pairs(LoadingScreensInfo) do if info.requiredEventFlag ~= "0" and env(GetEventFlag, info.requiredEventFlag) ~= TRUE then monitoredFlags[info.requiredEventFlag] = index end end elseif next(monitoredFlags) == nil then return end -- Monitoring loop (call this function periodically, e.g., every frame or on event update) for flag, index in pairs(monitoredFlags) do if env(GetEventFlag, flag) == TRUE then local nextMenuLoadIndex = GetPointerValue(GAME_BASE.CS_NOW_LOADING_HELPER.CURRENT_MENU_LOAD_INDEX) + 1 if nextMenuLoadIndex > MENU_LOAD_INDEX_MAX then nextMenuLoadIndex = 0 end SetMenuLoadEntryIfValid(nextMenuLoadIndex, index) monitoredFlags[flag] = nil end end end local nextLoadingScreen = 0 function LimitLoadingScreenAmountHandler() if nextLoadingScreen ~= 0 then return end local currentTable = GetCurrentLoadingScreenTable() local currentIndex = GetPointerValue(GAME_BASE.CS_NOW_LOADING_HELPER.CURRENT_MENU_LOAD_INDEX) -- Range 0 - 33 local nextTableIndex = currentIndex + 2 -- Not +1 because lua starts its table index at 1 if nextTableIndex > MENU_LOAD_ENTRY_COUNT then nextTableIndex = nextTableIndex - MENU_LOAD_ENTRY_COUNT end local _nextLoadingScreen = currentTable[nextTableIndex] local nextLoadingScreenInfo = LoadingScreensInfo[_nextLoadingScreen] local nextLoadingScreenFlag = nextLoadingScreenInfo and nextLoadingScreenInfo.requiredEventFlag or nil -- Doublecheck eventflag in case of character swap if nextLoadingScreenInfo ~= nil and (nextLoadingScreenFlag == "0" or env(GetEventFlag, nextLoadingScreenFlag) == TRUE) then nextLoadingScreen = _nextLoadingScreen return end SetPointerValue(GAME_BASE.CS_NOW_LOADING_HELPER.CURRENT_MENU_LOAD_INDEX, 1) PopulateLoadingScreens() -- Since index will be reset to 1 local refreshedTable = GetCurrentLoadingScreenTable() local refreshedFirst = refreshedTable[1] if IsValidLoadingScreenId(refreshedFirst) then nextLoadingScreen = refreshedFirst else nextLoadingScreen = 0 end end