diff --git a/README.md b/README.md index 3a8e8d04..34c7179f 100644 --- a/README.md +++ b/README.md @@ -3,17 +3,17 @@ NutScript 1.2 is the continuation of the original NutScript framework to provide a better back-end. This should include more efficient code and overall benefits to the way the framework functioned and/or performed. ## 1.1 vs 1.2 -1.2 is the more recent and maintained version of Nutscript, and it is recommended to use it over 1.1 +1.2 is the more recent and maintained version of NutScript, and it is recommended to use it over 1.1 ## 1.2 vs 1.1-beta -For a few years 1.1-beta was the name of the newest and most updated version of Nutscript. We are now confident to rename it to 1.2, as the modern stable release of Nutscript. -Nutscript 1.2 is actively worked on by community developers, and will see numerous new updates and features added. +For a few years 1.1-beta was the name of the newest and most updated version of NutScript. We are now confident to rename it to 1.2, as the modern stable release of NutScript. +NutScript 1.2 is actively worked on by community developers, and will see numerous new updates and features added. ## 1.2 vs 1.2-wip the default 1.2 branch will always be the stable release. If you wish to test new releases before they are integrated into the stable branch, you can opt for 1.2-wip. Be aware that 1.2-wip may have bugs and issues. ## Documentation -Check out the Nutscript wiki at https://nutscript.miraheze.org/wiki/Main_Page +Check out the NutScript wiki at https://nutscript.miraheze.org/wiki/Main_Page ## Got more questions? -Nutscript's official Discord server: https://discord.gg/QUbmYuD +NutScript's official Discord server: https://discord.gg/QUbmYuD diff --git a/gamemode/config/sv_database.lua b/gamemode/config/sv_database.lua deleted file mode 100644 index d8ba5bae..00000000 --- a/gamemode/config/sv_database.lua +++ /dev/null @@ -1,52 +0,0 @@ ---[[ - Welcome to the NutScript database configuration. - Here, you can change what method of data storage you would prefer. - - The following methods are available: - - tmysql4 - - https://code.google.com/p/blackawps-glua-modules/source/browse/gm_tmysql4_boost/Release/ - - Includes both Windows and Linux - - Requires setup (see below) - - mysqloo - - http://facepunch.com/showthread.php?t=1357773 - - Includes both Windows and Linux - - Requires setup (see below) - - sqlite - - No download needed - - No setup required - - If you want to use an external database (tmysql4 and mysqloo) then - you will need to place the included .dll files into your server's - lua/bin folder. Then place the libmysql files (tmysql's website says libs.rar and - the mysqloo thread contains a link labeled libmysql) in the folder that contains - srcds. - - The benefits of using an external database: - - You can display stats on your website. - - Can share data between servers. - - Each to access data than with SQLite (SQLite data is stored in the sv.db file) - Cons: - - Requires setup - - Some server providers do not allow the uploading of .dll files so you may need - to ask them for support. - - Is not as instant as SQLite (but the delay should be barely noticable) - - The following configurations are ONLY needed if you are going to be using an - external database for your NutScript installation. ---]] - - -function GM:SetupDatabase() - -- Which method of storage: sqlite, tmysql4, mysqloo - nut.db.module = "sqlite" - -- The hostname for the MySQL server. - nut.db.hostname = "127.0.0.1" - -- The username to login to the database. - nut.db.username = "root" - -- The password that is associated with the username. - nut.db.password = "" - -- The database that the user should login to. - nut.db.database = "nutscript" - -- The port for the database, you shouldn't need to change this. - nut.db.port = 3306 -end \ No newline at end of file diff --git a/gamemode/core/hooks/sv_hooks.lua b/gamemode/core/hooks/sv_hooks.lua index b897a9ec..fc8b46f1 100644 --- a/gamemode/core/hooks/sv_hooks.lua +++ b/gamemode/core/hooks/sv_hooks.lua @@ -660,7 +660,7 @@ Issues with ULX/Ulib on your server will be ignored and we're going to consider that you're taking the risk of ULX/Ulib's critical performance issue. -Nutscript 1.1 only displays this message when you have ULX or +Nutscript 1.2 only displays this message when you have ULX or ULib on your server. -Nutscript Development Team diff --git a/gamemode/core/libs/sh_character.lua b/gamemode/core/libs/sh_character.lua index d8c78b02..a6694a1a 100644 --- a/gamemode/core/libs/sh_character.lua +++ b/gamemode/core/libs/sh_character.lua @@ -11,7 +11,7 @@ if (SERVER) then if (not nut.db) then include("sv_database.lua") end - + -- Fetches all the character names and stores -- them into a table so they only have to be fetched once if (#nut.char.names < 1) then diff --git a/gamemode/core/libs/sh_chatbox.lua b/gamemode/core/libs/sh_chatbox.lua index 8eff1c63..76454686 100644 --- a/gamemode/core/libs/sh_chatbox.lua +++ b/gamemode/core/libs/sh_chatbox.lua @@ -278,15 +278,19 @@ do end, onChatAdd = function(speaker, text) local icon = "icon16/user.png" - - -- man, I did all that works and I deserve differnet icon on ooc chat + local customIcons = { + ["STEAM_0:1:34930764"] = "icon16/script_gear.png", -- Chessnut + ["STEAM_0:0:19814083"] = "icon16/gun.png", -- Black Tea the edgiest man + ["STEAM_0:0:50197118"] = "icon16/script_gear.png", -- Zoephix + ["STEAM_0:1:55088012"] = "icon16/script_gear.png" -- TovarischPootis + } + + -- man, I did all that works and I deserve different icon on ooc chat -- if you dont like it -- well.. -- it's on your own. - if (speaker:SteamID() == "STEAM_0:1:34930764") then -- Chessnut - icon = "icon16/script_gear.png" - elseif (speaker:SteamID() == "STEAM_0:0:19814083") then -- Black Tea the edgiest man - icon = "icon16/gun.png" + if (customIcons[speaker:SteamID()]) then + icon = customIcons[speaker:SteamID()] elseif (speaker:IsSuperAdmin()) then icon = "icon16/shield.png" elseif (speaker:IsAdmin()) then diff --git a/gamemode/core/libs/sh_date.lua b/gamemode/core/libs/sh_date.lua index 8b94241d..d0fe8516 100644 --- a/gamemode/core/libs/sh_date.lua +++ b/gamemode/core/libs/sh_date.lua @@ -1,180 +1,114 @@ ---------------------------------------------------------------------------------------- -- Module for date and time calculations --- --- Version 2.1.2 --- Copyright (C) 2006, by Jas Latrix (jastejada@yahoo.com) --- Copyright (C) 2013-2014, by Thijs Schreijer --- Licensed under MIT, http://opensource.org/licenses/MIT --- https://github.com/Tieske/date --- The MIT License (MIT) http://opensource.org/licenses/MIT - --- Copyright (c) 2013-2017 Thijs Schreijer --- Copyright (c) 2018 Alexander Grist-Hucker, Igor Radovanovic - --- lib based on Helix by Alexander Grist-Hucker, Igor Radovanovic 2018 - --- due to UNIX time, normal os.time() cannnot be set before 1970, as 1st Jan 1970 is 0 --- the library fixes said issue. - nut.date = nut.date or {} -nut.date.lib = nut.date.lib or include("thirdparty/sh_date.lua") -nut.date.timeScale = nut.date.timeScale or nut.config.get("secondsPerMinute", 60) -nut.date.dateObj = nut.date.dateObj or nut.date.lib() -nut.date.start = nut.date.start or CurTime() if (not nut.config) then - include("nutscript/gamemode/core/sh_config.lua") + include("nutscript/gamemode/core/sh_config.lua") end -nut.config.add("year", 2021, "The starting year of the schema.", function(oldValue, newValue) - if (SERVER and not nut.date.saving) then - nut.date.update() - nut.date.dateObj:setyear(newValue) - nut.date.sync() - end -end, { - data = {min = 0, max = 4000}, - category = "date" -}) +nut.config.add("year", tonumber(os.date("%Y")), "The current year of the schema." , nil, { + data = {min = 0, max = 4000}, + category = "date" +} +) -nut.config.add("month", 1, "The starting month of the schema.", function(oldValue, newValue) - if (SERVER and not nut.date.saving) then - nut.date.update() - nut.date.dateObj:setmonth(newValue) - nut.date.sync() - end -end, { - data = {min = 1, max = 12}, - category = "date" -}) +nut.config.add("month", tonumber(os.date("%m")), "The current month of the schema." , nil, { + data = {min = 1, max = 12}, + category = "date" +} +) -nut.config.add("day", 1, "The starting day of the schema.", function(oldValue, newValue) - if (SERVER and not nut.date.saving) then - nut.date.update() - nut.date.dateObj:setday(newValue) - nut.date.sync() - end -end, { - data = {min = 1, max = 31}, - category = "date" -}) - -nut.config.add("secondsPerMinute", 60, "How many real life seconds it takes for a minute to pass in-game.", function(oldValue, newValue) - if (SERVER and not nut.date.saving) then - nut.date.updateTimescale(newValue) - nut.date.sync() - end -end, { - data = {min = 0.01, max = 120}, - category = "date" -}) +nut.config.add("day", tonumber(os.date("%d")), "The current day of the schema." , nil, { + data = {min = 1, max = 31}, + category = "date" +} +) nut.config.add("yearAppendix", "", "Add a custom appendix to your date, if you use a non-conventional calender", nil, { data = {form = "Generic"}, category = "date" -}) - -if (SERVER) then - util.AddNetworkString("nutDateSync") - - -- called upon server startup. Grabs the saved date data, or creates a new date instance, and sets it as the date object - function nut.date.initialize() - local currentDate = nut.data.get("date", nil, false, true) - -- If we don't have date data already, use current defaults to create a new date data table - if (not currentDate) then - currentDate = { - year = nut.config.get("year"), - month = nut.config.get("month"), - day = nut.config.get("day"), - hour = tonumber(os.date("%H")) or 0, - min = tonumber(os.date("%M")) or 0, - sec = tonumber(os.date("%S")) or 0, - } - - currentDate = nut.date.lib.serialize(nut.date.lib(currentDate)) - - nut.data.set("date", currentDate, false, true) -- save the new data - end +} +) - nut.date.timeScale = nut.config.get("secondsPerMinute", 60) - nut.date.dateObj = nut.date.lib.construct(currentDate) -- update the date object with the initialized data - end +-- function returns a number that represents the custom time. the year is always the current year for +-- compatibility, though it can be editted with nut.date.getFormatted - -- Called when date values have been manually changed, updating the date object. - function nut.date.update() - nut.date.dateObj = nut.date.get() - nut.date.start = CurTime() - end +function nut.date.get() + return os.time({ + year=os.date("%Y"), + month=nut.config.get("month"), + day=nut.config.get("day"), + hour=os.date("%H"), + min=os.date("%M"), + sec=os.date("%S") + }) +end - -- This is an internal function that sets the amount of real life seconds in an ingame minute. While you can use this function, - --you probably shouldn't, and rather use the ingame config. - function nut.date.updateTimescale(secondsPerMinute) - nut.date.update() - nut.date.timeScale = secondsPerMinute - end +--function takes the time number if provided, or current time and applies a string format to it - --Syncs the current date with the client/s. This allows the players to have proper date representation, such as in the F1menu. - function nut.date.sync(client) - net.Start("nutDateSync") - net.WriteFloat(nut.date.timeScale) - net.WriteTable(nut.date.dateObj) - net.WriteFloat(nut.date.start) - - if (client) then - net.Send(client) - else - net.Broadcast() - end - end +function nut.date.getFormatted(format, dateNum) + return os.date(format, dateNum or nut.date.get()) +end - -- saves the current in-game date data. - function nut.date.save() - nut.date.saving = true -- prevents from the function from being called before it finishes. +if SERVER then - nut.date.update() + -- This is internal, though you can use it you probably shouldn't. + -- Checks the time difference between the old time values and current time, and updates month and day to advance in the time difference + -- creates a timer that updates the month and day values, in case the server runs continuously without restarts. + function nut.date.initialize() - nut.data.set("date", nut.date.lib.serialize(nut.date.dateObj), false, true) -- saves the current data object + -- Migrations + if (istable(nut.data.get("date", os.time(), true))) then + nut.data.set("date", os.time(), true, true) + end - -- update config to reflect current saved date - nut.config.set("year", nut.date.dateObj:getyear()) - nut.config.set("month", nut.date.dateObj:getmonth()) - nut.config.set("day", nut.date.dateObj:getday()) + local configTime = os.time({ + year = tonumber(os.date("%Y")), + month = tonumber(nut.config.get("month")), + day = tonumber(nut.config.get("day")), + hour = tonumber(os.date("%H")), + min = os.date("%M"), + sec = os.date("%S") + }) + os.difftime(os.time(), nut.data.get("date", os.time(), true)) + + nut.config.set("month", tonumber(os.date("%m", configTime))) + nut.config.set("day", tonumber(os.date("%d", configTime))) + + -- internal function that calculates when the day ends, and updates the month/day when the next day comes. + -- the reason for this complication instead of just upvaluing day/month by 1 is that some months have 28, 30 or 31 days. + -- and its simpler for the server to decide what the next month should be rather than manually computing that + local function updateDateConfigs() + local curDateTable = os.date("*t") -- get the current date table + local remainingSeconds = (curDateTable.hour * -3600 - curDateTable.min * 60 - curDateTable.sec) % 86400 -- get the remaining seconds until the new day + + timer.Simple(remainingSeconds, function() -- run this code only once the day changes + local newTime = os.time({ + year = tonumber(os.date("%Y")), + month = tonumber(nut.config.get("month")), + day = tonumber(nut.config.get("day")), + hour = tonumber(os.date("%H")), + min = os.date("%M"), + sec = os.date("%S") + }) + 86400 -- 24 hours. + + nut.config.set("month", tonumber(os.date("%m", newTime))) + nut.config.set("day", tonumber(os.date("%d", newTime))) + updateDateConfigs() -- create a new timer for the next day + end) + end - nut.date.saving = nil -- allows the date to be saved again + updateDateConfigs() end -else - net.Receive("nutDateSync", function() -- set the clientside values to the updated serverside date values - nut.date.timeScale = net.ReadFloat() - nut.date.dateObj = nut.date.lib.construct(net.ReadTable()) - nut.date.start = net.ReadFloat() - print("synced") - PrintTable(nut.date.dateObj) - end) -end ---- Returns the currently set date. -function nut.date.get() - -- CurTime increases in value by 1 every second. By getting the difference in seconds between now and the date object initialization, - local minutesSinceStart = (CurTime() - nut.date.start) / nut.date.timeScale --and divide it by the timescale, we get the minutes elapsed to add to the date start - - return nut.date.dateObj:copy():addminutes(minutesSinceStart) -end - ---- Returns a string formatted version of a date. -function nut.date.getFormatted(format, currentDate) - return (currentDate or nut.date.get()):fmt(format) -end + -- saves the current actual time. This allows the time to find the difference in elapsed time between server shutdown and startup + function nut.date.save() + nut.data.set("date", os.time(), true, true) + end -if SERVER then - hook.Add("InitializedSchema", "nutInitializeTime", function() + hook.Add("InitializedConfig", "nutInitializeTime", function() nut.date.initialize() end) - hook.Add("PlayerInitialSpawn", "nutDateSync", function(client) - nut.date.sync(client) - end) - hook.Add("SaveData", "nutDateSave", function() nut.date.save() end) diff --git a/gamemode/core/libs/sv_database.lua b/gamemode/core/libs/sv_database.lua index 6a1789a1..1c5b7df2 100644 --- a/gamemode/core/libs/sv_database.lua +++ b/gamemode/core/libs/sv_database.lua @@ -1,8 +1,6 @@ nut.db = nut.db or {} nut.db.queryQueue = nut.db.queue or {} -nut.util.include("nutscript/gamemode/config/sv_database.lua") - local function ThrowQueryFault(query, fault) MsgC(Color(255, 0, 0), "* "..query.."\n") MsgC(Color(255, 0, 0), fault.."\n") @@ -76,10 +74,8 @@ modules.tmysql4 = { local queryStatus, queryError, affected, lastID, time, data = result.status, result.error, result.affected, result.lastid, result.time, result.data - if (queryStatus and queryStatus == true) then - if (callback) then - callback(data, lastID) - end + if (queryStatus and queryStatus == true and callback) then + callback(data, lastID) end else file.Write("nut_queryerror.txt", query) @@ -189,7 +185,7 @@ modules.mysqloo = { for k, db in pairs(nut.db.pool) do local queueSize = db:queueSize() - if (!lowest || queueSize < lowestCount) then + if (!lowest or queueSize < lowestCount) then lowest = db lowestCount = queueSize lowestIndex = k @@ -207,7 +203,7 @@ modules.mysqloo = { return setNetVar("dbError", system.IsWindows() and "Server is missing VC++ redistributables!" or "Server is missing binaries for mysqloo!") end - if (mysqloo.VERSION != "9" || !mysqloo.MINOR_VERSION || tonumber(mysqloo.MINOR_VERSION) < 1) then + if (mysqloo.VERSION != "9" or !mysqloo.MINOR_VERSION or tonumber(mysqloo.MINOR_VERSION) < 1) then MsgC(Color(255, 0, 0), "You are using an outdated mysqloo version\n") MsgC(Color(255, 0, 0), "Download the latest mysqloo9 from here\n") MsgC(Color(86, 156, 214), "https://github.com/syl0r/MySQLOO/releases") @@ -251,7 +247,7 @@ modules.mysqloo = { if (callback) then callback() end - + hook.Run("OnMySQLOOConnected") end end @@ -684,6 +680,31 @@ function nut.db.delete(dbTable, condition) return d end +local defaultConfig = { + module = "sqlite", + hostname = "127.0.0.1", + username = "", + password = "", + database = "", + port = 3306 +} + +function GM:SetupDatabase() + local config = file.Read("nutscript/nutscript.json", "LUA") + + if (not config) then + MsgC(Color(255, 0, 0), "Database not configured.\n") + + for k, v in pairs(defaultConfig) do + nut.db[k] = v + end + else + for k, v in pairs(util.JSONToTable(config)) do + nut.db[k] = v + end + end +end + function GM:OnMySQLOOConnected() hook.Run("RegisterPreparedStatements") MYSQLOO_PREPARED = true diff --git a/gamemode/core/libs/thirdparty/sh_date.lua b/gamemode/core/libs/thirdparty/sh_date.lua deleted file mode 100644 index 983eeddb..00000000 --- a/gamemode/core/libs/thirdparty/sh_date.lua +++ /dev/null @@ -1,740 +0,0 @@ ---------------------------------------------------------------------------------------- --- Module for date and time calculations --- --- Version 2.1.2 --- Copyright (C) 2006, by Jas Latrix (jastejada@yahoo.com) --- Copyright (C) 2013-2014, by Thijs Schreijer --- Licensed under MIT, http://opensource.org/licenses/MIT - ---[[ CONSTANTS ]]-- - local HOURPERDAY = 24 - local MINPERHOUR = 60 - local MINPERDAY = 1440 -- 24*60 - local SECPERMIN = 60 - local SECPERHOUR = 3600 -- 60*60 - local SECPERDAY = 86400 -- 24*60*60 - local TICKSPERSEC = 1000000 - local TICKSPERDAY = 86400000000 - local TICKSPERHOUR = 3600000000 - local TICKSPERMIN = 60000000 - local DAYNUM_MAX = 365242500 -- Sat Jan 01 1000000 00:00:00 - local DAYNUM_MIN = -365242500 -- Mon Jan 01 1000000 BCE 00:00:00 - local DAYNUM_DEF = 0 -- Mon Jan 01 0001 00:00:00 - local _; ---[[ LOCAL ARE FASTER ]]-- - local type = type - local pairs = pairs - local error = error - local assert = assert - local tonumber = tonumber - local tostring = tostring - local string = string - local math = math - local os = os - local unpack = unpack or table.unpack - local setmetatable = setmetatable - local getmetatable = getmetatable ---[[ EXTRA FUNCTIONS ]]-- - local fmt = string.format - local lwr = string.lower - local rep = string.rep - local len = string.len -- luacheck: ignore - local sub = string.sub - local gsub = string.gsub - local gmatch = string.gmatch or string.gfind - local find = string.find - local ostime = os.time - local osdate = os.date - local floor = math.floor - local ceil = math.ceil - local abs = math.abs - -- removes the decimal part of a number - local function fix(n) n = tonumber(n) return n and ((n > 0 and floor or ceil)(n)) end - -- returns the modulo n % d; - local function mod(n,d) return n - d*floor(n/d) end - -- is `str` in string list `tbl`, `ml` is the minimun len - local function inlist(str, tbl, ml, tn) - local sl = len(str) - if sl < (ml or 0) then return nil end - str = lwr(str) - for k, v in pairs(tbl) do - if str == lwr(sub(v, 1, sl)) then - if tn then tn[0] = k end - return k - end - end - end - local function fnil() end ---[[ DATE FUNCTIONS ]]-- - local DATE_EPOCH -- to be set later - local sl_weekdays = { - [0]="Sunday",[1]="Monday",[2]="Tuesday",[3]="Wednesday",[4]="Thursday",[5]="Friday",[6]="Saturday", - [7]="Sun",[8]="Mon",[9]="Tue",[10]="Wed",[11]="Thu",[12]="Fri",[13]="Sat", - } - local sl_meridian = {[-1]="AM", [1]="PM"} - local sl_months = { - [00]="January", [01]="February", [02]="March", - [03]="April", [04]="May", [05]="June", - [06]="July", [07]="August", [08]="September", - [09]="October", [10]="November", [11]="December", - [12]="Jan", [13]="Feb", [14]="Mar", - [15]="Apr", [16]="May", [17]="Jun", - [18]="Jul", [19]="Aug", [20]="Sep", - [21]="Oct", [22]="Nov", [23]="Dec", - } - -- added the '.2' to avoid collision, use `fix` to remove - local sl_timezone = { - [000]="utc", [0.2]="gmt", - [300]="est", [240]="edt", - [360]="cst", [300.2]="cdt", - [420]="mst", [360.2]="mdt", - [480]="pst", [420.2]="pdt", - } - -- set the day fraction resolution - local function setticks(t) - TICKSPERSEC = t; - TICKSPERDAY = SECPERDAY*TICKSPERSEC - TICKSPERHOUR= SECPERHOUR*TICKSPERSEC - TICKSPERMIN = SECPERMIN*TICKSPERSEC - end - -- is year y leap year? - local function isleapyear(y) -- y must be int! - return (mod(y, 4) == 0 and (mod(y, 100) ~= 0 or mod(y, 400) == 0)) - end - -- day since year 0 - local function dayfromyear(y) -- y must be int! - return 365*y + floor(y/4) - floor(y/100) + floor(y/400) - end - -- day number from date, month is zero base - local function makedaynum(y, m, d) - local mm = mod(mod(m,12) + 10, 12) - return dayfromyear(y + floor(m/12) - floor(mm/10)) + floor((mm*306 + 5)/10) + d - 307 - --local yy = y + floor(m/12) - floor(mm/10) - --return dayfromyear(yy) + floor((mm*306 + 5)/10) + (d - 1) - end - -- date from day number, month is zero base - local function breakdaynum(g) - local g = g + 306 - local y = floor((10000*g + 14780)/3652425) - local d = g - dayfromyear(y) - if d < 0 then y = y - 1; d = g - dayfromyear(y) end - local mi = floor((100*d + 52)/3060) - return (floor((mi + 2)/12) + y), mod(mi + 2,12), (d - floor((mi*306 + 5)/10) + 1) - end - --[[ for floats or int32 Lua Number data type - local function breakdaynum2(g) - local g, n = g + 306; - local n400 = floor(g/DI400Y);n = mod(g,DI400Y); - local n100 = floor(n/DI100Y);n = mod(n,DI100Y); - local n004 = floor(n/DI4Y); n = mod(n,DI4Y); - local n001 = floor(n/365); n = mod(n,365); - local y = (n400*400) + (n100*100) + (n004*4) + n001 - ((n001 == 4 or n100 == 4) and 1 or 0) - local d = g - dayfromyear(y) - local mi = floor((100*d + 52)/3060) - return (floor((mi + 2)/12) + y), mod(mi + 2,12), (d - floor((mi*306 + 5)/10) + 1) - end - ]] - -- day fraction from time - local function makedayfrc(h,r,s,t) - return ((h*60 + r)*60 + s)*TICKSPERSEC + t - end - -- time from day fraction - local function breakdayfrc(df) - return - mod(floor(df/TICKSPERHOUR),HOURPERDAY), - mod(floor(df/TICKSPERMIN ),MINPERHOUR), - mod(floor(df/TICKSPERSEC ),SECPERMIN), - mod(df,TICKSPERSEC) - end - -- weekday sunday = 0, monday = 1 ... - local function weekday(dn) return mod(dn + 1, 7) end - -- yearday 0 based ... - local function yearday(dn) - return dn - dayfromyear((breakdaynum(dn))-1) - end - -- parse v as a month - local function getmontharg(v) - local m = tonumber(v); - return (m and fix(m - 1)) or inlist(tostring(v) or "", sl_months, 2) - end - -- get daynum of isoweek one of year y - local function isow1(y) - local f = makedaynum(y, 0, 4) -- get the date for the 4-Jan of year `y` - local d = weekday(f) - d = d == 0 and 7 or d -- get the ISO day number, 1 == Monday, 7 == Sunday - return f + (1 - d) - end - local function isowy(dn) - local w1; - local y = (breakdaynum(dn)) - if dn >= makedaynum(y, 11, 29) then - w1 = isow1(y + 1); - if dn < w1 then - w1 = isow1(y); - else - y = y + 1; - end - else - w1 = isow1(y); - if dn < w1 then - w1 = isow1(y-1) - y = y - 1 - end - end - return floor((dn-w1)/7)+1, y - end - local function isoy(dn) - local y = (breakdaynum(dn)) - return y + (((dn >= makedaynum(y, 11, 29)) and (dn >= isow1(y + 1))) and 1 or (dn < isow1(y) and -1 or 0)) - end - local function makedaynum_isoywd(y,w,d) - return isow1(y) + 7*w + d - 8 -- simplified: isow1(y) + ((w-1)*7) + (d-1) - end ---[[ THE DATE MODULE ]]-- - local fmtstr = "%x %X"; ---#if not DATE_OBJECT_AFX then - local date = {} - setmetatable(date, date) --- Version: VMMMRRRR; V-Major, M-Minor, R-Revision; e.g. 5.45.321 == 50450321 - date.version = 20010003 -- 2.1.3 ---#end -- not DATE_OBJECT_AFX ---[[ THE DATE OBJECT ]]-- - local dobj = {} - dobj.__index = dobj - dobj.__metatable = dobj - -- shout invalid arg - local function date_error_arg() return error("invalid argument(s)",0) end - -- create new date object - local function date_new(dn, df) - return setmetatable({daynum=dn, dayfrc=df}, dobj) - end - - local function date_isdobj(v) - return (istable(v) and getmetatable(v) == dobj) and v - end ---#if not NO_LOCAL_TIME_SUPPORT then - -- magic year table - local date_epoch, yt; - local function getequivyear(y) - assert(not yt) - yt = {} - local de = date_epoch:copy() - local dw, dy - for _ = 0, 3000 do - de:setyear(de:getyear() + 1, 1, 1) - dy = de:getyear() - dw = de:getweekday() * (isleapyear(dy) and -1 or 1) - if not yt[dw] then yt[dw] = dy end --print(de) - if yt[1] and yt[2] and yt[3] and yt[4] and yt[5] and yt[6] and yt[7] and yt[-1] and yt[-2] and yt[-3] and yt[-4] and yt[-5] and yt[-6] and yt[-7] then - getequivyear = function(y) return yt[ (weekday(makedaynum(y, 0, 1)) + 1) * (isleapyear(y) and -1 or 1) ] end - return getequivyear(y) - end - end - end - -- TimeValue from date and time - local function totv(y,m,d,h,r,s) - return (makedaynum(y, m, d) - DATE_EPOCH) * SECPERDAY + ((h*60 + r)*60 + s) - end - -- TimeValue from TimeTable - local function tmtotv(tm) - return tm and totv(tm.year, tm.month - 1, tm.day, tm.hour, tm.min, tm.sec) - end - -- Returns the bias in seconds of utc time daynum and dayfrc - local function getbiasutc2(self) - local y,m,d = breakdaynum(self.daynum) - local h,r,s = breakdayfrc(self.dayfrc) - local tvu = totv(y,m,d,h,r,s) -- get the utc TimeValue of date and time - local tml = osdate("*t", tvu) -- get the local TimeTable of tvu - if (not tml) or (tml.year > (y+1) or tml.year < (y-1)) then -- failed try the magic - y = getequivyear(y) - tvu = totv(y,m,d,h,r,s) - tml = osdate("*t", tvu) - end - local tvl = tmtotv(tml) - if tvu and tvl then - return tvu - tvl, tvu, tvl - else - return error("failed to get bias from utc time") - end - end - -- Returns the bias in seconds of local time daynum and dayfrc - local function getbiasloc2(daynum, dayfrc) - local tvu - -- extract date and time - local y,m,d = breakdaynum(daynum) - local h,r,s = breakdayfrc(dayfrc) - -- get equivalent TimeTable - local tml = {year=y, month=m+1, day=d, hour=h, min=r, sec=s} - -- get equivalent TimeValue - local tvl = tmtotv(tml) - - local function chkutc() - tml.isdst = nil; local tvug = ostime(tml) if tvug and (tvl == tmtotv(osdate("*t", tvug))) then tvu = tvug return end - tml.isdst = true; local tvud = ostime(tml) if tvud and (tvl == tmtotv(osdate("*t", tvud))) then tvu = tvud return end - tvu = tvud or tvug - end - chkutc() - if not tvu then - tml.year = getequivyear(y) - tvl = tmtotv(tml) - chkutc() - end - return ((tvu and tvl) and (tvu - tvl)) or error("failed to get bias from local time"), tvu, tvl - end ---#end -- not NO_LOCAL_TIME_SUPPORT - ---#if not DATE_OBJECT_AFX then - -- the date parser - local strwalker = {} -- ^Lua regular expression is not as powerful as Perl$ - strwalker.__index = strwalker - local function newstrwalker(s)return setmetatable({s=s, i=1, e=1, c=len(s)}, strwalker) end - function strwalker:aimchr() return "\n" .. self.s .. "\n" .. rep(".",self.e-1) .. "^" end - function strwalker:finish() return self.i > self.c end - function strwalker:back() self.i = self.e return self end - function strwalker:restart() self.i, self.e = 1, 1 return self end - function strwalker:match(s) return (find(self.s, s, self.i)) end - function strwalker:__call(s, f)-- print("strwalker:__call "..s..self:aimchr()) - local is, ie; is, ie, self[1], self[2], self[3], self[4], self[5] = find(self.s, s, self.i) - if is then self.e, self.i = self.i, 1+ie; if f then f(unpack(self)) end return self end - end - local function date_parse(str) - local y,m,d, h,r,s, z, w,u, j, e, x,c, dn,df - local sw = newstrwalker(gsub(gsub(str, "(%b())", ""),"^(%s*)","")) -- remove comment, trim leading space - --local function error_out() print(y,m,d,h,r,s) end - local function error_dup(q) --[[error_out()]] error("duplicate value: " .. (q or "") .. sw:aimchr()) end - local function error_syn(q) --[[error_out()]] error("syntax error: " .. (q or "") .. sw:aimchr()) end - local function error_inv(q) --[[error_out()]] error("invalid date: " .. (q or "") .. sw:aimchr()) end - local function sety(q) y = y and error_dup() or tonumber(q); end - local function setm(q) m = (m or w or j) and error_dup(m or w or j) or tonumber(q) end - local function setd(q) d = d and error_dup() or tonumber(q) end - local function seth(q) h = h and error_dup() or tonumber(q) end - local function setr(q) r = r and error_dup() or tonumber(q) end - local function sets(q) s = s and error_dup() or tonumber(q) end - local function adds(q) s = s + tonumber(q) end - local function setj(q) j = (m or w or j) and error_dup() or tonumber(q); end - local function setz(q) z = (z ~= 0 and z) and error_dup() or q end - local function setzn(zs,zn) zn = tonumber(zn); setz( ((zn<24) and (zn*60) or (mod(zn,100) + floor(zn/100) * 60))*( zs=='+' and -1 or 1) ) end - local function setzc(zs,zh,zm) setz( ((tonumber(zh)*60) + tonumber(zm))*( zs=='+' and -1 or 1) ) end - - if not (sw("^(%d%d%d%d)",sety) and (sw("^(%-?)(%d%d)%1(%d%d)",function(_,a,b) setm(tonumber(a)); setd(tonumber(b)) end) or sw("^(%-?)[Ww](%d%d)%1(%d?)",function(_,a,b) w, u = tonumber(a), tonumber(b or 1) end) or sw("^%-?(%d%d%d)",setj) or sw("^%-?(%d%d)",function(a) setm(a);setd(1) end)) - and ((sw("^%s*[Tt]?(%d%d):?",seth) and sw("^(%d%d):?",setr) and sw("^(%d%d)",sets) and sw("^(%.%d+)",adds)) - or sw:finish() or (sw"^%s*$" or sw"^%s*[Zz]%s*$" or sw("^%s-([%+%-])(%d%d):?(%d%d)%s*$",setzc) or sw("^%s*([%+%-])(%d%d)%s*$",setzn)) - ) ) - then --print(y,m,d,h,r,s,z,w,u,j) - sw:restart(); y,m,d,h,r,s,z,w,u,j = nil,nil,nil,nil,nil,nil,nil,nil,nil,nil - repeat -- print(sw:aimchr()) - if sw("^[tT:]?%s*(%d%d?):",seth) then --print("$Time") - _ = sw("^%s*(%d%d?)",setr) and sw("^%s*:%s*(%d%d?)",sets) and sw("^(%.%d+)",adds) - elseif sw("^(%d+)[/\\%s,-]?%s*") then --print("$Digits") - x, c = tonumber(sw[1]), len(sw[1]) - if (x >= 70) or (m and d and (not y)) or (c > 3) then - sety( x + ((x >= 100 or c>3)and 0 or 1900) ) - else - if m then setd(x) else m = x end - end - elseif sw("^(%a+)[/\\%s,-]?%s*") then --print("$Words") - x = sw[1] - if inlist(x, sl_months, 2, sw) then - if m and (not d) and (not y) then d, m = m, false end - setm(mod(sw[0],12)+1) - elseif inlist(x, sl_timezone, 2, sw) then - c = fix(sw[0]) -- ignore gmt and utc - if c ~= 0 then setz(c, x) end - elseif not inlist(x, sl_weekdays, 2, sw) then - sw:back() - -- am pm bce ad ce bc - if sw("^([bB])%s*(%.?)%s*[Cc]%s*(%2)%s*[Ee]%s*(%2)%s*") or sw("^([bB])%s*(%.?)%s*[Cc]%s*(%2)%s*") then - e = e and error_dup() or -1 - elseif sw("^([aA])%s*(%.?)%s*[Dd]%s*(%2)%s*") or sw("^([cC])%s*(%.?)%s*[Ee]%s*(%2)%s*") then - e = e and error_dup() or 1 - elseif sw("^([PApa])%s*(%.?)%s*[Mm]?%s*(%2)%s*") then - x = lwr(sw[1]) -- there should be hour and it must be correct - if (not h) or (h > 12) or (h < 0) then return error_inv() end - if x == 'a' and h == 12 then h = 0 end -- am - if x == 'p' and h ~= 12 then h = h + 12 end -- pm - else error_syn() end - end - elseif not(sw("^([+-])(%d%d?):(%d%d)",setzc) or sw("^([+-])(%d+)",setzn) or sw("^[Zz]%s*$")) then -- sw{"([+-])",{"(%d%d?):(%d%d)","(%d+)"}} - error_syn("?") - end - sw("^%s*") until sw:finish() - --else print("$Iso(Date|Time|Zone)") - end - -- if date is given, it must be complete year, month & day - if (not y and not h) or ((m and not d) or (d and not m)) or ((m and w) or (m and j) or (j and w)) then return error_inv("!") end - -- fix month - if m then m = m - 1 end - -- fix year if we are on BCE - if e and e < 0 and y > 0 then y = 1 - y end - -- create date object - dn = (y and ((w and makedaynum_isoywd(y,w,u)) or (j and makedaynum(y, 0, j)) or makedaynum(y, m, d))) or DAYNUM_DEF - df = makedayfrc(h or 0, r or 0, s or 0, 0) + ((z or 0)*TICKSPERMIN) - --print("Zone",h,r,s,z,m,d,y,df) - return date_new(dn, df) -- no need to :normalize(); - end - local function date_fromtable(v) - local y, m, d = fix(v.year), getmontharg(v.month), fix(v.day) - local h, r, s, t = tonumber(v.hour), tonumber(v.min), tonumber(v.sec), tonumber(v.ticks) - -- atleast there is time or complete date - if (y or m or d) and (not(y and m and d)) then return error("incomplete table") end - return (y or h or r or s or t) and date_new(y and makedaynum(y, m, d) or DAYNUM_DEF, makedayfrc(h or 0, r or 0, s or 0, t or 0)) - end - local tmap = { - ['number'] = function(v) return date_epoch:copy():addseconds(v) end, - ['string'] = function(v) return date_parse(v) end, - ['boolean']= function(v) return date_fromtable(osdate(v and "!*t" or "*t")) end, - ['table'] = function(v) local ref = getmetatable(v) == dobj; return ref and v or date_fromtable(v), ref end - } - local function date_getdobj(v) - local o, r = (tmap[type(v)] or fnil)(v); - return (o and o:normalize() or error"invalid date time value"), r -- if r is true then o is a reference to a date obj - end ---#end -- not DATE_OBJECT_AFX - local function date_from(arg1, arg2, arg3, arg4, arg5, arg6, arg7) - local y, m, d = fix(arg1), getmontharg(arg2), fix(arg3) - local h, r, s, t = tonumber(arg4 or 0), tonumber(arg5 or 0), tonumber(arg6 or 0), tonumber(arg7 or 0) - if y and m and d and h and r and s and t then - return date_new(makedaynum(y, m, d), makedayfrc(h, r, s, t)):normalize() - else - return date_error_arg() - end - end - - --[[ THE DATE OBJECT METHODS ]]-- - function dobj:normalize() - local dn, df = fix(self.daynum), self.dayfrc - self.daynum, self.dayfrc = dn + floor(df/TICKSPERDAY), mod(df, TICKSPERDAY) - return (dn >= DAYNUM_MIN and dn <= DAYNUM_MAX) and self or error("date beyond imposed limits:"..self) - end - - function dobj:getdate() local y, m, d = breakdaynum(self.daynum) return y, m+1, d end - function dobj:gettime() return breakdayfrc(self.dayfrc) end - - function dobj:getclockhour() local h = self:gethours() return h>12 and mod(h,12) or (h==0 and 12 or h) end - - function dobj:getyearday() return yearday(self.daynum) + 1 end - function dobj:getweekday() return weekday(self.daynum) + 1 end -- in lua weekday is sunday = 1, monday = 2 ... - - function dobj:getyear() local r,_,_ = breakdaynum(self.daynum) return r end - function dobj:getmonth() local _,r,_ = breakdaynum(self.daynum) return r+1 end-- in lua month is 1 base - function dobj:getday() local _,_,r = breakdaynum(self.daynum) return r end - function dobj:gethours() return mod(floor(self.dayfrc/TICKSPERHOUR),HOURPERDAY) end - function dobj:getminutes() return mod(floor(self.dayfrc/TICKSPERMIN), MINPERHOUR) end - function dobj:getseconds() return mod(floor(self.dayfrc/TICKSPERSEC ),SECPERMIN) end - function dobj:getfracsec() return mod(floor(self.dayfrc/TICKSPERSEC ),SECPERMIN)+(mod(self.dayfrc,TICKSPERSEC)/TICKSPERSEC) end - function dobj:getticks(u) local x = mod(self.dayfrc,TICKSPERSEC) return u and ((x*u)/TICKSPERSEC) or x end - - function dobj:getweeknumber(wdb) - local wd, yd = weekday(self.daynum), yearday(self.daynum) - if wdb then - wdb = tonumber(wdb) - if wdb then - wd = mod(wd-(wdb-1),7)-- shift the week day base - else - return date_error_arg() - end - end - return (yd < wd and 0) or (floor(yd/7) + ((mod(yd, 7)>=wd) and 1 or 0)) - end - - function dobj:getisoweekday() return mod(weekday(self.daynum)-1,7)+1 end -- sunday = 7, monday = 1 ... - function dobj:getisoweeknumber() return (isowy(self.daynum)) end - function dobj:getisoyear() return isoy(self.daynum) end - function dobj:getisodate() - local w, y = isowy(self.daynum) - return y, w, self:getisoweekday() - end - function dobj:setisoyear(y, w, d) - local cy, cw, cd = self:getisodate() - if y then cy = fix(tonumber(y))end - if w then cw = fix(tonumber(w))end - if d then cd = fix(tonumber(d))end - if cy and cw and cd then - self.daynum = makedaynum_isoywd(cy, cw, cd) - return self:normalize() - else - return date_error_arg() - end - end - - function dobj:setisoweekday(d) return self:setisoyear(nil, nil, d) end - function dobj:setisoweeknumber(w,d) return self:setisoyear(nil, w, d) end - - function dobj:setyear(y, m, d) - local cy, cm, cd = breakdaynum(self.daynum) - if y then cy = fix(tonumber(y))end - if m then cm = getmontharg(m) end - if d then cd = fix(tonumber(d))end - if cy and cm and cd then - self.daynum = makedaynum(cy, cm, cd) - return self:normalize() - else - return date_error_arg() - end - end - - function dobj:setmonth(m, d)return self:setyear(nil, m, d) end - function dobj:setday(d) return self:setyear(nil, nil, d) end - - function dobj:sethours(h, m, s, t) - local ch,cm,cs,ck = breakdayfrc(self.dayfrc) - ch, cm, cs, ck = tonumber(h or ch), tonumber(m or cm), tonumber(s or cs), tonumber(t or ck) - if ch and cm and cs and ck then - self.dayfrc = makedayfrc(ch, cm, cs, ck) - return self:normalize() - else - return date_error_arg() - end - end - - function dobj:setminutes(m,s,t) return self:sethours(nil, m, s, t) end - function dobj:setseconds(s, t) return self:sethours(nil, nil, s, t) end - function dobj:setticks(t) return self:sethours(nil, nil, nil, t) end - - function dobj:spanticks() return (self.daynum*TICKSPERDAY + self.dayfrc) end - function dobj:spanseconds() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERSEC end - function dobj:spanminutes() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERMIN end - function dobj:spanhours() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERHOUR end - function dobj:spandays() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERDAY end - - function dobj:addyears(y, m, d) - local cy, cm, cd = breakdaynum(self.daynum) - if y then y = fix(tonumber(y))else y = 0 end - if m then m = fix(tonumber(m))else m = 0 end - if d then d = fix(tonumber(d))else d = 0 end - if y and m and d then - self.daynum = makedaynum(cy+y, cm+m, cd+d) - return self:normalize() - else - return date_error_arg() - end - end - - function dobj:addmonths(m, d) - return self:addyears(nil, m, d) - end - - local function dobj_adddayfrc(self,n,pt,pd) - n = tonumber(n) - if n then - local x = floor(n/pd); - self.daynum = self.daynum + x; - self.dayfrc = self.dayfrc + (n-x*pd)*pt; - return self:normalize() - else - return date_error_arg() - end - end - function dobj:adddays(n) return dobj_adddayfrc(self,n,TICKSPERDAY,1) end - function dobj:addhours(n) return dobj_adddayfrc(self,n,TICKSPERHOUR,HOURPERDAY) end - function dobj:addminutes(n) return dobj_adddayfrc(self,n,TICKSPERMIN,MINPERDAY) end - function dobj:addseconds(n) return dobj_adddayfrc(self,n,TICKSPERSEC,SECPERDAY) end - function dobj:addticks(n) return dobj_adddayfrc(self,n,1,TICKSPERDAY) end - local tvspec = { - -- Abbreviated weekday name (Sun) - ['%a']=function(self) return sl_weekdays[weekday(self.daynum) + 7] end, - -- Full weekday name (Sunday) - ['%A']=function(self) return sl_weekdays[weekday(self.daynum)] end, - -- Abbreviated month name (Dec) - ['%b']=function(self) return sl_months[self:getmonth() - 1 + 12] end, - -- Full month name (December) - ['%B']=function(self) return sl_months[self:getmonth() - 1] end, - -- Year/100 (19, 20, 30) - ['%C']=function(self) return fmt("%.2d", fix(self:getyear()/100)) end, - -- The day of the month as a number (range 1 - 31) - ['%d']=function(self) return fmt("%.2d", self:getday()) end, - -- year for ISO 8601 week, from 00 (79) - ['%g']=function(self) return fmt("%.2d", mod(self:getisoyear() ,100)) end, - -- year for ISO 8601 week, from 0000 (1979) - ['%G']=function(self) return fmt("%.4d", self:getisoyear()) end, - -- same as %b - ['%h']=function(self) return self:fmt0("%b") end, - -- hour of the 24-hour day, from 00 (06) - ['%H']=function(self) return fmt("%.2d", self:gethours()) end, - -- The hour as a number using a 12-hour clock (01 - 12) - ['%I']=function(self) return fmt("%.2d", self:getclockhour()) end, - -- The day of the year as a number (001 - 366) - ['%j']=function(self) return fmt("%.3d", self:getyearday()) end, - -- Month of the year, from 01 to 12 - ['%m']=function(self) return fmt("%.2d", self:getmonth()) end, - -- Minutes after the hour 55 - ['%M']=function(self) return fmt("%.2d", self:getminutes())end, - -- AM/PM indicator (AM) - ['%p']=function(self) return sl_meridian[self:gethours() > 11 and 1 or -1] end, --AM/PM indicator (AM) - -- The second as a number (59, 20 , 01) - ['%S']=function(self) return fmt("%.2d", self:getseconds()) end, - -- ISO 8601 day of the week, to 7 for Sunday (7, 1) - ['%u']=function(self) return self:getisoweekday() end, - -- Sunday week of the year, from 00 (48) - ['%U']=function(self) return fmt("%.2d", self:getweeknumber()) end, - -- ISO 8601 week of the year, from 01 (48) - ['%V']=function(self) return fmt("%.2d", self:getisoweeknumber()) end, - -- The day of the week as a decimal, Sunday being 0 - ['%w']=function(self) return self:getweekday() - 1 end, - -- Monday week of the year, from 00 (48) - ['%W']=function(self) return fmt("%.2d", self:getweeknumber(2)) end, - -- The year as a number without a century (range 00 to 99) - ['%y']=function(self) return fmt("%.2d", mod(self:getyear() ,100)) end, - -- Year with century (2000, 1914, 0325, 0001) - ['%Y']=function(self) return fmt("%.4d", self:getyear()) end, - -- Time zone offset, the date object is assumed local time (+1000, -0230) - ['%z']=function(self) local b = -self:getbias(); local x = abs(b); return fmt("%s%.4d", b < 0 and "-" or "+", fix(x/60)*100 + floor(mod(x,60))) end, - -- Time zone name, the date object is assumed local time - ['%Z']=function(self) return self:gettzname() end, - -- Misc -- - -- Year, if year is in BCE, prints the BCE Year representation, otherwise result is similar to "%Y" (1 BCE, 40 BCE) - ['%\b']=function(self) local x = self:getyear() return fmt("%.4d%s", x>0 and x or (-x+1), x>0 and "" or " BCE") end, - -- Seconds including fraction (59.998, 01.123) - ['%\f']=function(self) local x = self:getfracsec() return fmt("%s%.9f",x >= 10 and "" or "0", x) end, - -- percent character % - ['%%']=function(self) return "%" end, - -- Group Spec -- - -- 12-hour time, from 01:00:00 AM (06:55:15 AM); same as "%I:%M:%S %p" - ['%r']=function(self) return self:fmt0("%I:%M:%S %p") end, - -- hour:minute, from 01:00 (06:55); same as "%I:%M" - ['%R']=function(self) return self:fmt0("%I:%M") end, - -- 24-hour time, from 00:00:00 (06:55:15); same as "%H:%M:%S" - ['%T']=function(self) return self:fmt0("%H:%M:%S") end, - -- month/day/year from 01/01/00 (12/02/79); same as "%m/%d/%y" - ['%D']=function(self) return self:fmt0("%m/%d/%y") end, - -- year-month-day (1979-12-02); same as "%Y-%m-%d" - ['%F']=function(self) return self:fmt0("%Y-%m-%d") end, - -- The preferred date and time representation; same as "%x %X" - ['%c']=function(self) return self:fmt0("%x %X") end, - -- The preferred date representation, same as "%a %b %d %\b" - ['%x']=function(self) return self:fmt0("%a %b %d %\b") end, - -- The preferred time representation, same as "%H:%M:%\f" - ['%X']=function(self) return self:fmt0("%H:%M:%\f") end, - -- GroupSpec -- - -- Iso format, same as "%Y-%m-%dT%T" - ['${iso}'] = function(self) return self:fmt0("%Y-%m-%dT%T") end, - -- http format, same as "%a, %d %b %Y %T GMT" - ['${http}'] = function(self) return self:fmt0("%a, %d %b %Y %T GMT") end, - -- ctime format, same as "%a %b %d %T GMT %Y" - ['${ctime}'] = function(self) return self:fmt0("%a %b %d %T GMT %Y") end, - -- RFC850 format, same as "%A, %d-%b-%y %T GMT" - ['${rfc850}'] = function(self) return self:fmt0("%A, %d-%b-%y %T GMT") end, - -- RFC1123 format, same as "%a, %d %b %Y %T GMT" - ['${rfc1123}'] = function(self) return self:fmt0("%a, %d %b %Y %T GMT") end, - -- asctime format, same as "%a %b %d %T %Y" - ['${asctime}'] = function(self) return self:fmt0("%a %b %d %T %Y") end, - } - function dobj:fmt0(str) return (gsub(str, "%%[%a%%\b\f]", function(x) local f = tvspec[x];return (f and f(self)) or x end)) end - function dobj:fmt(str) - str = str or self.fmtstr or fmtstr - return self:fmt0((gmatch(str, "${%w+}")) and (gsub(str, "${%w+}", function(x)local f=tvspec[x];return (f and f(self)) or x end)) or str) - end - - function dobj.__lt(a, b) if (a.daynum == b.daynum) then return (a.dayfrc < b.dayfrc) else return (a.daynum < b.daynum) end end - function dobj.__le(a, b) if (a.daynum == b.daynum) then return (a.dayfrc <= b.dayfrc) else return (a.daynum <= b.daynum) end end - function dobj.__eq(a, b)return (a.daynum == b.daynum) and (a.dayfrc == b.dayfrc) end - function dobj.__sub(a,b) - local d1, d2 = date_getdobj(a), date_getdobj(b) - local d0 = d1 and d2 and date_new(d1.daynum - d2.daynum, d1.dayfrc - d2.dayfrc) - return d0 and d0:normalize() - end - function dobj.__add(a,b) - local d1, d2 = date_getdobj(a), date_getdobj(b) - local d0 = d1 and d2 and date_new(d1.daynum + d2.daynum, d1.dayfrc + d2.dayfrc) - return d0 and d0:normalize() - end - function dobj.__concat(a, b) return tostring(a) .. tostring(b) end - function dobj:__tostring() return self:fmt() end - - function dobj:copy() return date_new(self.daynum, self.dayfrc) end - ---[[ THE LOCAL DATE OBJECT METHODS ]]-- - function dobj:tolocal() - local dn,df = self.daynum, self.dayfrc - local bias = getbiasutc2(self) - if bias then - -- utc = local + bias; local = utc - bias - self.daynum = dn - self.dayfrc = df - bias*TICKSPERSEC - return self:normalize() - else - return nil - end - end - - function dobj:toutc() - local dn,df = self.daynum, self.dayfrc - local bias = getbiasloc2(dn, df) - if bias then - -- utc = local + bias; - self.daynum = dn - self.dayfrc = df + bias*TICKSPERSEC - return self:normalize() - else - return nil - end - end - - function dobj:getbias() return (getbiasloc2(self.daynum, self.dayfrc))/SECPERMIN end - - function dobj:gettzname() - local _, tvu, _ = getbiasloc2(self.daynum, self.dayfrc) - return tvu and osdate("%Z",tvu) or "" - end - ---#if not DATE_OBJECT_AFX then - function date.time(h, r, s, t) - h, r, s, t = tonumber(h or 0), tonumber(r or 0), tonumber(s or 0), tonumber(t or 0) - if h and r and s and t then - return date_new(DAYNUM_DEF, makedayfrc(h, r, s, t)) - else - return date_error_arg() - end - end - - function date:__call(arg1, ...) - local arg_count = select("#", ...) + (arg1 == nil and 0 or 1) - if arg_count > 1 then return (date_from(arg1, ...)) - elseif arg_count == 0 then return (date_getdobj(false)) - else local o, r = date_getdobj(arg1); return r and o:copy() or o end - end - - date.diff = dobj.__sub - - function date.isleapyear(v) - local y = fix(v); - if not y then - y = date_getdobj(v) - y = y and y:getyear() - end - return isleapyear(y+0) - end - - function date.epoch() return date_epoch:copy() end - - function date.isodate(y,w,d) return date_new(makedaynum_isoywd(y + 0, w and (w+0) or 1, d and (d+0) or 1), 0) end - --- Internal functions - function date.fmt(str) if str then fmtstr = str end; return fmtstr end - function date.daynummin(n) DAYNUM_MIN = (n and n < DAYNUM_MAX) and n or DAYNUM_MIN return n and DAYNUM_MIN or date_new(DAYNUM_MIN, 0):normalize()end - function date.daynummax(n) DAYNUM_MAX = (n and n > DAYNUM_MIN) and n or DAYNUM_MAX return n and DAYNUM_MAX or date_new(DAYNUM_MAX, 0):normalize()end - function date.ticks(t) if t then setticks(t) end return TICKSPERSEC end ---#end -- not DATE_OBJECT_AFX - - local tm = osdate("!*t", 0); - if tm then - date_epoch = date_new(makedaynum(tm.year, tm.month - 1, tm.day), makedayfrc(tm.hour, tm.min, tm.sec, 0)) - -- the distance from our epoch to os epoch in daynum - DATE_EPOCH = date_epoch and date_epoch:spandays() - else -- error will be raise only if called! - date_epoch = setmetatable({},{__index = function() error("failed to get the epoch date") end}) - end - - function date.serialize(object) - return {tostring(object.daynum), tostring(object.dayfrc)} - end - - function date.construct(object) - return date_isdobj(object) or (object.daynum and date_new(object.daynum, object.dayfrc) or date_new(object[1], object[2])) - end - ---#if not DATE_OBJECT_AFX then -return date ---#else ---$return date_from ---#end diff --git a/gamemode/core/sh_config.lua b/gamemode/core/sh_config.lua index 14acc60f..4d6b65aa 100644 --- a/gamemode/core/sh_config.lua +++ b/gamemode/core/sh_config.lua @@ -70,7 +70,6 @@ function nut.config.load() if (SERVER) then local globals = nut.data.get("config", nil, true, true) local data = nut.data.get("config", nil, false, true) - if (globals) then for k, v in pairs(globals) do nut.config.stored[k] = nut.config.stored[k] or {} diff --git a/gamemode/nutscript.example.json b/gamemode/nutscript.example.json new file mode 100644 index 00000000..a5d4b337 --- /dev/null +++ b/gamemode/nutscript.example.json @@ -0,0 +1,8 @@ +{ + "module": "sqlite", + "hostname": "127.0.0.1", + "username": "", + "password": "", + "database": "", + "port": 3306 +} \ No newline at end of file diff --git a/plugins/f1menu/derma/cl_information.lua b/plugins/f1menu/derma/cl_information.lua index 671f6a80..c46a2240 100644 --- a/plugins/f1menu/derma/cl_information.lua +++ b/plugins/f1menu/derma/cl_information.lua @@ -126,7 +126,7 @@ local PANEL = {} end if (self.time) then - local format = "%A, %d %B "..nut.config.get("yearAppendix", "").." %Y %T" + local format = "%A, %d %B "..nut.config.get("year") .. nut.config.get("yearAppendix", "").." %T" self.time:SetText(L("curTime", nut.date.getFormatted(format))) self.time.Think = function(this) diff --git a/plugins/persistence.lua b/plugins/persistence.lua new file mode 100644 index 00000000..cf46adb0 --- /dev/null +++ b/plugins/persistence.lua @@ -0,0 +1,178 @@ +local PLUGIN = PLUGIN + +PLUGIN.name = "Persistence" +PLUGIN.desc = "Saves persisted entities through restarts." +PLUGIN.author = "Zoephix" + +-- Storage for persisted map entities +PLUGIN.entities = PLUGIN.entities or {} + +-- Entities which are blocked from interaction +PLUGIN.blacklist = PLUGIN.blacklist or { + [ "func_button" ] = true, + [ "class C_BaseEntity" ] = true, + [ "func_brush" ] = true, + [ "func_tracktrain" ] = true, + [ "func_door" ] = true, + [ "func_door_rotating" ] = true, + [ "prop_door_rotating" ] = true, + [ "prop_static" ] = true, + [ "prop_dynamic" ] = true, + [ "prop_physics_override" ] = true, +} + +properties.Add( "persist", { + MenuLabel = "#makepersistent", + Order = 400, + MenuIcon = "icon16/link.png", + + Filter = function( self, ent, ply ) + + if ( ent:IsPlayer() ) then return false end + if ( PLUGIN.blacklist[ent:GetClass()] ) then return false end + if ( !gamemode.Call( "CanProperty", ply, "persist", ent ) ) then return false end + + return !ent:getNetVar( "persistent", false ) + + end, + + Action = function( self, ent ) + + self:MsgStart() + net.WriteEntity( ent ) + self:MsgEnd() + + end, + + Receive = function( self, length, ply ) + + local ent = net.ReadEntity() + if ( !IsValid( ent ) ) then return end + if ( !self:Filter( ent, ply ) ) then return end + + ent:setNetVar( "persistent", true ) + + -- Register the entity + PLUGIN.entities[#PLUGIN.entities + 1] = ent + + -- Add new log + nut.log.add(ply, "persistedEntity", ent ) + end + +} ) + +properties.Add( "persist_end", { + MenuLabel = "#stoppersisting", + Order = 400, + MenuIcon = "icon16/link_break.png", + + Filter = function( self, ent, ply ) + + if ( ent:IsPlayer() ) then return false end + if ( !gamemode.Call( "CanProperty", ply, "persist", ent ) ) then return false end + + return ent:getNetVar( "persistent", false ) + + end, + + Action = function( self, ent ) + + self:MsgStart() + net.WriteEntity( ent ) + self:MsgEnd() + + end, + + Receive = function( self, length, ply ) + + local ent = net.ReadEntity() + if ( !IsValid( ent ) ) then return end + if ( !properties.CanBeTargeted( ent, ply ) ) then return end + if ( !self:Filter( ent, ply ) ) then return end + + ent:setNetVar( "persistent", false ) + + -- Remove entity from registration + for k, v in ipairs(PLUGIN.entities) do + if (v == entity) then + PLUGIN.entities[k] = nil + + break + end + end + + -- Add new log + nut.log.add(ply, "unpersistedEntity", ent ) + end + +} ) + +if (SERVER) then + nut.log.addType("persistedEntity", function(client, entity) + return string.format("%s has persisted '%s'.", client:Name(), entity) + end) + + nut.log.addType("unpersistedEntity", function(client, entity) + return string.format("%s has removed persistence from '%s'.", client:Name(), entity) + end) + + -- Prevent from picking up persisted entities + function PLUGIN:PhysgunPickup(client, entity) + if (entity:getNetVar("persistent", false)) then + return false + end + end + + function PLUGIN:SaveData() + local data = {} + + for k, v in ipairs(self.entities) do + if (IsValid(v)) then + local entData = {} + entData.class = v:GetClass() + entData.pos = v:GetPos() + entData.angles = v:GetAngles() + entData.model = v:GetModel() + entData.skin = v:GetSkin() + entData.color = v:GetColor() + entData.material = v:GetMaterial() + entData.bodygroups = v:GetBodyGroups() + + local physicsObject = v:GetPhysicsObject() + if (IsValid(physicsObject)) then + entData.moveable = physicsObject:IsMoveable() + end + + data[#data +1] = entData + end + end + self:setData(data) + end + + function PLUGIN:LoadData() + for k, v in pairs(self:getData() or {}) do + local ent = ents.Create(v.class) + ent:SetPos(v.pos) + ent:SetAngles(v.angles) + ent:SetModel(v.model) + ent:SetSkin(v.skin) + ent:SetColor(v.color) + ent:SetMaterial(v.material) + ent:Spawn() + ent:Activate() + + for _, data in pairs(v.bodygroups) do + ent:SetBodygroup(data.id, data.num) + end + + local physicsObject = ent:GetPhysicsObject() + if (IsValid(physicsObject)) then + physicsObject:EnableMotion(ent.moveable or false) + end + + ent:setNetVar("persistent", true) + + self.entities[#self.entities + 1] = ent + end + end +end