diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ffea8b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.idea/ +.vscode +.vs +.vu/* +ext/Client/.vu/* +ext/Shared/.vu/* +ext/Server/.vu/* +sb \ No newline at end of file diff --git a/ext/Client/__init__.lua b/ext/Client/__init__.lua deleted file mode 100644 index c93f5e2..0000000 --- a/ext/Client/__init__.lua +++ /dev/null @@ -1,6 +0,0 @@ ----@param p_UseHttp boolean ----@param p_Mirrors string[] -NetEvents:Subscribe("CLL:HTTPINFO", function(p_UseHttp, p_Mirrors) - Config.USE_HTTP = p_UseHttp - Config.MIRRORS = p_Mirrors -end) diff --git a/ext/Server/__init__.lua b/ext/Server/__init__.lua deleted file mode 100644 index d904432..0000000 --- a/ext/Server/__init__.lua +++ /dev/null @@ -1,34 +0,0 @@ ----@param p_Command string ----@param p_Args string[] ----@return string[] -RCON:RegisterCommand("CLL.useHttp", RemoteCommandFlag.DisableAfterStartup, function(p_Command, p_Args) - local s_BooleanString = p_Args[1] - - if not s_BooleanString then - return { "OK", tostring(Config.USE_HTTP) } - end - - if s_BooleanString == "1" or s_BooleanString:lower() == "true" then - Config.USE_HTTP = true - else - Config.USE_HTTP = false - end - - return { "OK" } -end) - ----@param p_Command string ----@param p_Args string[] ----@return string[] -RCON:RegisterCommand("CLL.addMirror", RemoteCommandFlag.DisableAfterStartup, function(p_Command, p_Args) - if p_Args[1] then - table.insert(Config.MIRRORS, p_Args[1]) - end - - return { "OK" } -end) - ----@param p_Player Player -Events:Subscribe('Player:Authenticated', function(p_Player) - NetEvents:SendToLocal("CLL:HTTPINFO", p_Player, Config.USE_HTTP, Config.MIRRORS) -end) diff --git a/ext/Shared/Config.lua b/ext/Shared/Config.lua index e776544..100542b 100644 --- a/ext/Shared/Config.lua +++ b/ext/Shared/Config.lua @@ -1,6 +1,4 @@ return { - USE_HTTP = false, - MIRRORS = {}, - LOGGER_ENABLED = false, + LOGGER_ENABLED = true, CLIENT_TIMEOUT = 50 } diff --git a/ext/Shared/Config.release.lua b/ext/Shared/Config.release.lua index e776544..8c7ab4f 100644 --- a/ext/Shared/Config.release.lua +++ b/ext/Shared/Config.release.lua @@ -1,6 +1,4 @@ return { - USE_HTTP = false, - MIRRORS = {}, LOGGER_ENABLED = false, CLIENT_TIMEOUT = 50 } diff --git a/ext/Shared/Levels/BundlesMap.lua b/ext/Shared/Levels/BundlesMap.lua new file mode 100644 index 0000000..bc0ca43 --- /dev/null +++ b/ext/Shared/Levels/BundlesMap.lua @@ -0,0 +1,28 @@ +return [[ +{ + "XP1_001/AAS_Standard": "XP1_001/ConquestAssaultLarge0", + "XP1_001/AAS_Alternative": "XP1_001/ConquestAssaultLarge0", + "XP1_001/SKR_Standard": "XP1_001/ConquestAssaultLarge0", + "XP1_002/AAS_Standard": "XP1_002/ConquestLarge0", + "XP1_002/AAS_Alternative": "XP1_002/ConquestLarge0", + "XP1_002/SKR_Standard": "XP1_002/ConquestLarge0", + "XP3_Shield/AAS_Standard": "XP3_Shield/ConquestLarge0", + "XP3_Shield/AAS_Alternative": "XP3_Shield/ConquestLarge0", + "XP3_Shield/SKR_Standard": "XP3_Shield/ConquestLarge0", + "XP3_Valley/AAS_Standard": "XP3_Valley/ConquestLarge0", + "XP3_Valley/AAS_Alternative": "XP3_Valley/ConquestLarge0", + "XP3_Valley/SKR_Standard": "XP3_Valley/ConquestLarge0", + "XP4_Parl/AAS_Standard": "XP4_Parl/ConquestLarge0", + "XP4_Parl/AAS_Alternative": "XP4_Parl/ConquestLarge0", + "XP4_Parl/SKR_Standard": "XP4_Parl/ConquestLarge0", + "XP5_001/AAS_Standard": "XP5_001/ConquestLarge0", + "XP5_001/AAS_Alternative": "XP5_001/ConquestLarge0", + "XP5_001/SKR_Standard": "XP5_001/ConquestLarge0", + "XP5_003/AAS_Standard": "XP5_003/ConquestLarge0", + "XP5_003/AAS_Alternative": "XP5_003/ConquestLarge0", + "XP5_003/SKR_Standard": "XP5_003/ConquestLarge0", + "XP5_004/AAS_Standard": "XP5_004/ConquestLarge0", + "XP5_004/AAS_Alternative": "XP5_004/ConquestLarge0", + "XP5_004/SKR_Standard": "XP5_004/ConquestLarge0" +} +]] \ No newline at end of file diff --git a/ext/Shared/__init__.lua b/ext/Shared/__init__.lua index 7f0b2b5..4b418c9 100644 --- a/ext/Shared/__init__.lua +++ b/ext/Shared/__init__.lua @@ -1,386 +1,216 @@ ---@class CustomLevelLoaderConfig ----@field USE_HTTP boolean ----@field MIRRORS string[] ---@field LOGGER_ENABLED boolean +---@field CLIENT_TIMEOUT number Config = require "__shared/Config" +BUNDLE_PREFIX = 'CustomLevels' + local print = function(p_Message, p_IsWarning) if Config.LOGGER_ENABLED or p_IsWarning then - print(p_Message) - end -end - -local GameObjectOriginType = { - Vanilla = 1, - Custom = 2, - CustomChild = 3 -} - -local SP_TERRAIN_WORLD_PART_DATA_GUID = Guid('68D438B3-FF1B-47D7-BCB4-F484E67CA700') -local SP_TERRAIN_WORLD_PART_REFERENCE_OBJECT_DATA_GUID = Guid('93842B6D-0185-483D-9EF5-AD2B47BDABDE') - --- This is a global table that stores the save file data as a Lua table. Will be populated on-demand by --- the server via NetEvents on the client-side --- Stores LevelData DataContainer guids. -local m_PrimaryLevelGuids = {} - -local m_IndexCount = 0 -local m_LastLoadedLevelName = nil -local m_LastLoadedGameModeName = nil -local m_ObjectVariations = {} -local m_PendingVariations = {} -local m_CustomLevelData = {} - -local m_Defaults = { - variation = 0, -} - -local m_MapGameModePaths = {} - -local function PatchOriginalObject(p_Object, p_World) - if p_Object.originalRef == nil then - print("Object without original reference found, dynamic object?", true) - print(p_Object, true) - return - end - - local s_Reference = nil - - if p_Object.originalRef.partitionGuid == nil or p_Object.originalRef.partitionGuid == "nil" then -- perform a search without partitionguid - s_Reference = ResourceManager:SearchForInstanceByGuid(Guid(p_Object.originalRef.instanceGuid)) - - if s_Reference == nil then - print("Unable to find original reference: " .. p_Object.originalRef.instanceGuid, true) - return - end - else - s_Reference = ResourceManager:FindInstanceByGuid(Guid(p_Object.originalRef.partitionGuid), Guid(p_Object.originalRef.instanceGuid)) - - if s_Reference == nil then - print("Unable to find original reference: " .. p_Object.originalRef.instanceGuid .. " in partition " .. p_Object.originalRef.partitionGuid, true) - return + if p_IsWarning then + p_Message = 'WARNING: ' .. p_Message end - end - - s_Reference = _G[s_Reference.typeInfo.name](s_Reference) - s_Reference:MakeWritable() - - if p_Object.isDeleted then - s_Reference.excluded = true - end - if p_Object.localTransform then - s_Reference.blueprintTransform = LinearTransform(p_Object.localTransform) -- LinearTransform(p_Object.localTransform) - else - s_Reference.blueprintTransform = LinearTransform(p_Object.transform) -- LinearTransform(p_Object.transform) + print(p_Message) end end -local function AddCustomObject(p_Object, p_World, p_RegistryContainer) - local s_Blueprint = ResourceManager:FindInstanceByGuid(Guid(p_Object.blueprintCtrRef.partitionGuid), Guid(p_Object.blueprintCtrRef.instanceGuid)) - - if s_Blueprint == nil then - print('Cannot find blueprint with guid ' .. tostring(p_Object.blueprintCtrRef.instanceGuid), true) - return - end - - -- Filter BangerEntityData. - if s_Blueprint:Is('ObjectBlueprint') then - local s_ObjectBlueprint = ObjectBlueprint(s_Blueprint) - - if s_ObjectBlueprint.object and s_ObjectBlueprint.object:Is('BangerEntityData') then - return - end - end - - local s_Reference - - if s_Blueprint:Is('EffectBlueprint') then - s_Reference = EffectReferenceObjectData() - s_Reference.autoStart = true - else - s_Reference = ReferenceObjectData() - end - - p_RegistryContainer.referenceObjectRegistry:add(s_Reference) - - if p_Object.localTransform then - s_Reference.blueprintTransform = LinearTransform(p_Object.localTransform) - else - s_Reference.blueprintTransform = LinearTransform(p_Object.transform) - end +---@type table? +local m_LevelRODMap = {} +local m_LazyLoadedCount = 0 - --print("AddCustomObject: " .. p_Object.transform) - s_Reference.blueprint = Blueprint(s_Blueprint) - -- s_Reference.blueprint:MakeWritable() - local s_Variation = p_Object.variation or m_Defaults.variation - if m_ObjectVariations[s_Variation] == nil then - m_PendingVariations[s_Variation] = s_Reference - else - s_Reference.objectVariation = m_ObjectVariations[s_Variation] +---Finds and returns the bundle name associated with the level and gamemode (@p_Path) loaded in the +---case that there is a gamemode map file, and in case the map file does not exist or the path does +---not have an entry it returns @p_Path +---@param p_Path string +---@return string +local function GetBundlePath(p_Path) + local _, s_BundlesMapJson = pcall(require, '__shared/Levels/BundlesMap.lua') + + if not s_BundlesMapJson then + return p_Path end - s_Reference.indexInBlueprint = #p_World.objects + m_IndexCount + 1 - s_Reference.isEventConnectionTarget = Realm.Realm_None - s_Reference.isPropertyConnectionTarget = Realm.Realm_None - s_Reference.castSunShadowEnable = true - s_Reference.excluded = false - - p_World.objects:add(s_Reference) -end - -local function CreateWorldPart(p_PrimaryLevel, p_RegistryContainer) - local s_World = WorldPartData(SP_TERRAIN_WORLD_PART_DATA_GUID) - p_RegistryContainer.blueprintRegistry:add(s_World) - - --find index - for _, l_Object in pairs(p_PrimaryLevel.objects) do - if l_Object:Is('WorldPartReferenceObjectData') then - local l_RefObjectData = WorldPartReferenceObjectData(l_Object) - - if l_RefObjectData.blueprint:Is('WorldPartData') then - local s_WorldPart = WorldPartData(l_RefObjectData.blueprint) - - if #s_WorldPart.objects ~= 0 then - local s_ROD = s_WorldPart.objects[#s_WorldPart.objects] -- last one in array + local s_BundlesMap = json.decode(s_BundlesMapJson) - if s_ROD and s_ROD:Is('ReferenceObjectData') then - s_ROD = ReferenceObjectData(s_ROD) - - if s_ROD.indexInBlueprint > m_IndexCount then - m_IndexCount = s_ROD.indexInBlueprint - end - end - end - end - end - end - -- m_IndexCount = 30000 - print('Index count is: ' .. tostring(m_IndexCount)) - - for _, l_Object in pairs(m_CustomLevelData.data) do - if l_Object.origin == GameObjectOriginType.Custom then - if not m_CustomLevelData.vanillaOnly then - AddCustomObject(l_Object, s_World, p_RegistryContainer) - end - elseif l_Object.origin == GameObjectOriginType.Vanilla then - PatchOriginalObject(l_Object, s_World) - end - -- TODO handle CustomChild + -- Replace spaces in case of custom gamemodes with spaces in their names + p_Path = p_Path:gsub(' ', '_') + + + if s_BundlesMap and s_BundlesMap[p_Path] then + print('Found custom bundle ' .. s_BundlesMap[p_Path] .. ' for gamemode ' .. p_Path .. ' in bundle map file') + return s_BundlesMap[p_Path] end - m_LastLoadedLevelName = SharedUtils:GetLevelName() - m_LastLoadedGameModeName = SharedUtils:GetCurrentGameMode() - - local s_WorldPartReference = WorldPartReferenceObjectData(SP_TERRAIN_WORLD_PART_REFERENCE_OBJECT_DATA_GUID) - s_WorldPartReference.blueprint = s_World - - s_WorldPartReference.isEventConnectionTarget = Realm.Realm_None - s_WorldPartReference.isPropertyConnectionTarget = Realm.Realm_None - s_WorldPartReference.excluded = false - - return s_WorldPartReference -end - ----@param p_FileName string ----@return nil|string @json table -local function GetCustomLevelFromHttp(p_FileName) - if #Config.MIRRORS == 0 then - return nil - end - - local s_HttpOptions = HttpOptions({}, 20) - --ignore cert for wine users - s_HttpOptions.verifyCertificate = false - - for _, l_Address in ipairs(Config.MIRRORS) do - local s_StartTime = SharedUtils:GetTime() - - if m_MapGameModePaths[p_FileName] then - p_FileName = m_MapGameModePaths[p_FileName] - print("Adjusted file name to " .. p_FileName) - end - - local s_Path = l_Address .. p_FileName .. ".json" - local s_HttpResponse = Net:GetHTTP(s_Path, s_HttpOptions) - - local s_Duration = SharedUtils:GetTime() - s_StartTime - - if s_Duration > 25.0 then - print("It took " .. s_Duration .. " seconds to download the custom level json", true) - end - - if not s_HttpResponse then - print("Received no response from " .. s_Path, true) - elseif s_HttpResponse.status ~= 200 then - print("Received http status " .. tostring(s_HttpResponse.status) .. " from " .. s_Path, true) - else - return s_HttpResponse.body - end - end - - print('Couldn\'t find custom level data for: ' .. p_FileName) - return nil + return p_Path end ---@param p_LevelName string ---@param p_GameModeName string ---@return table|nil -local function GetCustomLevel(p_LevelName, p_GameModeName) +local function GetLevelRODMap(p_LevelName, p_GameModeName) p_LevelName = p_LevelName:gsub(".*/", "") - local s_FileName = p_LevelName .. '_' .. p_GameModeName - - local s_PresetJson + + local s_FileName = GetBundlePath(p_LevelName .. '/' .. p_GameModeName) - if Config.USE_HTTP then - s_PresetJson = GetCustomLevelFromHttp(s_FileName) + -- File name uses _ instead of / + s_FileName = s_FileName:gsub('/', '_') - if not s_PresetJson then - return nil - end - else - local s_Path = '__shared/Levels/' .. p_LevelName .. '/' .. s_FileName + local s_Path = '__shared/Levels/' .. p_LevelName .. '/' .. s_FileName - local s_Ok - s_Ok, s_PresetJson = pcall(require, s_Path) - s_PresetJson = s_Ok and s_PresetJson or nil + local s_Ok, s_LevelRODMapJson = pcall(require, s_Path) + s_LevelRODMapJson = s_Ok and s_LevelRODMapJson or nil - if not s_PresetJson then - print('Couldn\'t find custom level data in path ' .. s_Path) - return nil - end + if not s_LevelRODMapJson then + print('Couldn\'t find ROD map file in path ' .. s_Path) + return nil end - local s_Preset = json.decode(s_PresetJson) + local s_LevelRODMap = json.decode(s_LevelRODMapJson) - if not s_Preset then - error('Couldn\'t decode json preset') + if not s_LevelRODMap then + error('Couldn\'t decode json ROD map') return nil end - print("Preset found for Level: " .. p_LevelName .. " - GameMode: " .. p_GameModeName) + print("ROD map file found for Level: " .. p_LevelName .. " - GameMode: " .. p_GameModeName) - return s_Preset + return s_LevelRODMap end +Hooks:Install('ResourceManager:LoadBundles', 100, function(p_Hook, p_Bundles, p_Compartment) + print(p_Compartment) + print(p_Bundles[1]) +end) + -- nº 1 in calling order -Events:Subscribe('Level:LoadResources', function() +Events:Subscribe('Level:LoadResources', function(p_LevelName, p_GameMode, p_IsDedicatedServer) print("-----Loading resources") - m_ObjectVariations = {} - m_PendingVariations = {} - m_CustomLevelData = GetCustomLevel(SharedUtils:GetLevelName(), SharedUtils:GetCurrentGameMode()) -end) + local s_SuperBundleName = string.gsub(p_LevelName, 'Levels', BUNDLE_PREFIX) + print("MountSuperBundle: " .. s_SuperBundleName) + ResourceManager:MountSuperBundle(s_SuperBundleName) --- nº 2 in calling order -Events:Subscribe('Partition:Loaded', function(p_Partition) - if not m_CustomLevelData then - return - end + m_LevelRODMap = GetLevelRODMap(p_LevelName, p_GameMode) - if p_Partition == nil then + if m_LevelRODMap == nil then + print('Could not get the level, level name: ' .. p_LevelName .. ', gamemode: ' .. p_GameMode, true) return end - local s_PrimaryInstance = p_Partition.primaryInstance - - if s_PrimaryInstance == nil then - print('Instance is null? ' .. p_Partition.name, true) - return + for l_PartitionGuid, l_Instances in pairs(m_LevelRODMap) do + for _, l_InstanceGuid in ipairs(l_Instances) do + ResourceManager:RegisterInstanceLoadHandlerOnce(Guid(l_PartitionGuid), Guid(l_InstanceGuid), function(p_Instance) + p_Instance = _G[p_Instance.typeInfo.name](p_Instance) + p_Instance:MakeWritable() + p_Instance.excluded = true + end) + end end +end) - -- if l_Instance:Is("Blueprint") then - --print("-------"..Blueprint(l_Instance).name) - -- end - - if s_PrimaryInstance.typeInfo.name == "LevelData" then - local s_Instance = LevelData(s_PrimaryInstance) +local function _GetHighestIndexInPartition(s_Partition) + local s_HighestIndexInPartition = 0 - if s_Instance.name == SharedUtils:GetLevelName() then - print("----Registering PrimaryLevel guids") - s_Instance:MakeWritable() + for _, l_Instance in ipairs(s_Partition.instances) do + if l_Instance:Is("GameObjectData") then + l_Instance = GameObjectData(l_Instance) - m_PrimaryLevelGuids = { - instanceGuid = s_Instance.instanceGuid, - partitionGuid = s_Instance.partitionGuid - } - end - elseif s_PrimaryInstance:Is('ObjectVariation') then - -- Store all variations in a map. - local s_Variation = ObjectVariation(s_PrimaryInstance) - m_ObjectVariations[s_Variation.nameHash] = s_Variation - - if m_PendingVariations[s_Variation.nameHash] ~= nil then - for _, l_Object in pairs(m_PendingVariations[s_Variation.nameHash]) do - l_Object.objectVariation = s_Variation + if l_Instance.indexInBlueprint > s_HighestIndexInPartition and l_Instance.indexInBlueprint ~= 65535 then + s_HighestIndexInPartition = l_Instance.indexInBlueprint end - - m_PendingVariations[s_Variation.nameHash] = nil end end -end) --- nº 3 in calling order -Events:Subscribe('Level:LoadingInfo', function(p_Info) - if p_Info == "Registering entity resources" then - print("-----Loading Info - Registering entity resources") + return s_HighestIndexInPartition +end - if not m_CustomLevelData then - print("No custom level specified.") - return - end +---Patches the level, adding a SubWorldReferenceObjectData to the level that references the SubWorld in the custom bundle +local function _PatchLevel(p_LevelName) + local s_Data = LevelData(ResourceManager:SearchForDataContainer(SharedUtils:GetLevelName())) + s_Data:MakeWritable() + + local s_SWROD = SubWorldReferenceObjectData(Guid('6a724d44-4efd-4f7e-9249-2230121d7ecc')) + + local s_Path = GetBundlePath(p_LevelName:gsub(".*/", "") .. '/' .. SharedUtils:GetCurrentGameMode()) + + s_SWROD.bundleName = BUNDLE_PREFIX .. '/' .. s_Path + s_SWROD.blueprintTransform = LinearTransform() + s_SWROD.blueprint = nil + s_SWROD.objectVariation = nil + s_SWROD.streamRealm = StreamRealm.StreamRealm_Both + s_SWROD.castSunShadowEnable = true + s_SWROD.excluded = false + s_SWROD.inclusionSettings = nil + s_SWROD.autoLoad = true + s_SWROD.isWin32SubLevel = true + s_SWROD.isXenonSubLevel = true + s_SWROD.isPs3SubLevel = true + s_SWROD.isEventConnectionTarget = 2 + s_SWROD.isPropertyConnectionTarget = 3 + + local s_Partition = s_Data.partition + + if not s_Partition then + print('Partition was nil', true) + return + end - if m_PrimaryLevelGuids == nil then - print("m_PrimaryLevelGuids is nil, something went wrong", true) - return - end + s_Partition:AddInstance(s_SWROD) - local s_PrimaryLevel = ResourceManager:FindInstanceByGuid(m_PrimaryLevelGuids.partitionGuid, m_PrimaryLevelGuids.instanceGuid) + local s_HighestIndexInPartition = _GetHighestIndexInPartition(s_Partition) + s_SWROD.indexInBlueprint = s_HighestIndexInPartition + 1 - if s_PrimaryLevel == nil then - print("Couldn\'t find PrimaryLevel DataContainer, aborting", true) - return - end + if s_Data.registryContainer ~= nil then + s_Data.registryContainer:MakeWritable() + local s_Registry = RegistryContainer(s_Data.registryContainer) + s_Registry.referenceObjectRegistry:add(s_SWROD) + end - s_PrimaryLevel = LevelData(s_PrimaryLevel) + local s_LinkConnection = LinkConnection() + s_LinkConnection.target = s_SWROD - if m_LastLoadedLevelName == SharedUtils:GetLevelName() and m_LastLoadedGameModeName == SharedUtils:GetCurrentGameMode() then - print('Same map and gamemode loading, skipping') - return - end + s_Data.linkConnections:add(s_LinkConnection) + s_Data.objects:add(s_SWROD) + print('Patched level') +end - print("Patching level") - local s_RegistryContainer = s_PrimaryLevel.registryContainer +Events:Subscribe('Partition:Loaded', function(p_Partition) + local s_LevelName = SharedUtils:GetLevelName() - if s_RegistryContainer == nil then - print('No registryContainer found, this shouldn\'t happen', true) - end + if not s_LevelName then return end - s_RegistryContainer = RegistryContainer(s_RegistryContainer) - s_RegistryContainer:MakeWritable() + if p_Partition.name == s_LevelName:lower() then + print('Patching level') - local s_WorldPartReference = CreateWorldPart(s_PrimaryLevel, s_RegistryContainer) + local s_LevelData = LevelData(p_Partition.primaryInstance) - s_WorldPartReference.indexInBlueprint = #s_PrimaryLevel.objects + for _, l_Object in ipairs(s_LevelData.objects) do + l_Object = _G[l_Object.typeInfo.name](l_Object) - s_PrimaryLevel.objects:add(s_WorldPartReference) + if l_Object.blueprint and l_Object.blueprint.isLazyLoaded then + m_LazyLoadedCount = m_LazyLoadedCount + 1 + print("LazyLoadedCount " .. m_LazyLoadedCount) + + l_Object.blueprint:RegisterLoadHandlerOnce(function (p_Instance) + m_LazyLoadedCount = m_LazyLoadedCount - 1 + if m_LazyLoadedCount == 0 then _PatchLevel(s_LevelName) end + end) + end + end - s_RegistryContainer.referenceObjectRegistry:add(s_WorldPartReference) - print('Level patched') + if m_LazyLoadedCount == 0 then _PatchLevel(s_LevelName) end end end) -- Remove all DataContainer references and reset vars Events:Subscribe('Level:Destroy', function() - m_ObjectVariations = {} - m_PendingVariations = {} - m_IndexCount = 0 - -- TODO: remove all custom objects from level registry and leveldata if next round is -- the same map but a different save, once that is implemented. If it's a different map -- there is no need to clear anything, as the leveldata will be unloaded and a new one loaded end) +-- Increase timeout ResourceManager:RegisterInstanceLoadHandler(Guid('C4DCACFF-ED8F-BC87-F647-0BC8ACE0D9B4'), Guid('B479A8FA-67FF-8825-9421-B31DE95B551A'), function(p_Instance) p_Instance = ClientSettings(p_Instance) p_Instance:MakeWritable() @@ -399,7 +229,3 @@ ResourceManager:RegisterInstanceLoadHandler(Guid('C4DCACFF-ED8F-BC87-F647-0BC8AC print("Changed ServerSettings") end) ----@param p_MapGameModePaths table -Events:Subscribe("CLL:MapGameModePaths", function(p_MapGameModePaths) - m_MapGameModePaths = p_MapGameModePaths -end) diff --git a/mod.json b/mod.json index 1a564d3..b7e9ec5 100644 --- a/mod.json +++ b/mod.json @@ -7,6 +7,16 @@ "HasWebUI": false, "HasVeniceEXT": true, "Dependencies": { - "veniceext": "^1.1.0" - } + "veniceext": "^1.1.0" + }, + "Superbundles": [ + "Win32/CustomLevels/XP1_001/XP1_001", + "Win32/CustomLevels/XP1_002/XP1_002", + "Win32/CustomLevels/XP3_Shield/XP3_Shield", + "Win32/CustomLevels/XP3_Valley/XP3_Valley", + "Win32/CustomLevels/XP4_Parl/XP4_Parl", + "Win32/CustomLevels/XP5_001/XP5_001", + "Win32/CustomLevels/XP5_003/XP5_003", + "Win32/CustomLevels/XP5_004/XP5_004" + ] }