514 lines
No EOL
14 KiB
Lua
514 lines
No EOL
14 KiB
Lua
--[[
|
|
© 2017 Thriving Ventures Limited do not share, re-distribute or modify
|
|
|
|
without permission of its author (gustaf@thrivingventures.com).
|
|
]]
|
|
|
|
--- ## Shared
|
|
-- Simplifies the saving/loading/networking of configuration options. Files will be saved in `data/serverguard/config/<name>.txt`.
|
|
|
|
-- @module serverguard.config
|
|
|
|
serverguard.AddFolder("config");
|
|
|
|
serverguard.config = serverguard.config or {};
|
|
|
|
local stored = {}
|
|
|
|
--- Configuration class object that's used to save, load, and network settings to clients.
|
|
-- @type CONFIG_CLASS
|
|
local CONFIG_CLASS = {};
|
|
CONFIG_CLASS.__index = CONFIG_CLASS;
|
|
CONFIG_CLASS.__tostring = function(self)
|
|
local result = "Config class \"" .. self.name .. "\":\n";
|
|
|
|
for k, v in pairs(self.entries) do
|
|
result = result .. "\t" .. k .. "\t(" .. v.type .. ")\t-> " .. tostring(v.value) .. "\n";
|
|
end;
|
|
|
|
return result;
|
|
end;
|
|
|
|
local CONFIG_TYPES = {
|
|
--- Adds a configuration option whose key is a boolean.
|
|
-- @string key The key it's referenced by.
|
|
-- @string value The default value that'll be set if none exists.
|
|
-- @string description[opt] The description of the option.
|
|
-- @usage config:AddBoolean("testbool", true, "Just a test boolean.");
|
|
Boolean = function(value)
|
|
return tobool(value);
|
|
end,
|
|
|
|
--- Adds a configuration option whose key is a number.
|
|
-- @string key The key it's referenced by.
|
|
-- @string value The default value that'll be set if none exists.
|
|
-- @string description[opt] The description of the option.
|
|
-- @usage config:AddNumber("testnumber", 1234, "Numbers are great!");
|
|
Number = function(value)
|
|
return util.ToNumber(value);
|
|
end;
|
|
|
|
--- Adds a configuration option whose key is a string.
|
|
-- @string key The key it's referenced by.
|
|
-- @string value The default value that'll be set if none exists.
|
|
-- @string description[opt] The description of the option.
|
|
-- @usage config:AddString("teststring", "hello hello hello!", "Strings are pretty cool too.");
|
|
String = function(value)
|
|
return tostring(value);
|
|
end;
|
|
};
|
|
|
|
--- **This is called internally, use the create method.**
|
|
-- Creates a new configuration object with the specified name.
|
|
-- This name is used as both a reference and the name of the file it's saved to. As such, ensure that the name is something the OS will like.
|
|
-- @see serverguard.config.Create
|
|
-- @string name The name of the config object.
|
|
-- @return New config object.
|
|
function CONFIG_CLASS:New(name)
|
|
local object = setmetatable({}, CONFIG_CLASS);
|
|
object.name = name;
|
|
object.entries = {};
|
|
object.autoSave = true;
|
|
object.saveQueued = false;
|
|
object.loaded = false;
|
|
object.permissions = {};
|
|
return object;
|
|
end;
|
|
|
|
for k, v in pairs(CONFIG_TYPES) do
|
|
CONFIG_CLASS["Add" .. k] = function(self, key, default, description)
|
|
self.entries[key] = {
|
|
type = k,
|
|
value = v(default),
|
|
description = tostring(description or ""),
|
|
boundPanels = {},
|
|
callbacks = {}
|
|
};
|
|
end;
|
|
end;
|
|
|
|
--- Sets a configuration value and networks it to clients.
|
|
-- @string key The key of the config option.
|
|
-- @string value The value to set it to.
|
|
-- @bool bDontNetwork[opt] Whether or not to network it to clients.
|
|
function CONFIG_CLASS:SetValue(key, value, bDontNetwork)
|
|
if (!self.entries[key]) then
|
|
ErrorNoHalt("[config] Tried to set non-existent key \"" .. key .. "\"!");
|
|
return;
|
|
end;
|
|
|
|
local newValue = CONFIG_TYPES[self.entries[key].type](value);
|
|
self.entries[key].value = newValue;
|
|
self.loaded = true;
|
|
|
|
for k, v in pairs(self.entries[key].callbacks) do
|
|
v(newValue);
|
|
end;
|
|
|
|
if (SERVER) then
|
|
if (self.autoSave) then
|
|
self.saveQueued = true;
|
|
end;
|
|
else
|
|
for k, v in pairs(self.entries[key].boundPanels) do
|
|
v:OnBoundConfigChanged(newValue);
|
|
end;
|
|
end;
|
|
|
|
if (bDontNetwork) then
|
|
return;
|
|
end;
|
|
|
|
if (SERVER) then
|
|
serverguard.netstream.Start(nil, "sgConfigUpdate", {
|
|
name = self.name,
|
|
key = key,
|
|
type = self.entries[key].type,
|
|
value = newValue
|
|
});
|
|
else
|
|
serverguard.netstream.Start("sgConfigUpdate", {
|
|
name = self.name,
|
|
key = key,
|
|
type = self.entries[key].type,
|
|
value = newValue
|
|
});
|
|
end;
|
|
end;
|
|
|
|
--- Sets a required permission to set values from the client.
|
|
-- @string permission The required permission. Can be a table of permissions.
|
|
function CONFIG_CLASS:SetPermission(permission)
|
|
if (type(permission) != "table") then
|
|
self.permissions = {tostring(permission)};
|
|
else
|
|
self.permissions = permission;
|
|
end;
|
|
end;
|
|
|
|
--- Checks whether or not a player has permission to set the value.
|
|
-- @player player The player to check permissions for.
|
|
-- @return bool Whether or not they have permission.
|
|
function CONFIG_CLASS:CheckPermission(player)
|
|
if (#self.permissions < 1) then
|
|
return true;
|
|
end;
|
|
|
|
return serverguard.player:HasPermission(player, self.permissions);
|
|
end;
|
|
|
|
--- Sets whether or not the configuration object auto-saves.
|
|
-- When set, it will be automatically saved when you set a value.
|
|
-- @bool value Whether or not to auto-save.
|
|
function CONFIG_CLASS:SetAutoSave(value)
|
|
self.autoSave = tobool(value);
|
|
end;
|
|
|
|
--- Adds a callback function to run when the specified key has been changed.
|
|
-- @string key The key to monitor for changes.
|
|
-- @func callback The function to call when the value changes.
|
|
function CONFIG_CLASS:AddCallback(key, callback)
|
|
if (!self.entries[key]) then
|
|
return;
|
|
end;
|
|
|
|
table.insert(self.entries[key].callbacks, callback);
|
|
end;
|
|
|
|
--- Gets the description of the configuration option.
|
|
-- @string key The key of the configuration option.
|
|
-- @treturn string The description.
|
|
function CONFIG_CLASS:GetDescription(key)
|
|
if (!self.entries[key] or !self.entries[key].description) then
|
|
return;
|
|
end;
|
|
|
|
return self.entries[key].description;
|
|
end;
|
|
|
|
--- Gets the serialized representation of the configuration object.
|
|
-- @treturn string The serialized object.
|
|
function CONFIG_CLASS:GetSerialized()
|
|
local data = {};
|
|
|
|
for k, v in pairs(self.entries) do
|
|
data[k] = {
|
|
type = v.type,
|
|
value = v.value
|
|
};
|
|
end;
|
|
|
|
return serverguard.von.serialize(data);
|
|
end;
|
|
|
|
--- Gets the value assigned to the given key. Returns nil if the key doesn't exist.
|
|
-- @string key The key to get the value from.
|
|
-- @return The value of the key.
|
|
function CONFIG_CLASS:GetValue(key)
|
|
if (!self.entries[key]) then
|
|
return nil;
|
|
end;
|
|
|
|
return self.entries[key].value;
|
|
end;
|
|
|
|
--- Gets a key -> value table of all the configuration options.
|
|
-- @treturn table Table of options.
|
|
function CONFIG_CLASS:GetKeyValues()
|
|
local data = {};
|
|
|
|
for k, v in pairs(self.entries) do
|
|
data[k] = v.value;
|
|
end;
|
|
|
|
return data;
|
|
end;
|
|
|
|
--- Returns the raw entry table for the configuration object.
|
|
-- @treturn table Configuration table.
|
|
function CONFIG_CLASS:GetTable()
|
|
return self.entries;
|
|
end;
|
|
|
|
--- Loads the saved configuration options from disk.
|
|
-- Requests a full update from the server if ran on the client.
|
|
-- @treturn CONFIG_CLASS The configuration object.
|
|
function CONFIG_CLASS:Load()
|
|
if (SERVER) then
|
|
if (file.Exists("serverguard/config/" .. self.name .. ".txt", "DATA")) then
|
|
local data = serverguard.von.deserialize(file.Read("serverguard/config/" .. self.name .. ".txt", "DATA"));
|
|
|
|
for k, v in pairs(data) do
|
|
if (!self.entries[k]) then
|
|
continue;
|
|
end;
|
|
|
|
self:SetValue(k, v.value, true);
|
|
end;
|
|
|
|
self.loaded = true;
|
|
else
|
|
self:Save(true);
|
|
end;
|
|
end;
|
|
|
|
if (CLIENT) then
|
|
timer.Simple(1, function()
|
|
self:RequestFullUpdate();
|
|
end);
|
|
end;
|
|
|
|
return self;
|
|
end;
|
|
|
|
if (SERVER) then
|
|
local lastSaveTime = CurTime();
|
|
local fullRequestQueue = {};
|
|
|
|
local function SaveConfigClass(object)
|
|
file.Write("serverguard/config/" .. object.name .. ".txt", object:GetSerialized());
|
|
object.saveQueued = false;
|
|
end;
|
|
|
|
--- **(SERVERSIDE)** Queues the configuration object to be saved to disk.
|
|
-- @bool bForce[opt] Whether or not to bypass the queue and save immediately to disk. Not recommended.
|
|
function CONFIG_CLASS:Save(bForce)
|
|
if (bForce) then
|
|
SaveConfigClass(self);
|
|
return;
|
|
end;
|
|
|
|
self.saveQueued = true;
|
|
end;
|
|
|
|
-- **(SERVERSIDE)** Sends a full update of all configuration options to the specified player(s).
|
|
-- @player player The player to send the update to. Can also be a table of players.
|
|
function CONFIG_CLASS:SendFullUpdate(player)
|
|
local data = {
|
|
name = self.name,
|
|
entries = {}
|
|
};
|
|
|
|
for k, v in pairs(self.entries) do
|
|
data.entries[k] = v.value;
|
|
end;
|
|
|
|
serverguard.netstream.Start(player, "sgReceiveFullUpdate", data);
|
|
end;
|
|
|
|
hook.Add("Think", "serverguard.config.Think", function()
|
|
local bSaved = false;
|
|
|
|
for k, v in pairs(stored) do
|
|
if (v.loaded and fullRequestQueue[k] and #fullRequestQueue > 0) then
|
|
v:SendFullUpdate(fullRequestQueue[k]);
|
|
|
|
fullRequestQueue[k] = nil;
|
|
end;
|
|
|
|
if (v.loaded and CurTime() >= lastSaveTime + 25) then
|
|
if (!v.entries or !v.saveQueued) then
|
|
continue;
|
|
end;
|
|
|
|
SaveConfigClass(v);
|
|
bSaved = true;
|
|
end;
|
|
end;
|
|
|
|
if (bSaved) then
|
|
lastSaveTime = CurTime();
|
|
end;
|
|
end);
|
|
|
|
serverguard.netstream.Hook("sgConfigUpdate", function(player, data)
|
|
if (!stored[data.name] or !stored[data.name]:CheckPermission(player)) then
|
|
return;
|
|
end;
|
|
|
|
stored[data.name]:SetValue(data.key, data.value, true);
|
|
stored[data.name]:Save();
|
|
end);
|
|
|
|
serverguard.netstream.Hook("sgRequestFullUpdate", function(player, data)
|
|
if (!stored[data.name]) then
|
|
return;
|
|
end;
|
|
|
|
if (!stored[data.name].loaded) then
|
|
if (!fullRequestQueue[data.name]) then
|
|
fullRequestQueue[data.name] = {};
|
|
end;
|
|
|
|
table.insert(fullRequestQueue[data.name], player);
|
|
return;
|
|
end;
|
|
|
|
stored[data.name]:SendFullUpdate(player);
|
|
end);
|
|
else
|
|
local panelSetup = false;
|
|
|
|
--- **(CLIENTSIDE)** Requests a full update of all configuration values from the server.
|
|
-- @see CONFIG_CLASS:Load
|
|
function CONFIG_CLASS:RequestFullUpdate()
|
|
serverguard.netstream.Start("sgRequestFullUpdate", {
|
|
name = self.name
|
|
});
|
|
end;
|
|
|
|
serverguard.netstream.Hook("sgConfigUpdate", function(data)
|
|
if (!stored[data.name]) then
|
|
return;
|
|
end;
|
|
|
|
stored[data.name]:SetValue(data.key, data.value, true);
|
|
end);
|
|
|
|
serverguard.netstream.Hook("sgReceiveFullUpdate", function(data)
|
|
if (!stored[data.name]) then
|
|
return;
|
|
end;
|
|
|
|
for k, v in pairs(data.entries) do
|
|
stored[data.name]:SetValue(k, v, true);
|
|
stored[data.name].loaded = true; -- client doesn't really care
|
|
end;
|
|
end);
|
|
|
|
hook.Add("PostGamemodeLoaded", "serverguard.config.PostGamemodeLoaded", function()
|
|
if (panelSetup) then
|
|
return;
|
|
end;
|
|
|
|
local DCheckBox = vgui.GetControlTable("DCheckBox");
|
|
local DNumSlider = vgui.GetControlTable("DNumSlider");
|
|
|
|
local tiger_checkbox = vgui.GetControlTable("tiger.checkbox");
|
|
local tiger_numslider = vgui.GetControlTable("tiger.numslider");
|
|
|
|
local checkboxFunction = function(self, config, key)
|
|
if (!stored[config] or
|
|
!stored[config].entries[key] or
|
|
!stored[config].entries[key].type == "Boolean") then
|
|
return;
|
|
end;
|
|
|
|
local configEntry = stored[config].entries[key];
|
|
|
|
self:SetChecked(configEntry.value);
|
|
self.sgOldOnChange = self.OnChange;
|
|
|
|
self.OnChange = function(_self, value)
|
|
stored[config]:SetValue(key, value);
|
|
|
|
self.sgOldOnChange(self, value);
|
|
end;
|
|
|
|
self.OnBoundConfigChanged = function(_self, value)
|
|
self:SetChecked(value);
|
|
end;
|
|
|
|
table.insert(configEntry.boundPanels, self);
|
|
end;
|
|
|
|
local sliderFunction = function(self, config, key)
|
|
if (!stored[config] or
|
|
!stored[config].entries[key] or
|
|
!stored[config].entries[key].type == "Number") then
|
|
return;
|
|
end;
|
|
|
|
local configEntry = stored[config].entries[key];
|
|
|
|
self:SetValue(configEntry.value);
|
|
self.sgOldValueChanged = self.ValueChanged;
|
|
|
|
self.ValueChanged = function(_self, value)
|
|
stored[config]:SetValue(key, (bRound and math.Round(value)) or value);
|
|
|
|
self.sgOldValueChanged(self, value);
|
|
end;
|
|
|
|
self.OnBoundConfigChanged = function(_self, value)
|
|
self:SetValue(value);
|
|
end;
|
|
|
|
table.insert(configEntry.boundPanels, self);
|
|
end;
|
|
|
|
if (DCheckBox) then
|
|
--- Binds a panel to a configuration option.
|
|
-- It will update the panel when the configuration option is updated, and vice versa.
|
|
-- This overrides OnChange/ValueChanged/etc for panels, so make sure you call this last! It will retain previous callbacks when overriding.
|
|
-- Applicable panels include: DCheckBox, DNumSlider, tiger.checkbox, tiger.numslider.
|
|
-- @string config The name of the configuration object.
|
|
-- @string key The configuration option to bind the panel to.
|
|
-- @bool bRound[opt] **DNumSlider only:** Whether or not to round the value before updating.
|
|
DCheckBox.BindToConfig = checkboxFunction;
|
|
end;
|
|
|
|
if (tiger_checkbox) then
|
|
tiger_checkbox.BindToConfig = checkboxFunction;
|
|
end;
|
|
|
|
if (DNumSlider) then
|
|
DNumSlider.BindToConfig = sliderFunction;
|
|
end;
|
|
|
|
if (tiger_numslider) then
|
|
tiger_numslider.BindToConfig = sliderFunction;
|
|
end;
|
|
|
|
panelSetup = true;
|
|
end);
|
|
end;
|
|
|
|
--- Creates a new configuration object with the specified name.
|
|
-- @string unique The name of the config object.
|
|
-- @return New config object.
|
|
function serverguard.config.Create(unique)
|
|
local object = CONFIG_CLASS:New(unique);
|
|
stored[unique] = object;
|
|
return object;
|
|
end;
|
|
|
|
local oldStored = {}
|
|
|
|
--- **(DEPRECATED)** Add a new configuration entry. The callback function is passed a table argument of the saved data.
|
|
-- @string unique The unique ID of the entry.
|
|
-- @func callback Function to execute when the data is loaded.
|
|
function serverguard.config.New(unique, callback)
|
|
oldStored[unique] = callback
|
|
end
|
|
|
|
--- **(DEPRECATED)** Saves the configuration entry.
|
|
-- @string unique The unique ID of the entry.
|
|
-- @table data The data to save.
|
|
function serverguard.config.Save(unique, data)
|
|
file.Write("serverguard/config/" .. unique .. ".txt", serverguard.von.serialize(data))
|
|
end
|
|
|
|
if (SERVER) then
|
|
hook.Add("serverguard.Initialize", "serverguard.config.Initialize", function()
|
|
for unique, callback in pairs(oldStored) do
|
|
local data = file.Read("serverguard/config/" .. unique .. ".txt")
|
|
|
|
if (data) then
|
|
callback(serverguard.von.deserialize(data))
|
|
end
|
|
end
|
|
|
|
hook.Call("serverguard.PostLoadConfig", nil);
|
|
end)
|
|
else
|
|
hook.Add("serverguard.LoadPlayerData", "serverguard.config.LoadPlayerData", function()
|
|
for unique, callback in pairs(oldStored) do
|
|
local data = file.Read("serverguard/config/" .. unique .. ".txt")
|
|
|
|
if (data) then
|
|
callback(serverguard.von.deserialize(data))
|
|
end
|
|
end
|
|
end)
|
|
end |