diff --git a/lua/acf/core/globals.lua b/lua/acf/core/globals.lua index c488b90d..500b18ef 100644 --- a/lua/acf/core/globals.lua +++ b/lua/acf/core/globals.lua @@ -106,6 +106,15 @@ do return Float end end + + function ACF.StringDataCallback() + local SettingData = ACF.GetWorkingSetting() + SettingData.Type = "String" + + return function(_, Value) + return tostring(Value) + end + end end do -- ACF global vars @@ -117,6 +126,8 @@ do -- ACF global vars ACF.ModelData = ACF.ModelData or { Models = {} } -- General Settings + ACF.DefineSetting("SelectedLimitset", "none", "The current limitset has been set to %s.", ACF.StringDataCallback()) + ACF.DefineSetting("AllowAdminData", false, "Admin server data access has been %s.", ACF.BooleanDataCallback()) ACF.DefineSetting("RestrictInfo", true, "Entity information restrictions have been %s.", ACF.BooleanDataCallback()) ACF.DefineSetting("LegalChecks", true, "Legality checks for ACF entities has been %s.", ACF.BooleanDataCallback()) diff --git a/lua/acf/core/networking/data_vars/data_vars_sh.lua b/lua/acf/core/networking/data_vars/data_vars_sh.lua index c9dea97d..860b1e61 100644 --- a/lua/acf/core/networking/data_vars/data_vars_sh.lua +++ b/lua/acf/core/networking/data_vars/data_vars_sh.lua @@ -118,7 +118,7 @@ do -- Data persisting UpdateData(Key) end) - hook.Add("Initialize", "ACF Load Persisted Data", function() + hook.Add("ACF_OnLoadAddon", "ACF Load Persisted Data", function() local Saved = ACF.LoadFromFile(Folder, File) local SetFunction = ACF["Set" .. Realm .. "Data"] @@ -140,7 +140,7 @@ do -- Data persisting end hook.Run("ACF_OnLoadPersistedData") - hook.Remove("Initialize", "ACF Load Persisted Data") + hook.Remove("ACF_OnLoadAddon", "ACF Load Persisted Data") end) end diff --git a/lua/acf/core/networking/data_vars/data_vars_sv.lua b/lua/acf/core/networking/data_vars/data_vars_sv.lua index 5616a31b..bda4c505 100644 --- a/lua/acf/core/networking/data_vars/data_vars_sv.lua +++ b/lua/acf/core/networking/data_vars/data_vars_sv.lua @@ -68,15 +68,18 @@ do -- Data syncronization if not Data then return end - local Hook = "ACF_OnUpdate" .. Type .. "Data" + local HookUpdate = "ACF_OnUpdate" .. Type .. "Data" + local HookUpload = "ACF_OnUpload" .. Type .. "Data" for K, V in pairs(Data) do if Values[K] ~= V then Values[K] = V - hook.Run(Hook, Player, K, V) + hook.Run(HookUpdate, Player, K, V) end + hook.Run(HookUpload, Player, K, V) + Data[K] = nil end end diff --git a/lua/acf/hooks/hooks_sh.lua b/lua/acf/hooks/hooks_sh.lua index 153372b2..f0c72b43 100644 --- a/lua/acf/hooks/hooks_sh.lua +++ b/lua/acf/hooks/hooks_sh.lua @@ -53,6 +53,25 @@ Hooks.Add("ACF_Base_Shared", function(Gamemode) function Gamemode:ACF_OnUpdateClientData() end + --- Called when a server data variable value gets uploaded. Is fucntionally equivalent to ACF_OnUploadServerData, but is called after it and is called regardless if the value was truly changed or not. + --- @param Player entity The player that triggered the server data variable change. + --- On the clientside, if the upload was done by the client then this will always be the local player. + --- On the serverside, if the upload was done by the server then this will always be nil. + --- @param Key string The name of the affected server data variable. + --- @param Value any The new value assigned to the server data variable. + function Gamemode:ACF_OnUploadServerData() + end + + --- Called when a client data variable value gets uploaded. Is fucntionally equivalent to ACF_OnUploadClientData, but is called after it and is called regardless if the value was truly changed or not. + --- On the clientside, this will be called every time the client uploads something to the data var. + --- This means that this hook can be called multiple times on the same tick for the same data variable. + --- On the serverside, this will be called once per tick when the value gets networked. + --- @param Player entity The player that triggered the client data variable change. + --- @param Key string The name of the affected client data variable. + --- @param Value any The new value assigned to the client data variable. + function Gamemode:ACF_OnUploadClientData() + end + --- Called after the Think hook is called. --- The only difference with the Think hook are the convenience arguments provided by this one. --- @param CurTime number Returns the uptime of the server. diff --git a/lua/acf/limitsets/limitset.lua b/lua/acf/limitsets/limitset.lua new file mode 100644 index 00000000..c81ac838 --- /dev/null +++ b/lua/acf/limitsets/limitset.lua @@ -0,0 +1,91 @@ +local OldRegistered = ACF.LimitSets and ACF.LimitSets.Registered or nil + +local LimitSets = { + Registered = OldRegistered or {}, + __PostInitLimitSet = false +} +ACF.LimitSets = LimitSets + +if SERVER then + LimitSets.acf_has_limitset_notice_been_shown = CreateConVar("__acf_has_limitset_notice_been_shown", 0, FCVAR_ARCHIVE + FCVAR_REPLICATED + FCVAR_UNREGISTERED, "Internal limitset flag", 0, 1) +end + +function LimitSets.Create(Name) + local Object = { + ServerData = {} + } + + LimitSets.Registered[Name] = Object + + Object.Name = Name + + function Object:WithAuthor(Author) self.Author = Author end + function Object:WithDescription(Description) self.Description = Description end + + function Object:SetServerData(Key, Value) + self.ServerData[Key] = Value + end + + -- This allows hot-reloading + if Name == ACF.ServerData.SelectedLimitset and LimitSets.__PostInitLimitSet then + timer.Simple(0, function() + LimitSets.Execute(Name) + end) + end + + return Object +end + +function LimitSets.Get(Name) + return LimitSets.Registered[Name] +end + +function LimitSets.GetAll() + return table.GetKeys(LimitSets.Registered) +end + +function LimitSets.Execute(Name) + if CLIENT then return end + if Name == "none" then + + return true, "OK" + end + if not isstring(Name) then return false, "Argument #1 (Name) must be a string." end + + local LimitSet = LimitSets.Registered[Name] + if not LimitSet then return false, "No limitset with the name '" .. Name .. "'." end + + ACF.Utilities.Messages.PrintLog("Info", "Loading the limitset '" .. Name .. "' by " .. (LimitSet.Author or "") .. "...") + + for Key, Value in pairs(LimitSet.ServerData) do + ACF.SetServerData(Key, Value) + end + + return true, "OK" +end + +local function UpdateLimitSet() + local SelectedLimitsetName = ACF.ServerData.SelectedLimitset or "none" + local SelectedLimitset = ACF.LimitSets.Registered[SelectedLimitsetName] + + if SelectedLimitset then + LimitSets.Execute(SelectedLimitsetName) + else + ACF.Utilities.Messages.PrintLog("Info", "No limitset loaded.") + end + + ACF.LimitSets.__PostInitLimitSet = true +end + +hook.Add("ACF_OnLoadPersistedData", "ACF_LimitSets_Setup", function() + if CLIENT then return end + + UpdateLimitSet() + + hook.Add("ACF_OnUploadServerData", "ACF_LimitSets_WatchForKey", function(_, Key, _) + if Key ~= "SelectedLimitset" then return end + UpdateLimitSet() + + LimitSets.acf_has_limitset_notice_been_shown:SetBool(true) + end) +end) \ No newline at end of file diff --git a/lua/acf/limitsets/limitset_cl.lua b/lua/acf/limitsets/limitset_cl.lua new file mode 100644 index 00000000..7e5b8ec5 --- /dev/null +++ b/lua/acf/limitsets/limitset_cl.lua @@ -0,0 +1,248 @@ +ACF.LimitSets = ACF.LimitSets or {} + +local acf_has_limitset_notice_been_shown = CreateConVar("__acf_has_limitset_notice_been_shown", 0, FCVAR_REPLICATED + FCVAR_UNREGISTERED, "Internal limitset flag", 0, 1) + +surface.CreateFont("ACF_LimitsetsNotice_Font1", { + font = "Tahoma", + size = 16, + antialias = true, + weight = 600 +}) + +surface.CreateFont("ACF_LimitsetsNotice_Font2", { + font = "Tahoma", + size = 42, + antialias = true, + weight = 900 +}) + +surface.CreateFont("ACF_LimitsetsNotice_Font3", { + font = "Tahoma", + size = 24, + antialias = true, + italic = true, + weight = 200 +}) + +surface.CreateFont("ACF_LimitsetsNotice_Font4", { + font = "Tahoma", + size = 16, + antialias = true, + weight = 600 +}) + +surface.CreateFont("ACF_LimitsetsNotice_Font5", { + font = "Tahoma", + size = 13, + antialias = true, + weight = 600 +}) + +surface.CreateFont("ACF_LimitsetsNotice_Font6", { + font = "Tahoma", + size = 26, + antialias = true, + weight = 600 +}) + +local function ShowLimitsetNotice(Bypass) + if acf_has_limitset_notice_been_shown:GetBool() and not Bypass then return end + if not ACF.CanSetServerData(LocalPlayer()) then return end + + if IsValid(ACF.LimitSets.NoticePanel) then ACF.LimitSets.NoticePanel:Remove() end + + local Frame = vgui.Create("DFrame") + Frame:SetIcon("icon16/cog_edit.png") + ACF.LimitSets.NoticePanel = Frame + + Frame:SetSize(640, ScrH() * .85) + Frame:Center() + Frame:MakePopup() + Frame:SetSizable(true) + Frame:SetTitle("ACF - Limitsets Notice & Selector") + + local Back = Frame:Add("DScrollPanel") + Back:SetSize(0, 400) + Back:DockMargin(8, 8, 8, 8) + Back:Dock(TOP) + Back:SetPaintBackground(true) + + local Warn = Back:Add "DLabel" + Warn:SetFont("ACF_LimitsetsNotice_Font2") + Warn:Dock(TOP) + Warn:SetContentAlignment(5) + Warn:SetText("Important - Please Read!") + Warn:SetSize(0, 72) + Warn:SetColor(color_black) + + local Contents = Back:Add "DLabel" + Contents:SetFont("ACF_LimitsetsNotice_Font1") + local CurrentLimitset = ACF.GetServerData("SelectedLimitset") + Contents:SetText("We are introducing a new system into ACF, called 'Limitsets'.\n\nThese are a collection of server-side settings overrides that will automatically update as the addon develops.\n\nWe created this system to provide a curated list of server settings for both sandbox and combat playstyles.\n\nThe current limitset has been set to \"" .. CurrentLimitset .. "\".\n\nPlease choose a limitset that looks appropriate for your server.\nIf neither will work for you, select \"Custom\" to opt-out of the system. Settings will not automatically update.\n\nWe highly recommend Combat unless you're a more creative-building oriented server. Combat implements the most checks & balances, which makes PvP fair and reduces the need for human moderation during combat.\n\nYou can open this menu at any time with the concommand 'acf_select_limitset'.") + Contents:SetWrap(true) + Contents:Dock(TOP) + Contents:SetContentAlignment(7) + Contents:SetColor(Color(0, 0, 0)) + Contents:DockMargin(8, 8, 8, 8) + function Contents:PerformLayout() + Contents:SizeToContentsY() + end + + local SelectLimitsetPanel = Frame:Add("DListView") + SelectLimitsetPanel:Dock(TOP) + SelectLimitsetPanel:SetSize(0, 100) + SelectLimitsetPanel:DockMargin(8, 4, 8, 4) + SelectLimitsetPanel:AddColumn("Limitset Name") + + local InformationSheets = Frame:Add("DPropertySheet") + InformationSheets:Dock(FILL) + InformationSheets:DockMargin(8, 6, 8, 8) + + local ShowSelectedLimitsetPanel = InformationSheets:AddSheet("Limitset Information", vgui.Create("DScrollPanel"), "icon16/information.png", false, true).Panel + ShowSelectedLimitsetPanel:Dock(FILL) + ShowSelectedLimitsetPanel:SetPaintBackground(true) + ShowSelectedLimitsetPanel:DockMargin(4, -4, 4, 4) + + local ShowSelectedLimitsetSettings = InformationSheets:AddSheet("Changed Settings", vgui.Create("DPanel"), "icon16/cog_edit.png", false, true).Panel + ShowSelectedLimitsetSettings:Dock(FILL) + ShowSelectedLimitsetSettings:DockMargin(4, -4, 4, 4) + + local SetTo = Frame:Add("DButton") + SetTo:Dock(BOTTOM) + SetTo:SetSize(0, 64) + SetTo:DockMargin(8, 0, 8, 8) + SetTo:SetFont("ACF_LimitsetsNotice_Font6") + + local Right = Material("icon16/arrow_right.png", "mips smooth") + + SetTo.PaintColor = Color(129, 179, 255) + function SetTo:Paint(w, h) + self.PaintColor.a = ((math.sin(CurTime() * 6) + 1) / 2) * 150 + DButton.Paint(self, w, h) + local Skin = self:GetSkin() + if not self.m_bBackground then return end + + if self.Depressed or self:IsSelected() or self:GetToggle() then + return Skin.tex.Button_Down(0, 0, w, h, self.PaintColor) + end + + if self:GetDisabled() then + return Skin.tex.Button_Dead(0, 0, w, h, self.PaintColor) + end + + if self.Hovered then + return Skin.tex.Button_Hovered( 0, 0, w, h, self.PaintColor) + end + + Skin.tex.Button(0, 0, w, h, self.PaintColor) + end + + local CurDesc + function SelectLimitsetPanel:OnRowSelected(_, Row) + SetTo:SetText(Row.LimitSet and ("Choose the " .. Row.LimitSet.Name .. " limitset") or "Choose no limitset") + + ShowSelectedLimitsetPanel:Clear() + ShowSelectedLimitsetSettings:Clear() + + local Title = ShowSelectedLimitsetPanel:Add("DLabel") + Title:Dock(TOP) + Title:DockMargin(8, 8, 8, 8) + Title:SetColor(color_black) + Title:SetSize(0, 38) + Title:SetContentAlignment(1) + Title:SetFont("ACF_LimitsetsNotice_Font2") + Title:SetText(Row.LimitSet and Row.LimitSet.Name or "None") + + if Row.LimitSet and Row.LimitSet.Author then + local Author = ShowSelectedLimitsetPanel:Add("DLabel") + Author:SetColor(color_black) + Author:DockMargin(16, -42, 8, 16) + Author:SetSize(0, 24) + Author:SetContentAlignment(9) + Author:Dock(TOP) + Author:SetFont("ACF_LimitsetsNotice_Font3") + Author:SetText("by " .. Row.LimitSet.Author) + end + + local Desc = ShowSelectedLimitsetPanel:Add("DLabel") + CurDesc = Desc + Desc:SetColor(color_black) + Desc:DockMargin(24, 2, 24, 2) + Desc:SetContentAlignment(7) + Desc:SetFont("ACF_LimitsetsNotice_Font4") + Desc:SetText(Row.LimitSet and (Row.LimitSet.Description or "No description provided.") or "Don't select a limitset. This will keep your current server settings intact.\n\nIf you'd like to use a limitset at the base for your server, but then tweak limits/restrictions and have them persist, select a limitset from the list, choose it, then go back to this menu and choose \"Custom\".\n\nThis will override the settings that the limitset defined, but allow you to change values afterward and have them persist.") + Desc:SetWrap(true) + Desc:Dock(TOP) + + local SettingsChanged = ShowSelectedLimitsetSettings:Add("DListView") + SettingsChanged:Dock(FILL) + SettingsChanged:DockMargin(8, 4, 8, 4) + if Row.LimitSet and next(Row.LimitSet.ServerData) then + SettingsChanged:AddColumn("Setting Name") + SettingsChanged:AddColumn("Current Value") + SettingsChanged:AddColumn("Will Be Changed To") + + for Key, Value in SortedPairs(Row.LimitSet.ServerData, true) do + local Old = ACF.GetServerData(Key) + local Changed = Old ~= Value + + local Phrase = language.GetPhrase(("acf.globals.%s"):format(Key:lower())) + local Line = SettingsChanged:AddLine(Phrase, Old, Value) + for _, v in ipairs(Line.Columns) do + v:SetFont("ACF_LimitsetsNotice_Font5") + end + local oldPaint = Line.Paint + + function Line:Paint(w, h) + oldPaint(self, w, h) + if Changed then + surface.SetMaterial(Right) + surface.SetDrawColor(255, 255, 255, 225) + surface.DrawTexturedRectRotated(((w / 3) * 2) - 24, h / 2, 24, 24, 0) + end + end + end + else + function SettingsChanged:Paint(w, h) + draw.SimpleText("No settings will be changed.", "ACF_LimitsetsNotice_Font2", w / 2, h / 2, color_black, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + end + + for _, Name in ipairs(ACF.LimitSets.GetAll()) do + local LimitSet = ACF.LimitSets.Get(Name) + local Line = SelectLimitsetPanel:AddLine(LimitSet.Name) + Line.LimitSet = LimitSet + + if Name == "Combat" then + SelectLimitsetPanel:SelectItem(Line) + end + + end + SelectLimitsetPanel:AddLine("Custom") + + function SetTo:DoClick() + local _, Selected = SelectLimitsetPanel:GetSelectedLine() + ACF.SetServerData("SelectedLimitset", Selected.LimitSet and Selected.LimitSet.Name or "none", true) + Frame:Close() + end + + local OldFrameLayout = Frame.PerformLayout + function Frame:PerformLayout(w, h) + OldFrameLayout(self, w, h) + Back:SetSize(0, h * .43) + SelectLimitsetPanel:SetSize(0, h * .11) + if IsValid(CurDesc) then + CurDesc:SizeToContentsY() + end + end +end + +hook.Add("StartCommand", "ACF_Limitsets_Postent", function() + ShowLimitsetNotice() + hook.Remove("StartCommand", "ACF_Limitsets_Postent") +end) + +concommand.Add("acf_select_limitset", function() + ShowLimitsetNotice(true) +end) \ No newline at end of file diff --git a/lua/acf/limitsets/limitsets/acf_combat.lua b/lua/acf/limitsets/limitsets/acf_combat.lua new file mode 100644 index 00000000..ecdd909f --- /dev/null +++ b/lua/acf/limitsets/limitsets/acf_combat.lua @@ -0,0 +1,15 @@ +local Combat = ACF.LimitSets.Create("Combat") + Combat:WithAuthor("ACF Team") + Combat:WithDescription("The default mode for ACF combat, with curated combat settings by the developers. Recommended for PvP servers.\n\nCombat is easier for server moderation, as it implements the most checks & balances within ACF, and also makes ACF PvP a much more fair experience.\n\nWe recommend that all servers try Combat and/or base their settings off of it. If your server is more creative-building oriented, and Combat doesn't work for you, you can try Sandbox instead.") + Combat:SetServerData("LegalChecks", true) + Combat:SetServerData("NameAndShame", true) + Combat:SetServerData("VehicleLegalChecks", true) + -- Combat:SetServerData("LinkDistance", 250) + -- Combat:SetServerData("MobilityLinkDistance", 350) + Combat:SetServerData("RequireFuel", true) + Combat:SetServerData("GunsCanFire", true) + Combat:SetServerData("RacksCanFire", true) + Combat:SetServerData("HEPush", true) + Combat:SetServerData("KEPush", true) + Combat:SetServerData("RecoilPush", true) + Combat:SetServerData("AllowProcArmor", false) \ No newline at end of file diff --git a/lua/acf/limitsets/limitsets/acf_sandbox.lua b/lua/acf/limitsets/limitsets/acf_sandbox.lua new file mode 100644 index 00000000..5ea0aae2 --- /dev/null +++ b/lua/acf/limitsets/limitsets/acf_sandbox.lua @@ -0,0 +1,8 @@ +local Sandbox = ACF.LimitSets.Create("Sandbox") + Sandbox:WithAuthor("ACF Team") + Sandbox:WithDescription("Most major restrictions are disabled, alongside some parameter buffs. Good for building/casual servers or high-trust environments.\n\nWe don't recommend this for PvP servers, as this mode aims to provide more freedom for builders, and therefore disables a lot of the exploit checks Combat provides and will make ACF moderation harder for staff.\n\n") + Sandbox:SetServerData("LegalChecks", true) + Sandbox:SetServerData("NameAndShame", false) + Sandbox:SetServerData("VehicleLegalChecks", false) + -- Sandbox:SetServerData("LinkDistance", 650) + -- Sandbox:SetServerData("MobilityLinkDistance", 650) \ No newline at end of file diff --git a/lua/acf/menu/items_cl/settings.lua b/lua/acf/menu/items_cl/settings.lua index 297dab77..a3e28825 100644 --- a/lua/acf/menu/items_cl/settings.lua +++ b/lua/acf/menu/items_cl/settings.lua @@ -139,8 +139,10 @@ do -- Serverside settings ACF.AddMenuItem(101, "#acf.menu.settings", "#acf.menu.settings.server", "server", ACF.GenerateServerSettings) ACF.AddServerSettings(1, "#acf.menu.settings.general", function(Base) + Base:AddLabel(language.GetPhrase("acf.menu.settings.general.current_limitset_set_to"):format(ACF.GetServerData("SelectedLimitset") or "")) + local ShowLimitset = Base:AddButton("#acf.menu.settings.general.show_limitset_menu") + ShowLimitset.DoClick = function() concommand.Run(LocalPlayer(), "acf_select_limitset", "") end local Admins = Base:AddCheckBox("#acf.menu.settings.general.allow_admin") - -- What? Why is this different?? Admins:SetServerData("ServerDataAllowAdmin", "OnChange") Admins:DefineSetter(function(Panel, _, _, Value) diff --git a/resource/localization/en/acf_menu_settings.properties b/resource/localization/en/acf_menu_settings.properties index 3b3303fe..47fc968f 100644 --- a/resource/localization/en/acf_menu_settings.properties +++ b/resource/localization/en/acf_menu_settings.properties @@ -79,6 +79,10 @@ acf.menu.settings.tool_category.option_desc=You will need to rejoin the server f # General Settings category acf.menu.settings.general=General Settings +acf.menu.settings.general.current_limitset_set_to=The current limitset is set to %s. +acf.menu.settings.general.show_limitset_menu=Show Limitset Menu + + acf.menu.settings.general.allow_admin=Allow admins to control server data. acf.menu.settings.general.allow_admin_desc=If enabled, admins will be able to mess with the settings on this panel. diff --git a/resource/localization/en/acf_settings.properties b/resource/localization/en/acf_settings.properties new file mode 100644 index 00000000..2939a393 --- /dev/null +++ b/resource/localization/en/acf_settings.properties @@ -0,0 +1,52 @@ +# Globals names. Some are left out; I only added the ones that might turn into settings at some point or are currently settings +acf.globals.selectedlimitset = Selected limitset. +acf.globals.allowadmindata = Allow admins to control server data +acf.globals.restrictinfo = Restrict entity information +acf.globals.legalchecks = Enable legality checks +acf.globals.nameandshame = Call out players if legality checks fail +acf.globals.vehiclelegalchecks = Enable vehicle legality checks +acf.globals.gunscanfire = Can guns fire? +acf.globals.gunscansmoke = Can guns produce particles? +acf.globals.rackscanfire = Can missile racks fire? +acf.globals.requirefuel = Engines require fuel? +acf.globals.healthfactor = Prop health factor +acf.globals.armorfactor = Prop armor factor +acf.globals.fuelfactor = Engine fuel-consumption factor +acf.globals.maxthickness = Maximum armor thickness +acf.globals.hepush = Enable explosive entity pushing? +acf.globals.kepush = Enable kinectic entity pushing? +acf.globals.recoilpush = Enable recoil entity pushing? +acf.globals.allowfunents = Allow fun entities? +acf.globals.allowprocarmor = Allow procedural armor? +acf.globals.workshopcontent = Enable workshop content download? +acf.globals.workshopextras = Enable extra workshop content download? +acf.globals.createdebris = Network debris to clients? +acf.globals.createfireballs = Create serverside debris fireballs? +acf.globals.fireballmult = Serverside debris fireball multiplier +acf.globals.minimumarmor = Minimum armor thickness +acf.globals.maximumarmor = Maximum armor thickness +acf.globals.illegaldisabletime = Seconds for an entity to be disabled when illegal +acf.globals.volume = Global ACF volume factor +acf.globals.smokewind = Is smoke affected by wind? +acf.globals.mobilitylinkdistance = Link distance between mobility components +acf.globals.linkdistance = Link distance between ACF components +acf.globals.minfuzecaliber = Minimum fuze caliber +acf.globals.basereload = Reload time factor +acf.globals.scale = Global ACF scale +acf.globals.gravity = Global ACF gravity +acf.globals.ammoarmor = Millimeters of armor on ammo crates +acf.globals.ammopadding = Wasted space to projectile case ratio +acf.globals.ammocasescale = Size of ammo case vs. the projectile +acf.globals.ammominsize = Minimum ammo crate dimensions in all axises +acf.globals.ammomaxsize = Maximum ammo crate dimensions in all axises +acf.globals.propimpetus = Energy (kJ) produced by 1 kilogram of propellant +acf.globals.pdensity = Propellant loading density +acf.globals.spreadscale = Spread scale factor +acf.globals.guninaccuracyscale = Gun inaccuracy scale multiplier +acf.globals.guninaccuracybias = Gun inaccuracy bias factor (higher -> more inaccurate) +acf.globals.fuelminsize = Minimum fuel tank dimensions in all axises +acf.globals.fuelmaxsize = Minimum fuel tank dimensions in all axises +acf.globals.fuelarmor = Millimeters of armor on fuel tanks +acf.globals.refilldistance = Maximum refilling distance +acf.globals.refillspeed = Refilling speed +acf.globals.refuelspeed = Refueling speed