Track LocalMods as part of monolith

This commit is contained in:
2026-06-08 18:50:16 +03:00
parent 143f2fed7c
commit 1b214b44c2
1287 changed files with 139255 additions and 1 deletions
@@ -0,0 +1,54 @@
NTID = {}
NTID.Name="Informative Descriptions"
NTID.Version = "1.21"
NTID.Path = table.pack(...)[1]
Timer.Wait(function() if NTC ~= nil and NTC.RegisterExpansion ~= nil then NTC.RegisterExpansion(NTID) end end,1)
--In case neurotrauma doesnt actually register NTC or NT on CLIENTs for MP, gonna bruteforce
NTworkshopIds = {
"3190189044",
"2776270649"
}
function IsNTEnabled()
for package in ContentPackageManager.EnabledPackages.All do
for NTworkshopId in NTworkshopIds do
if tostring(package.UgcId) == NTworkshopId then
return true
end
end
end
return false
end
function EnableNTID()
if NTC ~= nil or NT ~= nil or IsNTEnabled() then
dofile(NTID.Path .. '/Lua/main.lua')
-- Calling UpdateEntityList in short timer crashes subeditor with too many mods
if CLIENT and Game.IsSubEditor then
Timer.Wait(function()
LuaUserData.MakeMethodAccessible(Descriptors["Barotrauma.SubEditorScreen"], "UpdateEntityList")
Game.SubEditorScreen.UpdateEntityList()
end, 5000)
end
return true
end
return false
end
-- longer timer fallback in case NT isnt registered yet on first lua pass
if EnableNTID() then
return
end
Timer.Wait(function()
if EnableNTID() then
return
end
print("Error loading NT Informative Descriptions: it appears Neurotrauma isn't loaded!")
end,1000)
@@ -0,0 +1,10 @@
-- getting this from different text files from disabled languages is exorbitantly complicated in lua due to billion different types i have to register.
-- so fuck it just manual table
local suffixes = {
English = "A ‖color:gui.blue‖printed donor card‖end‖ can be slotted into it.",
Russian = "Можно вставить ‖color:gui.blue‖карту донора‖end‖ в слот.",
["Simplified Chinese"] = "可以插入一张‖color:gui.blue‖打印的捐赠卡‖end‖。",
French = "Une ‖color:gui.blue‖carte de donneur‖end‖ peut être insérée."
}
return suffixes
+544
View File
@@ -0,0 +1,544 @@
InfDescriptions = {}
local rawconfig = require("rawconfig")
local config = {
Name = "infdescriptions", --Internal name, used for rawconfig.Configs.Name
Label = "infdescriptions.modname", --String displayed in menus, can be localization tag
Entries = {
Enabled = {
name = "infdescriptions.cfg.enabled", --String displayed in menus, can be localization tag
default = true,
type = "bool",
description = "infdescriptions.cfg.enableddescription", --String displayed in tooltip, can be localization tag
enforcment = rawconfig.Enforcment.Client,
},
Shared = {
name = "infdescriptions.cfg.shared", --String displayed in menus, can be localization tag
default = true,
type = "bool",
description = "infdescriptions.cfg.shareddescription", --String displayed in tooltip, can be localization tag
enforcment = rawconfig.Enforcment.Server,
},
},
}
config = rawconfig.addConfig(config)
config:LoadConfig()
local pkg
for package in ContentPackageManager.EnabledPackages.All do
local path = tostring(package.Dir)
if path == NTID.Path or string.gsub(path,"\\","/") == NTID.Path then
pkg = package
break
end
end
if SERVER and CSActive then
LuaUserData.RegisterType("System.Collections.Immutable.ImmutableArray`1[[Barotrauma.ContentPackage,DedicatedServer]]")
LuaUserData.RegisterType("System.Collections.Immutable.ImmutableArray`1+Builder[[Barotrauma.ContentPackage,DedicatedServer]]")
BindingFlags = LuaUserData.CreateEnumTable("System.Reflection.BindingFlags")
LuaUserData.RegisterType("System.Type")
LuaUserData.RegisterType("System.Reflection.FieldInfo")
LuaUserData.RegisterType("Barotrauma.Networking.ModSender")
ModSender = LuaUserData.CreateStatic('Barotrauma.Networking.ModSender')
LuaUserData.RegisterType("Barotrauma.SaveUtil")
SaveUtil = LuaUserData.CreateStatic('Barotrauma.SaveUtil')
LuaUserData.RegisterType("Barotrauma.Steam.SteamManager")
SteamManager = LuaUserData.CreateStatic('Barotrauma.Steam.SteamManager')
LuaUserData.RegisterType("Steamworks.SteamServer")
SteamServer = LuaUserData.CreateStatic('Steamworks.SteamServer')
LuaUserData.RegisterType("Barotrauma.Networking.ServerPeer`1")
LuaUserData.RegisterType("Barotrauma.Networking.LidgrenServerPeer")
LuaUserData.RegisterType("Barotrauma.Networking.P2PServerPeer")
HasMultiplayerSyncedContent_fieldinfo = LuaUserData.GetType('Barotrauma.ContentPackage').GetField("<HasMultiplayerSyncedContent>k__BackingField", bit32.bor(BindingFlags.Instance, BindingFlags.NonPublic))
if Game.IsDedicated then
contentPackages_fieldinfo = LuaUserData.GetType('Barotrauma.Networking.LidgrenServerPeer').BaseType.GetField("contentPackages", bit32.bor(BindingFlags.Instance, BindingFlags.NonPublic))
else
contentPackages_fieldinfo = LuaUserData.GetType('Barotrauma.Networking.P2PServerPeer').BaseType.GetField("contentPackages", bit32.bor(BindingFlags.Instance, BindingFlags.NonPublic))
end
function InfDescriptions.AddToPublicModlist()
HasMultiplayerSyncedContent_fieldinfo.SetValue(pkg, true)
--Game.Server.ModSender.CompressMod(pkg)
SaveUtil.CompressDirectory(pkg.Dir, ModSender.GetCompressedModPath(pkg))
contentpackagestmp = contentPackages_fieldinfo.GetValue(Game.Server.serverPeer).ToBuilder()
contentpackagestmp.Add(pkg)
contentPackages_fieldinfo.SetValue(Game.Server.serverPeer, contentpackagestmp.ToImmutable())
SteamServer.ClearKeys()
SteamManager.RefreshServerDetails(Game.Server)
end
function InfDescriptions.RemoveFromPublicModlist()
HasMultiplayerSyncedContent_fieldinfo.SetValue(pkg, false)
SaveUtil.DeleteIfExists(ModSender.GetCompressedModPath(pkg))
contentpackagestmp = contentPackages_fieldinfo.GetValue(Game.Server.serverPeer).ToBuilder()
contentpackagestmp.Remove(pkg)
contentPackages_fieldinfo.SetValue(Game.Server.serverPeer, contentpackagestmp.ToImmutable())
SteamServer.ClearKeys()
SteamManager.RefreshServerDetails(Game.Server)
end
end
config.SaveConfig = function()
if CLIENT then
if config:Get("Enabled",true) then
EnableNTID()
else
DisableNTID()
end
end
if SERVER then
if config:Get("Shared",false) and not pkg.HasMultiplayerSyncedContent then
InfDescriptions.AddToPublicModlist()
elseif config:Get("Shared",false) == false and pkg.HasMultiplayerSyncedContent then
InfDescriptions.RemoveFromPublicModlist()
end
end
rawconfig.util.SaveConfig(config)
end
if SERVER then return end
local modconfig = require("modconfig")
local idcardsuffixes = require("idcardsuffixes")
local TextFile = LuaUserData.CreateStatic("Barotrauma.TextFile", true)
local FileList = {}
LuaUserData.MakeFieldAccessible(Descriptors["Barotrauma.GameSettings"], "currentConfig")
local ClientLanguage = tostring(GameSettings.currentConfig.Language)
local prev_language = ClientLanguage
LuaUserData.MakeFieldAccessible(Descriptors['Barotrauma.ContentPackageManager+EnabledPackages'], 'regular')
-- LuaUserData.MakeMethodAccessible(Descriptors['Barotrauma.ContentPackageManager+EnabledPackages'], 'SortContent')
LuaUserData.RegisterType("System.Collections.Generic.List`1[[Barotrauma.RegularPackage,Barotrauma]]")
LuaUserData.RegisterType("System.Collections.Immutable.ImmutableList`1[[Barotrauma.TextPack,Barotrauma]]")
LuaUserData.RegisterType("System.Collections.Immutable.ImmutableList`1+Builder[[Barotrauma.TextPack,Barotrauma]]")
LuaUserData.RegisterType("System.Collections.Concurrent.ConcurrentDictionary`2[[Barotrauma.LanguageIdentifier,Barotrauma],[System.Collections.Immutable.ImmutableList`1[[Barotrauma.TextPack,Barotrauma]],System.Collections.Immutable]]")
function EnableTextFile(file, workshopId)
local targetPackage
local targetFile
if workshopId == nil then
targetPackage = pkg
else
for package in ContentPackageManager.AllPackages do
if tostring(package.UgcId) == workshopId then
targetPackage = package
break
end
end
end
if targetPackage == nil then
print("Could not find package to enable with workshop id ", workshopId)
return false
end
if File.Exists(targetPackage.Dir .. "/" .. file) == false then
print("Could not find file " .. file .. " in " .. targetPackage.name)
return false
end
targetFile = TextFile(targetPackage, ContentPath.FromRaw(targetPackage, targetPackage.Dir .. "/" .. file))
if targetFile == nil then
print("Could not find file " .. file .. " in " .. targetPackage.name)
return false
end
targetFile.LoadFile()
table.insert(FileList, targetFile)
--print("Enabled " .. targetFile.Path.Value .. " in package ", targetPackage.name)
--print(targetFile.ContentPackage.Name)
return true
end
function EnableTextFiles(files, language)
if not language then language = ClientLanguage end
for file in files do
file = StripModDir(file)
file = string.gsub(file, "%%Language%%", language)
if not EnableTextFile(file) then
return false
end
end
return true
end
function DisableTextPackage(workshopId, language)
local targetPackage
local languageTrimmed = string.sub(language, 2) .. ".xml"
for package in ContentPackageManager.EnabledPackages.All do
if tostring(package.UgcId) == workshopId then
targetPackage = package
break
end
end
if targetPackage == nil then
print("Could not find package to disable with workshop id ", workshopId)
return
end
for file in targetPackage.Files do
if LuaUserData.IsTargetType(file, "Barotrauma.TextFile") and string.endsWith(file.Path.Value, languageTrimmed) then
file.UnloadFile()
--print("Disabled " .. file.Path.Value .. " in package ", workshopId)
break
end
end
end
function ClientHasSupportedLanguage(supportedlanguages)
local value = false
for language in supportedlanguages do
if language == ClientLanguage then
value = true
break
end
end
return value
end
function IsModEnabled(workshopId)
local value = false
if workshopId == nil or workshopId == "" then
return false
end
for package in ContentPackageManager.EnabledPackages.All do
if tostring(package.UgcId) == workshopId then
value = true
break
end
end
return value
end
function IsFirstHigherPriority(a, b)
if a.loadpriority > b.loadpriority then
return true
else
return false
end
end
function GetPackageByName(name)
for package in ContentPackageManager.EnabledPackages.All do
if package.Name == name then
return package
end
end
return nil
end
function GetPackageById(id)
for package in ContentPackageManager.EnabledPackages.All do
if tostring(package.UgcId) == id then
return package
end
end
end
function LoadPatches()
table.sort(modconfig, IsFirstHigherPriority)
for _, patch in pairs(modconfig) do
local files = {}
local modname = ""
if patch.IgnoreTargetModState or IsModEnabled(patch.workshopId) then
for language in patch.supportedlanguages do
--DisableTextPackage(patch.workshopId, language)
modname = GetPackageById(patch.workshopId).name
if not EnableTextFiles(patch.files, language) then
print("Errors enabling NTID files")
break
end
end
end
end
--For whatever reason content specific Sort isnt static method and i dont want to be sorting all the content in game
--ContentPackageManager.EnabledPackages.SortContent()
--if FileList[1] then FileList[1].Sort() end
if FileList[1] then
local textpacks = TextManager.TextPacks[GameSettings.CurrentConfig.Language].ToBuilder()
local firstindex = 0
local targetindex = 0
for i = 0, textpacks.Count-1, 1 do
if textpacks[i].ContentFile.ContentPackage.Name ~= "Vanilla" then
targetindex = i
break
end
end
for i = textpacks.Count-1, 0, -1 do
if textpacks[i].ContentFile.ContentPackage ~= pkg then
firstindex = i+1
break
end
end
local range = textpacks.GetRange(firstindex, textpacks.Count-firstindex)
-- for pack in range do
-- print(pack.ContentFile.Path)
-- end
for i=textpacks.Count-1, firstindex, -1 do
textpacks.RemoveAt(i)
end
textpacks.InsertRange(targetindex, range)
textpacks = textpacks.ToImmutable()
TextManager.TextPacks.TryUpdate(GameSettings.CurrentConfig.Language, textpacks, TextManager.TextPacks[GameSettings.CurrentConfig.Language])
end
end
function StripModDir(filepath)
local div = string.find(filepath, "%%[/\\]" )
if div == nil then
filepath = filepath
else
--moddir = string.sub(filepath, 1, div)
filepath = string.sub(filepath, div+2)
end
return filepath
end
function UnloadPatches()
for file in FileList do
--print("Unloading ", file.Path.Value)
file.UnloadFile()
end
FileList = {}
end
function ReloadModsLocalization()
for package in ContentPackageManager.EnabledPackages.All do
for _, patch in pairs(modconfig) do
if tostring(package.UgcId) == patch.workshopId then
for file in package.Files do
if LuaUserData.IsTargetType(file, "Barotrauma.TextFile") then
file.LoadFile()
--print("Reenabled " .. file.Path.Value .. " in package ", workshopId)
end
end
break
end
end
end
end
function AppendIdcard(instance, spawnPoint, character)
if spawnPoint ~= nil and spawnPoint.IdCardDesc ~= nil then
if string.find(spawnPoint.IdCardDesc, "%S") then
obj = instance.item
obj.Description = obj.Description .. " " .. idcardsuffixes[ClientLanguage]
end
end
end
function UpdateIdCards()
-- retuns empty string if unsupported localization
local idcard_suffix = idcardsuffixes[ClientLanguage]
if idcard_suffix == nil or idcard_suffix == "" then
return
end
-- characterInfos = Game.GameSession.CrewManager.characterInfos
-- for info in characterInfos do
-- print(info.Name)
-- end
-- mainSubSpawnPoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub)
for item in Item.ItemList do
if item.Prefab.Identifier.Value == "idcard" then
OriginalDescription = TextManager.Get("EntityDescription." .. item.Prefab.Identifier.Value)
if item.Description ~= OriginalDescription then
item.Description = tostring(item.Description) .. " " .. idcard_suffix
end
end
end
end
function CleanUpIdCards()
local description
for item in Item.ItemList do
if item.Prefab.Identifier.Value == "idcard" then
OriginalDescription = TextManager.Get("EntityDescription." .. item.Prefab.Identifier.Value)
if item.Description ~= OriginalDescription then
description = tostring(item.Description)
for suffix in idcardsuffixes do
description = string.gsub(description, " " .. suffix, "")
end
item.Description = description
end
end
end
end
function LanguageChanged()
ClientLanguage = tostring(GameSettings.currentConfig.Language)
if ClientLanguage ~= prev_language then
prev_language = ClientLanguage
return true
end
return false
end
function ReloadNTID()
if pkg == nil then
print("Package not found.")
return
end
UnloadPatches()
ClientLanguage = tostring(GameSettings.currentConfig.Language)
modconfig = {}
modconfig = dofile(NTID.Path .. "/Lua/modconfig.lua")
LoadPatches()
CleanUpIdCards()
UpdateIdCards()
end
function ReloadIdCards()
CleanUpIdCards()
UpdateIdCards()
end
function EnableNTID()
if pkg == nil then
print("Package not found.")
return
end
ClientLanguage = tostring(GameSettings.currentConfig.Language)
modconfig = {}
modconfig = dofile(NTID.Path .. "/Lua/modconfig.lua")
LoadPatches()
CleanUpIdCards()
UpdateIdCards()
end
function DisableNTID()
if pkg == nil then
print("Package not found.")
return
end
UnloadPatches()
--ReloadModsLocalization()
CleanUpIdCards()
end
Hook.Add("stop", "NTIDCleanUp", function ()
UnloadPatches()
--ReloadModsLocalization()
end)
Hook.Patch("Barotrauma.GameSettings", "SaveCurrentConfig", function(instance, ptable)
if LanguageChanged() then
ReloadIdCards()
end
end, Hook.HookMethodType.After)
Hook.Patch("Barotrauma.Items.Components.IdCard", "Initialize", function(instance, ptable)
AppendIdcard(instance, ptable["spawnPoint"], ptable["character"])
end, Hook.HookMethodType.After)
-- Hook.Patch("Barotrauma.Items.Components.IdCard", "OnItemLoaded", function(instance, ptable)
-- print("id card testing 2")
-- AppendIdcard(instance)
-- end, Hook.HookMethodType.After)
Game.AddCommand("reloadNTID", "Reloads NT Informative Descriptions.", function()
ReloadNTID()
print("NTID reloaded.")
end, GetValidArguments)
if not pkg then
print("NTID couldn't find its own folder to load texts, report to mod author")
return
end
ContentPackageManager.EnabledPackages.regular.Remove(pkg)
ContentPackageManager.EnabledPackages.regular.Insert(0, pkg)
if config:Get("Enabled",true) then
EnableNTID()
else
DisableNTID()
end
--ContentPackageManager.ReloadContentPackage(pkg)
Game.AddCommand("ntid_debug", "Shows various debug info", function()
print("###TEST1###")
for pack in TextManager.TextPacks[GameSettings.CurrentConfig.Language] do print(pack.ContentFile.Path) end
print("\n###TEST2###")
for k, v in pairs(modconfig) do
print(k, " > ", v)
for k1, v1 in pairs(v) do
print(k1, " >> ", v1)
if type(v1) == "table" then
for k2, v2 in pairs(v1) do
print(k2, " >>> ", v2)
end
end
end
end
end, GetValidArguments)
+110
View File
@@ -0,0 +1,110 @@
-- Config to enable language files based on enabled mods.
local config = {
{
--Neurotrauma guns fork
supportedlanguages = {"English","Simplified Chinese","Russian", "French"}, -- which languages patch supports for this specific mod {"English","German","Russian"} etc
IgnoreTargetModState = false, -- doesnt check for target mod being enabled. Still respects supported languages.
workshopId = "3190189044", -- id of the mod being patched and which language files we disable, "" means nothing gets disabled
loadpriority = 0, -- patches with higher priotiry would override ones with lower when enabled. works similiar to game modlist
files = { -- path to language files we want to use instead of originals, %Language% is automatically replaced with client language
"%ModDir%/Localization/%Language%/Items.xml", -- if files is empty (files = {}) then it would just disable mod language files
"%ModDir%/Localization/%Language%/Afflictions.xml"
}
},
{
-- Curly's Surgery Plus (not yet ready)
supportedlanguages = {"English","Simplified Chinese","Russian","French"},
IgnoreTargetModState = false,
workshopId = "",
loadpriority = 1,
files = {
--"%ModDir%/Localization/%Language%/SurgeryPlus.xml"
}
},
{
-- Cybernetics
supportedlanguages = {"English","Russian","French"},
IgnoreTargetModState = false,
workshopId = "2788543375",
loadpriority = 0,
files = {"%ModDir%/Localization/%Language%/Cybernetics.xml"}
},
{
-- Cybernetics Improved
supportedlanguages = {"English","French"},
IgnoreTargetModState = false,
workshopId = "2963474291",
loadpriority = 0,
files = {"%ModDir%/Localization/%Language%/Cybernetics.xml"}
},
{
-- Cybernetics Enhanced
supportedlanguages = {"English","Russian","French"},
IgnoreTargetModState = false,
workshopId = "3324062208",
loadpriority = 1,
files = {"%ModDir%/Localization/%Language%/Cybernetics.xml"}
},
{
-- Neurotrauma experimental dev version
supportedlanguages = {"English","Simplified Chinese","Russian","French"},
IgnoreTargetModState = false,
workshopId = "3439141713",
loadpriority = 0,
files = {
"%ModDir%/Localization/%Language%/Items.xml",
"%ModDir%/Localization/%Language%/Afflictions.xml"
}
},
{
-- Infections
supportedlanguages = {"English","Russian","French"},
IgnoreTargetModState = false,
workshopId = "",
loadpriority = 2,
files = {
"%ModDir%/Localization/%Language%/Infections.xml"
}
},
{
-- Barometric Pressure
supportedlanguages = {"English"},
IgnoreTargetModState = false,
workshopId = "",
loadpriority = 3,
files = {
"%ModDir%/Localization/%Language%/BarometricPressureItems.xml",
"%ModDir%/Localization/%Language%/BarometricPressureAfflictions.xml",
"%ModDir%/Localization/%Language%/BarometricPressureAddons.xml"
}
},
{
-- Immersive Repairs NT Cybernetics
supportedlanguages = {"English","French"},
IgnoreTargetModState = false,
workshopId = "3153498101",
loadpriority = 0,
files = {"%ModDir%/Localization/%Language%/ImmersiveRepairs.xml"}
}
-- Example blank, you dont need to add localization for this
-- {
-- supportedlanguages = {"English"},
-- IgnoreTargetModState = false,
-- workshopId = "", -- if empty doesnt announce patch being enabled or check for other mods
-- loadpriority = 1, -- patches with same priority would load in random order
-- files = {
-- "%ModDir%/Localization/%Language%/dummy.xml"
-- }
-- }
}
return config
+527
View File
@@ -0,0 +1,527 @@
local LockedStr = TextManager.ContainsTag("rawconfig.enforcedtooltip") and TextManager.Get("rawconfig.enforcedtooltip") or "This setting is enforced by server"
local UnlockedStr = TextManager.ContainsTag("rawconfig.notenforcedtooltip") and TextManager.Get("rawconfig.notenforcedtooltip") or "This setting is not enforced by server"
local Version = 0.2
local rawconfig = {}
rawconfig.Version = Version
local configDirectoryPath = Game.SaveFolder .. "/ModConfigs"
local configFilePath = configDirectoryPath .. "/Neurotrauma.json"
rawconfig.Configs = {}
rawconfig.gui = {}
rawconfig.util = {}
rawconfig.util.DirectoryPath = Game.SaveFolder .. "/ModConfigs"
rawconfig.util.Buffer = {}
--Which side provides config value. Optional adds a Lock icon which can be toggled by Owner.
--Clientside entires can be bypassed with some "hacking" but also by not having clientside lua so either use Clientsidelua Enforced
--Or dont use clientside entries for anythign critical
rawconfig.Enforcment = {
Client = 0,
Server = 1,
Optional = 2,
}
if CLIENT then
Hook.Patch("rawconfig" .. Version .. "_Pausemenu", "Barotrauma.GUI", "TogglePauseMenu", {}, function(instance, ptable)
if not GUI.GUI.PauseMenuOpen then return end
local PMframe = GUI.GUI.PauseMenu
local PMInner = PMframe.GetChild(Int32(1))
local PMbuttonContainer = PMInner.GetChild(Int32(0))
--local textblocks = {}
for config in rawconfig.Configs do
local button = GUI.Button(
GUI.RectTransform(Vector2(1, 0.1), PMbuttonContainer.RectTransform),
config.Label or config.Name,
GUI.Alignment.Center,
"GUIButtonSmall"
)
button.OnClicked = function()
config:OpenMenu()
if Game.IsMultiplayer then
rawconfig.util.RequestConfig(config)
end
end
config.PauseMenuButton = button
end
--GUI.TextBlock.AutoScaleAndNormalize(textblocks)
local PMSizeY = 0
for child in PMbuttonContainer.Children do
PMSizeY = PMSizeY + child.Rect.Height + PMbuttonContainer.AbsoluteSpacing
end
PMSizeY = math.floor(PMSizeY / PMbuttonContainer.RectTransform.RelativeSize.Y)
PMInner.RectTransform.MinSize = Point(PMInner.RectTransform.MinSize.X, math.max(PMSizeY, PMInner.RectTransform.MinSize.X));
end, Hook.HookMethodType.After)
end
--Allows indexing util and gui table values from config table itself
--Makes possible stuff like Config:Set(key, value) without needing to touch underlying util
--This also allows overriding any gui/util function by adding your own implementation to Config as __index meta is only used for unspecified keys
metatable = {
__index = function(self, key)
return rawconfig.util[key] or rawconfig.gui[key]
end,
}
---Add a button to the bottom of pause menu for your config menu
---@param string how your button would be labeled. Supports localization tags.
---@param function(button) function called when button is pressed with Barotrauma.GUIButton passed as argument
function rawconfig.addConfig(config)
config.Label = config.Label and TextManager.ContainsTag(config.Label) and TextManager.Get(config.Label) or Label
config.EnableGUI = config.EnableGUI or true
config.CanBeReset = config.CanBeReset or false
config.FilePath = config.FilePath or rawconfig.util.DirectoryPath .. "/" .. config.Name .. ".json"
config.PauseMenuButton = nil
for entry in config.Entries do
entry.value = entry.default
entry.name = TextManager.ContainsTag(entry.name) and TextManager.Get(entry.name) or entry.name
entry.description = TextManager.ContainsTag(entry.description) and TextManager.Get(entry.description) or entry.description
if entry.enforcment == rawconfig.Enforcment.Optional then
entry.enforced = entry.enforced or false
else
entry.enforced = entry.enforcment == rawconfig.Enforcment.Server
end
end
setmetatable(config, metatable)
rawconfig.Configs[config.Name] = config
return rawconfig.Configs[config.Name]
end
function rawconfig.util.RequestConfig(config)
if Game.IsMultiplayer then
local msg = Networking.Start("rawconfig.ConfigRequest")
msg.WriteString(config.Name)
Networking.Send(msg)
end
end
function rawconfig.util.LoadConfig(config)
if not File.Exists(config.FilePath) then
return
end
local readConfig = json.parse(File.Read(config.FilePath))
for key, entry in pairs(readConfig) do
if config.Entries[key] then
config.Entries[key].value = entry.value
if entry.enforced ~= nil and config.Entries[key].enforcment == rawconfig.Enforcment.Optional then
config.Entries[key].enforced = entry.enforced
end
end
end
if Game.IsMultiplayer and CLIENT then
rawconfig.util.RequestConfig(config)
end
end
function rawconfig.util.SaveConfig(config, preserveUnusedKeys)
--prevent both owner client and server saving config at the same time and potentially erroring from file access
if Game.IsMultiplayer and CLIENT and Game.Client.MyClient.IsOwner then
return
end
preserveUnusedKeys = preserveUnusedKeys or true
local tableToSave = {}
if preserveUnusedKeys and File.Exists(config.FilePath) then
tableToSave = json.parse(File.Read(config.FilePath))
end
for key, entry in pairs(config.Entries) do
tableToSave[key] = {}
--Only save clientside keys when in mp
if Game.IsMultiplayer and CLIENT then
if not entry.enforced then
tableToSave[key].value = entry.value
end
else
tableToSave[key].value = entry.value
if entry.enforcment == rawconfig.Enforcment.Optional then
tableToSave[key].enforced = entry.enforced
end
end
end
File.CreateDirectory(config.DirectoryPath)
--Apparently access error can still happen if player hosts dedicated and joins it on same pc.
--Haven't found how to check file being in use properly so just delay to avoid conflict
if CLIENT then
File.Write(config.FilePath, json.serialize(tableToSave))
else
Timer.Wait(function()
File.Write(config.FilePath, json.serialize(tableToSave))
end, 100)
end
end
function rawconfig.util.ResetConfig(config)
for key, entry in pairs(config.Entries) do
config.Entries[key].value = entry.default
end
end
function rawconfig.util.SendConfig(config, reciverClient)
local tableToSend = {}
for key, entry in pairs(config.Entries) do
tableToSend[key] = {}
tableToSend[key].value = entry.value
tableToSend[key].enforced = entry.enforced
end
local msg = Networking.Start("rawconfig.ConfigUpdate")
msg.WriteString(config.Name)
msg.WriteString(json.serialize(tableToSend))
if SERVER then
Networking.Send(msg, reciverClient and reciverClient.Connection or nil)
else
Networking.Send(msg)
end
end
function rawconfig.util.ReceiveConfig(msg)
local RecivedTable = {}
local targetConfig = rawconfig.Configs[msg.ReadString()]
RecivedTable = json.parse(msg.ReadString())
for key, entry in pairs(RecivedTable) do
if entry.enforced then
targetConfig.Entries[key].value = entry.value
end
if entry.enforced ~= nil and targetConfig.Entries[key].enforcment == rawconfig.Enforcment.Optional then
targetConfig.Entries[key].enforced = entry.enforced
end
end
return targetConfig
end
if CLIENT then
Networking.Receive("rawconfig.ConfigUpdate", function(msg)
config = rawconfig.util.ReceiveConfig(msg)
if config.Frame then
config:UpdateMenu()
end
end)
end
if SERVER then
Networking.Receive("rawconfig.ConfigUpdate", function(msg, sender)
if not rawconfig.util.ClientHasAccess(sender) then
return
end
config = rawconfig.util.ReceiveConfig(msg)
config:SaveConfig()
end)
Networking.Receive("rawconfig.ConfigRequest", function(msg, sender)
if not sender then
return
end
local targetConfig = rawconfig.Configs[msg.ReadString()]
if targetConfig then targetConfig:SendConfig(sender) end
end)
end
function rawconfig.util.Get(config, key, default)
if config.Entries[key] ~= nil and config.Entries[key].value ~= nil then
return config.Entries[key].value
end
return default
end
function rawconfig.util.Set(config, key, value)
if config.Entries[key] ~= nil then
config.Entries[key].value = value
end
end
function rawconfig.util.ClearBuffer()
rawconfig.util.Buffer = {}
end
function rawconfig.util.DumpBuffer(config)
for key, entry in pairs(rawconfig.util.Buffer) do
if config.Entries[key] ~= nil then
for datakey, value in pairs(entry) do
config.Entries[key][datakey] = value
end
end
end
end
function rawconfig.util.ClientHasAccess(client)
return client.HasPermission(ClientPermissions.ManageSettings)
end
rawconfig.gui = {
CloseButton = nil,
SaveButton = nil,
ResetButton = nil,
PauseMenuButton = function(config) return config.PauseMenuButton end,
Frame = nil,
ListContainer = nil,
ControlGroup = nil,
}
---Creates empty box and returns a GUI.Frame to attach other guis into
---@param Barotrauma.RectTransform
---@param Vector2
---@param bool show or hide Reset button
function rawconfig.gui.OpenMenu(config, parent, size)
local menuFrame = GUI.Frame(GUI.RectTransform(size or Vector2(0.3, 0.6), parent or GUI.GUI.PauseMenu.RectTransform, GUI.Anchor.Center))
local menuList = GUI.ListBox(GUI.RectTransform(Vector2(1, 0.9), menuFrame.RectTransform, GUI.Anchor.TopCenter))
menuList.Padding = Vector4(10,15,10,10)
menuList.UpdateDimensions()
local menuControlGroup = GUI.LayoutGroup(GUI.RectTransform(Vector2(0.9, 0.1), menuFrame.RectTransform, GUI.Anchor.BottomCenter), true, GUI.Anchor.BottomLeft)
menuControlGroup.RectTransform.AbsoluteOffset = Point(0, 7)
menuControlGroup.Stretch = true
menuControlGroup.AbsoluteSpacing = 7
rawconfig.gui.SaveButton = config:CreateSaveButton(menuControlGroup)
rawconfig.gui.CloseButton = config:CreateCloseButton(menuControlGroup)
rawconfig.gui.Frame = menuFrame
rawconfig.gui.ListContainer = menuList
rawconfig.gui.ControlGroup = menuControlGroup
if config.CanBeReset then
--rawconfig.ResetButton(menuFrame)
end
menuControlGroup.Recalculate()
config:PopulateMenu()
return menuFrame
end
---Adds save button
function rawconfig.gui.CreateSaveButton(config, parent)
local button = GUI.Button(
GUI.RectTransform(Vector2(0.33, 0.05), parent.RectTransform, GUI.Anchor.BottomCenter),
"Save",
GUI.Alignment.Center,
"GUIButton"
)
button.OnClicked = function()
rawconfig.util.DumpBuffer(config)
rawconfig.util.ClearBuffer()
if Game.IsMultiplayer and Game.Client.HasPermission(ClientPermissions.ManageSettings) then
config:SendConfig()
end
config:SaveConfig()
config:CloseMenu()
end
return button
end
---Adds close button
function rawconfig.gui.CreateCloseButton(config, parent)
local button = GUI.Button(
GUI.RectTransform(Vector2(0.33, 0.05), parent.RectTransform, GUI.Anchor.BottomCenter),
"Discard",
GUI.Alignment.Center,
"GUIButton"
)
button.OnClicked = function()
config:CloseMenu()
end
return button
end
function rawconfig.gui.CloseMenu(config)
config.Frame.RectTransform.Parent = nil
rawconfig.gui.CloseButton = nil
rawconfig.gui.SaveButton = nil
rawconfig.gui.ResetButton = nil
rawconfig.gui.Frame = nil
rawconfig.gui.ListContainer = nil
rawconfig.gui.ControlGroup = nil
for key, entry in pairs(config.Entries) do
entry.gui = nil
end
end
---@param Barotrauma.RectTransform
function rawconfig.gui.PopulateMenu(config)
for key, entry in pairs(config.Entries) do
local EntryGroup = GUI.LayoutGroup(GUI.RectTransform(Vector2(0.98, 0.08), config.ListContainer.Content.RectTransform, GUI.Anchor.TopCenter), true, GUI.Anchor.CenterLeft)
--EntryGroup.RectTransform.AbsoluteOffset = Point(0,100)
--EntryGroup.AbsoluteSpacing = 5
--EntryGroup.Stretch = true
local Lock = config:CreateLock(EntryGroup, key, entry)
if entry.type == "bool" then
config:CreateTickBox(EntryGroup, key, entry)
elseif entry.type == "float" then
end
entry.gui = EntryGroup
end
end
---@param Barotrauma.RectTransform
function rawconfig.gui.CreateLock(config, parent, key, entry)
local rect = GUI.RectTransform(Vector2(0.05, 1), parent.RectTransform)
local toggle = GUI.TickBox(rect, "", nil, "GUILockToggle")
if Game.IsSingleplayer or entry.enforcment == rawconfig.Enforcment.Client then
toggle.Selected = false
toggle.Visible = false
return toggle
end
toggle.Selected = entry.enforced
toggle.Enabled = entry.enforcment == rawconfig.Enforcment.Optional and rawconfig.util.ClientHasAccess(Game.Client)
if toggle.Selected and not toggle.Enabled then
toggle.Box.DisabledColor = Color(255,106,106,255)
end
if entry.description then
toggle.ToolTip = toggle.Selected and LockedStr or UnlockedStr
end
toggle.OnSelected = function()
toggle.ToolTip = toggle.Selected and LockedStr or UnlockedStr
rawconfig.util.Buffer[key].enforced = toggle.Selected
end
return toggle
end
---@param Barotrauma.RectTransform
function rawconfig.gui.CreateTickBox(config, parent, key, entry)
-- toggle
local rect = GUI.RectTransform(Vector2(0.8, 1), parent.RectTransform)
local toggle = GUI.TickBox(rect, entry.name)
if entry.description then
toggle.ToolTip = entry.description
end
toggle.Selected = config:Get(key, entry.default or false)
toggle.Enabled = Game.IsSingleplayer or not entry.enforced or rawconfig.util.ClientHasAccess(Game.Client)
toggle.OnSelected = function()
if not rawconfig.util.Buffer[key] then rawconfig.util.Buffer[key] = {} end
rawconfig.util.Buffer[key].value = toggle.State == GUI.Component.ComponentState.Selected
end
return toggle
end
---@param Barotrauma.RectTransform
function rawconfig.gui.UpdateMenu(config)
for key, entry in pairs(config.Entries) do
if entry.type == "bool" then
local lock = entry.gui.GetChild(Int32(0))
local tickbox = entry.gui.GetChild(Int32(1))
tickbox.Selected = entry.value
tickbox.Enabled = Game.IsSingleplayer or not entry.enforced or rawconfig.util.ClientHasAccess(Game.Client)
lock.Selected = entry.enforced
if lock.Selected and not lock.Enabled then
lock.Box.DisabledColor = Color(255,106,106,255)
end
elseif entry.type == "float" then
end
end
end
--[[
testcfg = {
Name = "testcfg", --Internal name, used for rawconfig.Configs.Name
Label = "pausemenuquitverification", --String displayed in menus, can be localization tag
Entries = {
TST_tickbox = {
name = "pausemenuquitverification",
default = false,
type = "bool",
description = "pausemenuquitverification",
enforcment = rawconfig.Enforcment.Client,
},
TST_field = {
name = "Test name2, can also be a localization tag",
default = false,
type = "bool",
description = "Whatever whatever 2, can also be a localization tag",
--enforced = true,
enforcment = rawconfig.Enforcment.Optional,
},
},
}
rawconfig.addConfig(testcfg)
rawconfig.Configs.testcfg:LoadConfig()
]]
return rawconfig