This commit is contained in:
lifestorm
2024-08-04 23:12:27 +03:00
parent 0e770b2b49
commit ba1fc01b16
7084 changed files with 2173495 additions and 14 deletions

View File

@@ -0,0 +1,362 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[
BLACK TEA ICON LIBRARY FOR NUTSCRIPT 1.1
The MIT License (MIT)
Copyright (c) 2017, Kyu Yeon Lee(Black Tea Za rebel1324)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so, subject
to the following conditions:
The above copyright notice and thispermission notice shall be included in all copies
or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
TL;DR: https://tldrlegal.com/license/mit-license
OK -
Commercial Use
Modify
Distribute
Sublicense
Private Use
NOT OK -
Hold Liable
MUST -
Include Copyright
Include License
]]--
--[[
Default Tables.
]]--
ikon = ikon or {}
ikon.cache = ikon.cache or {}
ikon.requestList = ikon.requestList or {}
ikon.dev = false
ikon.maxSize = 8 -- 8x8 (512^2) is max icon size.
IKON_BUSY = 1
IKON_PROCESSING = 0
IKON_SOMETHINGWRONG = -1
local schemaName = schemaName or (Schema and Schema.folder)
--[[
Initialize hooks and RT Screens.
returns nothing
]]--
function ikon:init()
if (self.dev) then
hook.Add("HUDPaint", "ikon_dev2", ikon.showResult)
else
hook.Remove("HUDPaint", "ikon_dev2")
end
--[[
Being good at gmod is knowing all of stinky hacks
- Black Tea (2017)
]]--
ikon.haloAdd = ikon.haloAdd or halo.Add
function halo.Add(...)
if (ikon.rendering != true) then
ikon.haloAdd(...)
end
end
ikon.haloRender = ikon.haloRender or halo.Render
function halo.Render(...)
if (ikon.rendering != true) then
ikon.haloRender(...)
end
end
file.CreateDir("helix/icons")
file.CreateDir("helix/icons/" .. schemaName)
end
--[[
IKON Library Essential Material/Texture Declare
]]--
local TEXTURE_FLAGS_CLAMP_S = 0x0004
local TEXTURE_FLAGS_CLAMP_T = 0x0008
ikon.max = ikon.maxSize * 64
ikon.RT = GetRenderTargetEx("ixIconRendered",
ikon.max,
ikon.max,
RT_SIZE_NO_CHANGE,
MATERIAL_RT_DEPTH_SHARED,
bit.bor(TEXTURE_FLAGS_CLAMP_S, TEXTURE_FLAGS_CLAMP_T),
CREATERENDERTARGETFLAGS_UNFILTERABLE_OK,
IMAGE_FORMAT_RGBA8888
)
local tex_effect = GetRenderTarget("ixIconRenderedOutline", ikon.max, ikon.max)
local mat_outline = CreateMaterial("ixIconRenderedTemp", "UnlitGeneric", {
["$basetexture"] = tex_effect:GetName(),
["$translucent"] = 1
})
local lightPositions = {
BOX_TOP = Color(255, 255, 255),
BOX_FRONT = Color(255, 255, 255),
}
function ikon:renderHook()
local entity = ikon.renderEntity
if (halo.RenderedEntity() == entity) then
return
end
local w, h = ikon.curWidth * 64, ikon.curHeight * 64
local x, y = 0, 0
local tab
if (ikon.info) then
tab = {
origin = ikon.info.pos,
angles = ikon.info.ang,
fov = ikon.info.fov,
outline = ikon.info.outline,
outCol = ikon.info.outlineColor
}
if (!tab.origin and !tab.angles and !tab.fov) then
table.Merge(tab, PositionSpawnIcon(entity, entity:GetPos(), true))
end
else
tab = PositionSpawnIcon(entity, entity:GetPos(), true)
end
-- Taking MDave's Tip
xpcall(function()
render.OverrideAlphaWriteEnable(true, true) -- some playermodel eyeballs will not render without this
render.SetWriteDepthToDestAlpha(false)
render.OverrideBlend(true, BLEND_ONE, BLEND_ONE, BLENDFUNC_ADD, BLEND_ONE, BLEND_ONE, BLENDFUNC_ADD)
render.SuppressEngineLighting(true)
render.Clear(0, 0, 0, 0, true, true)
render.SetLightingOrigin(vector_origin)
render.ResetModelLighting(200 / 255, 200 / 255, 200 / 255)
render.SetColorModulation(1, 1, 1)
for i = 0, 6 do
local col = lightPositions[i]
if (col) then
render.SetModelLighting(i, col.r / 255, col.g / 255, col.b / 255)
end
end
if (tab.outline) then
ix.util.ResetStencilValues()
render.SetStencilEnable(true)
render.SetStencilWriteMask(137) -- yeah random number to avoid confliction
render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS)
render.SetStencilPassOperation(STENCILOPERATION_REPLACE)
render.SetStencilFailOperation(STENCILOPERATION_REPLACE)
end
--[[
Add more effects on the Models!
]]--
if (ikon.info and ikon.info.drawHook) then
ikon.info.drawHook(entity)
end
cam.Start3D(tab.origin, tab.angles, tab.fov, 0, 0, w, h)
render.SetBlend(1)
entity:DrawModel()
cam.End3D()
if (tab.outline) then
render.PushRenderTarget(tex_effect)
render.Clear(0, 0, 0, 0)
render.ClearDepth()
cam.Start2D()
cam.Start3D(tab.origin, tab.angles, tab.fov, 0, 0, w, h)
render.SetBlend(0)
entity:DrawModel()
render.SetStencilWriteMask(138) -- could you please?
render.SetStencilTestMask(1)
render.SetStencilReferenceValue(1)
render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL)
render.SetStencilPassOperation(STENCILOPERATION_KEEP)
render.SetStencilFailOperation(STENCILOPERATION_KEEP)
cam.Start2D()
surface.SetDrawColor(tab.outCol or color_white)
surface.DrawRect(0, 0, ScrW(), ScrH())
cam.End2D()
cam.End3D()
cam.End2D()
render.PopRenderTarget()
render.SetBlend(1)
render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_NOTEQUAL)
--[[
Thanks for Noiwex
NxServ.eu
]]--
cam.Start2D()
surface.SetMaterial(mat_outline)
surface.DrawTexturedRectUV(-2, 0, w, h, 0, 0, w / ikon.max, h / ikon.max)
surface.DrawTexturedRectUV(2, 0, w, h, 0, 0, w / ikon.max, h / ikon.max)
surface.DrawTexturedRectUV(0, 2, w, h, 0, 0, w / ikon.max, h / ikon.max)
surface.DrawTexturedRectUV(0, -2, w, h, 0, 0, w / ikon.max, h / ikon.max)
cam.End2D()
render.SetStencilEnable(false)
end
render.SuppressEngineLighting(false)
render.SetWriteDepthToDestAlpha(true)
render.OverrideAlphaWriteEnable(false)
end, function(message)
print(message)
end)
end
function ikon:showResult()
local x, y = ScrW() / 2, ScrH() / 2
local w, h = ikon.curWidth * 64, ikon.curHeight * 64
surface.SetDrawColor(255, 255, 255, 255)
surface.DrawOutlinedRect(x, 0, w, h)
surface.SetMaterial(mat_outline)
surface.DrawTexturedRect(x, 0, w, h)
end
--[[
Renders the Icon with given arguments.
returns nothing
]]--
function ikon:renderIcon(name, w, h, mdl, camInfo, updateCache)
if (#ikon.requestList > 0) then return IKON_BUSY end
if (ikon.requestList[name]) then return IKON_PROCESSING end
if (!w or !h or !mdl) then return IKON_SOMETHINGWRONG end
local capturedIcon
ikon.curWidth = w or 1
ikon.curHeight = h or 1
ikon.renderModel = mdl
if (camInfo) then
ikon.info = camInfo
end
local w, h = ikon.curWidth * 64, ikon.curHeight * 64
local sw, sh = ScrW(), ScrH()
if (ikon.renderModel) then
if (!IsValid(ikon.renderEntity)) then
ikon.renderEntity = ClientsideModel(ikon.renderModel, RENDERGROUP_BOTH)
ikon.renderEntity:SetNoDraw(true)
end
end
ikon.renderEntity:SetModel(ikon.renderModel)
local bone = ikon.renderEntity:LookupBone("ValveBiped.Bip01_Head1")
if (bone) then
ikon.renderEntity:SetEyeTarget(ikon.renderEntity:GetBonePosition(bone) + ikon.renderEntity:GetForward() * 32)
end
local oldRT = render.GetRenderTarget()
render.PushRenderTarget(ikon.RT)
ikon.rendering = true
ikon:renderHook()
ikon.rendering = nil
capturedIcon = render.Capture({
format = "png",
alpha = true,
x = 0,
y = 0,
w = w,
h = h
})
file.Write("helix/icons/" .. schemaName .. "/" .. name .. ".png", capturedIcon)
ikon.info = nil
render.PopRenderTarget()
if (updateCache) then
local materialID = tostring(os.time())
file.Write(materialID .. ".png", capturedIcon)
timer.Simple(0, function()
local material = Material("../data/".. materialID ..".png")
ikon.cache[name] = material
file.Delete(materialID .. ".png")
end)
end
ikon.requestList[name] = nil
return true
end
--[[
Gets rendered icon with given unique name.
returns IMaterial
]]--
function ikon:GetIcon(name)
if (ikon.cache[name]) then
return ikon.cache[name] -- yeah return cache
end
if (file.Exists("helix/icons/" .. schemaName .. "/" .. name .. ".png", "DATA")) then
ikon.cache[name] = Material("../data/helix/icons/" .. schemaName .. "/".. name ..".png")
return ikon.cache[name] -- yeah return cache
else
return false -- retryd
end
end
concommand.Add("ix_flushicon", function()
local root = "helix/icons/" .. schemaName
for _, v in pairs(file.Find(root .. "/*.png", "DATA")) do
file.Delete(root .. "/" .. v)
end
ikon.cache = {}
end)
hook.Add("InitializedSchema", "updatePath", function()
schemaName = Schema.folder
ikon:init()
end)
if (schemaName) then
ikon:init()
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,560 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[
CAMI - Common Admin Mod Interface.
Makes admin mods intercompatible and provides an abstract privilege interface
for third party addons.
Follows the specification on this page:
https://docs.google.com/document/d/1QIRVcAgZfAYf1aBl_dNV_ewR6P25wze2KmUVzlbFgMI
Structures:
CAMI_USERGROUP, defines the charactaristics of a usergroup:
{
Name
string
The name of the usergroup
Inherits
string
The name of the usergroup this usergroup inherits from
}
CAMI_PRIVILEGE, defines the charactaristics of a privilege:
{
Name
string
The name of the privilege
MinAccess
string
One of the following three: user/admin/superadmin
Description
string
optional
A text describing the purpose of the privilege
HasAccess
function(
privilege :: CAMI_PRIVILEGE,
actor :: Player,
target :: Player
) :: bool
optional
Function that decides whether a player can execute this privilege,
optionally on another player (target).
}
]]
-- Version number in YearMonthDay format.
local version = 20190102
if CAMI and CAMI.Version >= version then return end
CAMI = CAMI or {}
CAMI.Version = version
--[[
usergroups
Contains the registered CAMI_USERGROUP usergroup structures.
Indexed by usergroup name.
]]
local usergroups = CAMI.GetUsergroups and CAMI.GetUsergroups() or {
user = {
Name = "user",
Inherits = "user"
},
admin = {
Name = "admin",
Inherits = "user"
},
superadmin = {
Name = "superadmin",
Inherits = "admin"
}
}
--[[
privileges
Contains the registered CAMI_PRIVILEGE privilege structures.
Indexed by privilege name.
]]
local privileges = CAMI.GetPrivileges and CAMI.GetPrivileges() or {}
--[[
CAMI.RegisterUsergroup
Registers a usergroup with CAMI.
Parameters:
usergroup
CAMI_USERGROUP
(see CAMI_USERGROUP structure)
source
any
Identifier for your own admin mod. Can be anything.
Use this to make sure CAMI.RegisterUsergroup function and the
CAMI.OnUsergroupRegistered hook don't cause an infinite loop
Return value:
CAMI_USERGROUP
The usergroup given as argument.
]]
function CAMI.RegisterUsergroup(usergroup, source)
usergroups[usergroup.Name] = usergroup
hook.Call("CAMI.OnUsergroupRegistered", nil, usergroup, source)
return usergroup
end
--[[
CAMI.UnregisterUsergroup
Unregisters a usergroup from CAMI. This will call a hook that will notify
all other admin mods of the removal.
Call only when the usergroup is to be permanently removed.
Parameters:
usergroupName
string
The name of the usergroup.
source
any
Identifier for your own admin mod. Can be anything.
Use this to make sure CAMI.UnregisterUsergroup function and the
CAMI.OnUsergroupUnregistered hook don't cause an infinite loop
Return value:
bool
Whether the unregistering succeeded.
]]
function CAMI.UnregisterUsergroup(usergroupName, source)
if not usergroups[usergroupName] then return false end
local usergroup = usergroups[usergroupName]
usergroups[usergroupName] = nil
hook.Call("CAMI.OnUsergroupUnregistered", nil, usergroup, source)
return true
end
--[[
CAMI.GetUsergroups
Retrieves all registered usergroups.
Return value:
Table of CAMI_USERGROUP, indexed by their names.
]]
function CAMI.GetUsergroups()
return usergroups
end
--[[
CAMI.GetUsergroup
Receives information about a usergroup.
Return value:
CAMI_USERGROUP
Returns nil when the usergroup does not exist.
]]
function CAMI.GetUsergroup(usergroupName)
return usergroups[usergroupName]
end
--[[
CAMI.UsergroupInherits
Returns true when usergroupName1 inherits usergroupName2.
Note that usergroupName1 does not need to be a direct child.
Every usergroup trivially inherits itself.
Parameters:
usergroupName1
string
The name of the usergroup that is queried.
usergroupName2
string
The name of the usergroup of which is queried whether usergroupName
inherits from.
Return value:
bool
Whether usergroupName1 inherits usergroupName2.
]]
function CAMI.UsergroupInherits(usergroupName1, usergroupName2)
repeat
if usergroupName1 == usergroupName2 then return true end
usergroupName1 = usergroups[usergroupName1] and
usergroups[usergroupName1].Inherits or
usergroupName1
until not usergroups[usergroupName1] or
usergroups[usergroupName1].Inherits == usergroupName1
-- One can only be sure the usergroup inherits from user if the
-- usergroup isn't registered.
return usergroupName1 == usergroupName2 or usergroupName2 == "user"
end
--[[
CAMI.InheritanceRoot
All usergroups must eventually inherit either user, admin or superadmin.
Regardless of what inheritance mechism an admin may or may not have, this
always applies.
This method always returns either user, admin or superadmin, based on what
usergroups eventually inherit.
Parameters:
usergroupName
string
The name of the usergroup of which the root of inheritance is
requested
Return value:
string
The name of the root usergroup (either user, admin or superadmin)
]]
function CAMI.InheritanceRoot(usergroupName)
if not usergroups[usergroupName] then return end
local inherits = usergroups[usergroupName].Inherits
while inherits ~= usergroups[usergroupName].Inherits do
usergroupName = usergroups[usergroupName].Inherits
end
return usergroupName
end
--[[
CAMI.RegisterPrivilege
Registers a privilege with CAMI.
Note: do NOT register all your admin mod's privileges with this function!
This function is for third party addons to register privileges
with admin mods, not for admin mods sharing the privileges amongst one
another.
Parameters:
privilege
CAMI_PRIVILEGE
See CAMI_PRIVILEGE structure.
Return value:
CAMI_PRIVILEGE
The privilege given as argument.
]]
function CAMI.RegisterPrivilege(privilege)
privileges[privilege.Name] = privilege
hook.Call("CAMI.OnPrivilegeRegistered", nil, privilege)
return privilege
end
--[[
CAMI.UnregisterPrivilege
Unregisters a privilege from CAMI. This will call a hook that will notify
all other admin mods of the removal.
Call only when the privilege is to be permanently removed.
Parameters:
privilegeName
string
The name of the privilege.
Return value:
bool
Whether the unregistering succeeded.
]]
function CAMI.UnregisterPrivilege(privilegeName)
if not privileges[privilegeName] then return false end
local privilege = privileges[privilegeName]
privileges[privilegeName] = nil
hook.Call("CAMI.OnPrivilegeUnregistered", nil, privilege)
return true
end
--[[
CAMI.GetPrivileges
Retrieves all registered privileges.
Return value:
Table of CAMI_PRIVILEGE, indexed by their names.
]]
function CAMI.GetPrivileges()
return privileges
end
--[[
CAMI.GetPrivilege
Receives information about a privilege.
Return value:
CAMI_PRIVILEGE when the privilege exists.
nil when the privilege does not exist.
]]
function CAMI.GetPrivilege(privilegeName)
return privileges[privilegeName]
end
--[[
CAMI.PlayerHasAccess
Queries whether a certain player has the right to perform a certain action.
Parameters:
actorPly
Player
The player of which is requested whether they have the privilege.
privilegeName
string
The name of the privilege.
callback
function(bool, string) or nil
This function will be called with the answer. The bool signifies the
yes or no answer as to whether the player is allowed. The string
will optionally give a reason.
Give an explicit nil here to get an answer immediately
Important note: May throw an error when the admin mod doesn't
give an answer immediately!
targetPly
Optional.
The player on which the privilege is executed.
extraInfoTbl
Optional.
Table containing extra information.
Officially supported members:
Fallback
string
Either of user/admin/superadmin. When no admin mod replies,
the decision is based on the admin status of the user.
Defaults to admin if not given.
IgnoreImmunity
bool
Ignore any immunity mechanisms an admin mod might have.
CommandArguments
table
Extra arguments that were given to the privilege command.
Return value:
If callback is specified:
None
Otherwise:
hasAccess
bool
Whether the player has access
reason
Optional.
The reason why a player does or does not have access.
]]
-- Default access handler
local defaultAccessHandler = {["CAMI.PlayerHasAccess"] =
function(_, actorPly, privilegeName, callback, _, extraInfoTbl)
-- The server always has access in the fallback
if not IsValid(actorPly) then return callback(true, "Fallback.") end
local priv = privileges[privilegeName]
local fallback = extraInfoTbl and (
not extraInfoTbl.Fallback and actorPly:IsAdmin() or
extraInfoTbl.Fallback == "user" and true or
extraInfoTbl.Fallback == "admin" and actorPly:IsAdmin() or
extraInfoTbl.Fallback == "superadmin" and actorPly:IsSuperAdmin())
if not priv then return callback(fallback, "Fallback.") end
callback(
priv.MinAccess == "user" or
priv.MinAccess == "admin" and actorPly:IsAdmin() or
priv.MinAccess == "superadmin" and actorPly:IsSuperAdmin()
, "Fallback.")
end,
["CAMI.SteamIDHasAccess"] =
function(_, _, _, callback)
callback(false, "No information available.")
end
}
function CAMI.PlayerHasAccess(actorPly, privilegeName, callback, targetPly,
extraInfoTbl)
local hasAccess, reason = nil, nil
local callback_ = callback or function(hA, r) hasAccess, reason = hA, r end
hook.Call("CAMI.PlayerHasAccess", defaultAccessHandler, actorPly,
privilegeName, callback_, targetPly, extraInfoTbl)
if callback ~= nil then return end
if hasAccess == nil then
local err = [[The function CAMI.PlayerHasAccess was used to find out
whether Player %s has privilege "%s", but an admin mod did not give an
immediate answer!]]
error(string.format(err,
actorPly:IsPlayer() and actorPly:Nick() or tostring(actorPly),
privilegeName))
end
return hasAccess, reason
end
--[[
CAMI.GetPlayersWithAccess
Finds the list of currently joined players who have the right to perform a
certain action.
NOTE: this function will NOT return an immediate result!
The result is in the callback!
Parameters:
privilegeName
string
The name of the privilege.
callback
function(players)
This function will be called with the list of players with access.
targetPly
Optional.
The player on which the privilege is executed.
extraInfoTbl
Optional.
Table containing extra information.
Officially supported members:
Fallback
string
Either of user/admin/superadmin. When no admin mod replies,
the decision is based on the admin status of the user.
Defaults to admin if not given.
IgnoreImmunity
bool
Ignore any immunity mechanisms an admin mod might have.
CommandArguments
table
Extra arguments that were given to the privilege command.
]]
function CAMI.GetPlayersWithAccess(privilegeName, callback, targetPly,
extraInfoTbl)
local allowedPlys = {}
local allPlys = player.GetAll()
local countdown = #allPlys
local function onResult(ply, hasAccess, _)
countdown = countdown - 1
if hasAccess then table.insert(allowedPlys, ply) end
if countdown == 0 then callback(allowedPlys) end
end
for _, ply in ipairs(allPlys) do
CAMI.PlayerHasAccess(ply, privilegeName,
function(...) onResult(ply, ...) end,
targetPly, extraInfoTbl)
end
end
--[[
CAMI.SteamIDHasAccess
Queries whether a player with a steam ID has the right to perform a certain
action.
Note: the player does not need to be in the server for this to
work.
Note: this function does NOT return an immediate result!
The result is in the callback!
Parameters:
actorSteam
Player
The SteamID of the player of which is requested whether they have
the privilege.
privilegeName
string
The name of the privilege.
callback
function(bool, string)
This function will be called with the answer. The bool signifies the
yes or no answer as to whether the player is allowed. The string
will optionally give a reason.
targetSteam
Optional.
The SteamID of the player on which the privilege is executed.
extraInfoTbl
Optional.
Table containing extra information.
Officially supported members:
IgnoreImmunity
bool
Ignore any immunity mechanisms an admin mod might have.
CommandArguments
table
Extra arguments that were given to the privilege command.
Return value:
None, the answer is given in the callback function in order to allow
for the admin mod to perform e.g. a database lookup.
]]
function CAMI.SteamIDHasAccess(actorSteam, privilegeName, callback,
targetSteam, extraInfoTbl)
hook.Call("CAMI.SteamIDHasAccess", defaultAccessHandler, actorSteam,
privilegeName, callback, targetSteam, extraInfoTbl)
end
--[[
CAMI.SignalUserGroupChanged
Signify that your admin mod has changed the usergroup of a player. This
function communicates to other admin mods what it thinks the usergroup
of a player should be.
Listen to the hook to receive the usergroup changes of other admin mods.
Parameters:
ply
Player
The player for which the usergroup is changed
old
string
The previous usergroup of the player.
new
string
The new usergroup of the player.
source
any
Identifier for your own admin mod. Can be anything.
]]
function CAMI.SignalUserGroupChanged(ply, old, new, source)
hook.Call("CAMI.PlayerUsergroupChanged", nil, ply, old, new, source)
end
--[[
CAMI.SignalSteamIDUserGroupChanged
Signify that your admin mod has changed the usergroup of a disconnected
player. This communicates to other admin mods what it thinks the usergroup
of a player should be.
Listen to the hook to receive the usergroup changes of other admin mods.
Parameters:
ply
string
The steam ID of the player for which the usergroup is changed
old
string
The previous usergroup of the player.
new
string
The new usergroup of the player.
source
any
Identifier for your own admin mod. Can be anything.
]]
function CAMI.SignalSteamIDUserGroupChanged(steamId, old, new, source)
hook.Call("CAMI.SteamIDUsergroupChanged", nil, steamId, old, new, source)
end

View File

@@ -0,0 +1,791 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
---------------------------------------------------------------------------------------
-- Module for date and time calculations
--
-- Version 2.1.1
-- 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
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]
--[[ 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 pack = table.pack or function(...) return { n = select('#', ...), ... } end
local setmetatable = setmetatable
local getmetatable = getmetatable
--[[ EXTRA FUNCTIONS ]]--
local fmt = string.format
local lwr = string.lower
local upr = string.upper
local rep = string.rep
local len = string.len
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
-- rounds a number;
local function round(n, d) d=d^10 return floor((n*d)+.5)/d end
-- rounds a number to whole;
local function whole(n)return floor(n+.5)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
local function fret(x)return x;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 = 20010001 -- 2.1.1
--#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
-- is `v` a date object?
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, dw, dy = date_epoch:copy()
for i = 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 daynum and dayfrc
local function dvtotv(dn, df)
return fix(dn - DATE_EPOCH) * SECPERDAY + (df/1000)
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, k, x,v,c, chkfin, 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;
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 inlist(x, sl_weekdays, 2, sw) then
k = sw[0]
else
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(...)
local arg = pack(...)
local y, m, d = fix(arg[1]), getmontharg(arg[2]), fix(arg[3])
local h, r, s, t = tonumber(arg[4] or 0), tonumber(arg[5] or 0), tonumber(arg[6] or 0), tonumber(arg[7] 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
dobj.format = dobj.fmt
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(...)
local arg = pack(...)
if arg.n > 1 then return (date_from(...))
elseif arg.n == 0 then return (date_getdobj(false))
else local o, r = date_getdobj(arg[1]); 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

View File

@@ -0,0 +1,193 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local middleclass = {
_VERSION = 'middleclass v4.1.1',
_DESCRIPTION = 'Object Orientation for Lua',
_URL = 'https://github.com/kikito/middleclass',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2011 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
}
local function _createIndexWrapper(aClass, f)
if f == nil then
return aClass.__instanceDict
else
return function(self, name)
local value = aClass.__instanceDict[name]
if value ~= nil then
return value
elseif type(f) == "function" then
return (f(self, name))
else
return f[name]
end
end
end
end
local function _propagateInstanceMethod(aClass, name, f)
f = name == "__index" and _createIndexWrapper(aClass, f) or f
aClass.__instanceDict[name] = f
for subclass in pairs(aClass.subclasses) do
if rawget(subclass.__declaredMethods, name) == nil then
_propagateInstanceMethod(subclass, name, f)
end
end
end
local function _declareInstanceMethod(aClass, name, f)
aClass.__declaredMethods[name] = f
if f == nil and aClass.super then
f = aClass.super.__instanceDict[name]
end
_propagateInstanceMethod(aClass, name, f)
end
local function _tostring(self) return "class " .. self.name end
local function _call(self, ...) return self:New(...) end
local function _createClass(name, super)
local dict = {}
dict.__index = dict
local aClass = { name = name, super = super, static = {},
__instanceDict = dict, __declaredMethods = {},
subclasses = setmetatable({}, {__mode='k'}) }
if super then
setmetatable(aClass.static, {
__index = function(_,k)
local result = rawget(dict,k)
if result == nil then
return super.static[k]
end
return result
end
})
else
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end })
end
setmetatable(aClass, { __index = aClass.static, __tostring = _tostring,
__call = _call, __newindex = _declareInstanceMethod })
return aClass
end
local function _includeMixin(aClass, mixin)
assert(type(mixin) == 'table', "mixin must be a table")
for name,method in pairs(mixin) do
if name ~= "Included" and name ~= "static" then aClass[name] = method end
end
for name,method in pairs(mixin.static or {}) do
aClass.static[name] = method
end
if type(mixin.Included)=="function" then mixin:Included(aClass) end
return aClass
end
local DefaultMixin = {
__tostring = function(self) return "instance of " .. tostring(self.class) end,
Initialize = function(self, ...) end,
IsInstanceOf = function(self, aClass)
return type(aClass) == 'table'
and type(self) == 'table'
and (self.class == aClass
or type(self.class) == 'table'
and type(self.class.IsSubclassOf) == 'function'
and self.class:IsSubclassOf(aClass))
end,
static = {
Allocate = function(self)
assert(type(self) == 'table', "Make sure that you are using 'Class:Allocate' instead of 'Class.Allocate'")
return setmetatable({ class = self }, self.__instanceDict)
end,
New = function(self, ...)
assert(type(self) == 'table', "Make sure that you are using 'Class:New' instead of 'Class.New'")
local instance = self:Allocate()
instance:Initialize(...)
return instance
end,
Subclass = function(self, name)
assert(type(self) == 'table', "Make sure that you are using 'Class:Subclass' instead of 'Class.Subclass'")
assert(type(name) == "string", "You must provide a name(string) for your class")
local subclass = _createClass(name, self)
for methodName, f in pairs(self.__instanceDict) do
_propagateInstanceMethod(subclass, methodName, f)
end
subclass.Initialize = function(instance, ...) return self.Initialize(instance, ...) end
self.subclasses[subclass] = true
self:Subclassed(subclass)
return subclass
end,
Subclassed = function(self, other) end,
IsSubclassOf = function(self, other)
return type(other) == 'table' and
type(self.super) == 'table' and
( self.super == other or self.super:IsSubclassOf(other) )
end,
Include = function(self, ...)
assert(type(self) == 'table', "Make sure you that you are using 'Class:Include' instead of 'Class.Include'")
for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
return self
end
}
}
function middleclass.class(name, super)
assert(type(name) == 'string', "A name (string) is needed for the new class")
return super and super:Subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
end
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })
ix.middleclass = middleclass

View File

@@ -0,0 +1,174 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[
NetStream - 2.0.0
Alexander Grist-Hucker
http://www.revotech.org
Credits to:
thelastpenguin for pON.
https://github.com/thelastpenguin/gLUA-Library/tree/master/pON
--]]
AddCSLuaFile();
local _player = player
netstream = netstream or {};
netstream.stored = netstream.stored or {};
-- A function to split data for a data stream.
function netstream.Split(data)
local index = 1;
local result = {};
local buffer = {};
for i = 0, string.len(data) do
buffer[#buffer + 1] = string.sub(data, i, i);
if (#buffer == 32768) then
result[#result + 1] = table.concat(buffer);
index = index + 1;
buffer = {};
end;
end;
result[#result + 1] = table.concat(buffer);
return result;
end;
-- A function to hook a data stream.
function netstream.Hook(name, Callback)
netstream.stored[name] = Callback;
end;
if (SERVER) then
util.AddNetworkString("NetStreamDS");
-- A function to start a net stream.
function netstream.Start(player, name, ...)
local recipients = {};
local bShouldSend = false;
local bSendPVS = false;
if (type(player) != "table") then
if (!player) then
player = _player.GetAll();
elseif (type(player) == "Vector") then
bSendPVS = true;
else
player = {player};
end;
end;
if (type(player) != "Vector") then
for k, v in pairs(player) do
if (type(v) == "Player") then
recipients[#recipients + 1] = v;
bShouldSend = true;
elseif (type(k) == "Player") then
recipients[#recipients + 1] = k;
bShouldSend = true;
end;
end;
else
bShouldSend = true;
end;
local dataTable = {...};
local encodedData = pon.encode(dataTable);
if (encodedData and #encodedData > 0 and bShouldSend) then
net.Start("NetStreamDS");
net.WriteString(name);
net.WriteUInt(#encodedData, 32);
net.WriteData(encodedData, #encodedData);
if (bSendPVS) then
net.SendPVS(player);
else
net.Send(recipients);
end;
end;
end;
net.Receive("NetStreamDS", function(length, player)
local NS_DS_NAME = net.ReadString();
local NS_DS_LENGTH = net.ReadUInt(32);
local NS_DS_DATA = net.ReadData(NS_DS_LENGTH);
if (NS_DS_NAME and NS_DS_DATA and NS_DS_LENGTH) then
player.nsDataStreamName = NS_DS_NAME;
player.nsDataStreamData = "";
if (player.nsDataStreamName and player.nsDataStreamData) then
player.nsDataStreamData = NS_DS_DATA;
if (netstream.stored[player.nsDataStreamName]) then
local bStatus, value = pcall(pon.decode, player.nsDataStreamData);
if (bStatus) then
netstream.stored[player.nsDataStreamName](player, unpack(value));
else
ErrorNoHalt("NetStream: '"..NS_DS_NAME.."'\n"..value.."\n");
end;
else
ErrorNoHalt("NetStream: Undefined hook for '"..NS_DS_NAME.."'\n")
end;
player.nsDataStreamName = nil;
player.nsDataStreamData = nil;
end;
end;
NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH = nil, nil, nil;
end);
else
-- A function to start a net stream.
function netstream.Start(name, ...)
local dataTable = {...};
local encodedData = pon.encode(dataTable);
if (encodedData and #encodedData > 0) then
net.Start("NetStreamDS");
net.WriteString(name);
net.WriteUInt(#encodedData, 32);
net.WriteData(encodedData, #encodedData);
net.SendToServer();
end;
end;
net.Receive("NetStreamDS", function(length)
local NS_DS_NAME = net.ReadString();
local NS_DS_LENGTH = net.ReadUInt(32);
local NS_DS_DATA = net.ReadData(NS_DS_LENGTH);
if (NS_DS_NAME and NS_DS_DATA and NS_DS_LENGTH) then
if (netstream.stored[NS_DS_NAME]) then
local bStatus, value = pcall(pon.decode, NS_DS_DATA);
if (bStatus) then
netstream.stored[NS_DS_NAME](unpack(value));
else
ErrorNoHalt("NetStream: '"..NS_DS_NAME.."'\n"..value.."\n");
end;
else
ErrorNoHalt("NetSteam: Undefined hook for '"..NS_DS_NAME.."'\n")
end;
end;
NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH = nil, nil, nil;
end);
end;

View File

@@ -0,0 +1,411 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[
DEVELOPMENTAL VERSION;
VERSION 1.2.2
Copyright thelastpenguin™
You may use this for any purpose as long as:
- You don't remove this copyright notice.
- You don't claim this to be your own.
- You properly credit the author, thelastpenguin™, if you publish your work based on (and/or using) this.
If you modify the code for any purpose, the above still applies to the modified code.
The author is not held responsible for any damages incured from the use of pon, you use it at your own risk.
DATA TYPES SUPPORTED:
- tables - k,v - pointers
- strings - k,v - pointers
- numbers - k,v
- booleans- k,v
- Vectors - k,v
- Angles - k,v
- Entities- k,v
- Players - k,v
CHANGE LOG
V 1.1.0
- Added Vehicle, NPC, NextBot, Player, Weapon
V 1.2.0
- Added custom handling for k,v tables without any array component.
V 1.2.1
- fixed deserialization bug.
THANKS TO...
- VERCAS for the inspiration.
]]
local pon = {};
_G.pon = pon;
local type, count = type, table.Count ;
local tonumber = tonumber ;
local format = string.format;
do
local type, count = type, table.Count ;
local tonumber = tonumber ;
local format = string.format;
local encode = {};
local tryCache ;
local cacheSize = 0;
encode['table'] = function( self, tbl, output, cache )
if( cache[ tbl ] )then
output[ #output + 1 ] = format('(%x)', cache[tbl] );
return ;
else
cacheSize = cacheSize + 1;
cache[ tbl ] = cacheSize;
end
local first = next(tbl, nil)
local predictedNumeric = 1
local lastKey = nil
-- starts with a numeric dealio
if first == 1 then
output[#output + 1] = '{'
for k,v in next, tbl do
if k == predictedNumeric then
predictedNumeric = predictedNumeric + 1
local tv = type(v)
if tv == 'string' then
local pid = cache[v]
if pid then
output[#output + 1] = format('(%x)', pid)
else
cacheSize = cacheSize + 1
cache[v] = cacheSize
self.string(self, v, output, cache)
end
else
self[tv](self, v, output, cache)
end
else
break
end
end
predictedNumeric = predictedNumeric - 1
else
predictedNumeric = nil
end
if predictedNumeric == nil then
output[#output + 1] = '[' -- no array component
else
output[#output + 1] = '~' -- array component came first so shit needs to happen
end
for k, v in next, tbl, predictedNumeric do
local tk, tv = type(k), type(v)
-- WRITE KEY
if tk == 'string' then
local pid = cache[ k ];
if( pid )then
output[ #output + 1 ] = format('(%x)', pid );
else
cacheSize = cacheSize + 1;
cache[ k ] = cacheSize;
self.string( self, k, output, cache );
end
else
self[tk](self, k, output, cache)
end
-- WRITE VALUE
if( tv == 'string' )then
local pid = cache[ v ];
if( pid )then
output[ #output + 1 ] = format('(%x)', pid );
else
cacheSize = cacheSize + 1;
cache[ v ] = cacheSize;
self.string( self, v, output, cache );
end
else
self[ tv ]( self, v, output, cache );
end
end
output[#output + 1] = '}'
end
-- ENCODE STRING
local gsub = string.gsub ;
encode['string'] = function( self, str, output )
--if tryCache( str, output ) then return end
local estr, count = gsub( str, ";", "\\;");
if( count == 0 )then
output[ #output + 1 ] = '\''..str..';';
else
output[ #output + 1 ] = '"'..estr..'";';
end
end
-- ENCODE NUMBER
encode['number'] = function( self, num, output )
if num%1 == 0 then
if num < 0 then
output[ #output + 1 ] = format( 'x%x;', -num );
else
output[ #output + 1 ] = format('X%x;', num );
end
else
output[ #output + 1 ] = tonumber( num )..';';
end
end
-- ENCODE BOOLEAN
encode['boolean'] = function( self, val, output )
output[ #output + 1 ] = val and 't' or 'f'
end
-- ENCODE VECTOR
encode['Vector'] = function( self, val, output )
output[ #output + 1 ] = ('v'..val.x..','..val.y)..(','..val.z..';');
end
-- ENCODE ANGLE
encode['Angle'] = function( self, val, output )
output[ #output + 1 ] = ('a'..val.p..','..val.y)..(','..val.r..';');
end
encode['Entity'] = function( self, val, output )
output[ #output + 1] = 'E'..(IsValid( val ) and (val:EntIndex( )..';') or '#');
end
encode['Player'] = encode['Entity'];
encode['Vehicle'] = encode['Entity'];
encode['Weapon'] = encode['Entity'];
encode['NPC'] = encode['Entity'];
encode['NextBot'] = encode['Entity'];
encode['PhysObj'] = encode['Entity'];
encode['nil'] = function()
output[ #output + 1 ] = '?';
end
encode.__index = function( key )
ErrorNoHalt('Type: '..key..' can not be encoded. Encoded as as pass-over value.');
return encode['nil'];
end
do
local empty, concat = table.Empty, table.concat ;
function pon.encode( tbl )
local output = {};
cacheSize = 0;
encode[ 'table' ]( encode, tbl, output, {} );
local res = concat( output );
return res;
end
end
end
do
local tonumber = tonumber ;
local find, sub, gsub, Explode = string.find, string.sub, string.gsub, string.Explode ;
local Vector, Angle, Entity = Vector, Angle, Entity ;
local decode = {};
decode['{'] = function( self, index, str, cache )
local cur = {};
cache[ #cache + 1 ] = cur;
local k, v, tk, tv = 1, nil, nil, nil;
while( true )do
tv = sub( str, index, index );
if( not tv or tv == '~' )then
index = index + 1;
break ;
end
if( tv == '}' )then
return index + 1, cur;
end
-- READ THE VALUE
index = index + 1;
index, v = self[ tv ]( self, index, str, cache );
cur[ k ] = v;
k = k + 1;
end
while( true )do
tk = sub( str, index, index );
if( not tk or tk == '}' )then
index = index + 1;
break ;
end
-- READ THE KEY
index = index + 1;
index, k = self[ tk ]( self, index, str, cache );
-- READ THE VALUE
tv = sub( str, index, index );
index = index + 1;
index, v = self[ tv ]( self, index, str, cache );
cur[ k ] = v;
end
return index, cur;
end
decode['['] = function( self, index, str, cache )
local cur = {};
cache[ #cache + 1 ] = cur;
local k, v, tk, tv = 1, nil, nil, nil;
while( true )do
tk = sub( str, index, index );
if( not tk or tk == '}' )then
index = index + 1;
break ;
end
-- READ THE KEY
index = index + 1;
index, k = self[ tk ]( self, index, str, cache );
if not k then continue end
-- READ THE VALUE
tv = sub( str, index, index );
index = index + 1;
if not self[tv] then
print('did not find type: '..tv)
end
index, v = self[ tv ]( self, index, str, cache );
cur[ k ] = v;
end
return index, cur;
end
-- STRING
decode['"'] = function( self, index, str, cache )
local finish = find( str, '";', index, true );
local res = gsub( sub( str, index, finish - 1 ), '\\;', ';' );
index = finish + 2;
cache[ #cache + 1 ] = res;
return index, res;
end
-- STRING NO ESCAPING NEEDED
decode['\''] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local res = sub( str, index, finish - 1 )
index = finish + 1;
cache[ #cache + 1 ] = res;
return index, res;
end
-- NUMBER
decode['n'] = function( self, index, str, cache )
index = index - 1;
local finish = find( str, ';', index, true );
local num = tonumber( sub( str, index, finish - 1 ) );
index = finish + 1;
return index, num;
end
decode['0'] = decode['n'];
decode['1'] = decode['n'];
decode['2'] = decode['n'];
decode['3'] = decode['n'];
decode['4'] = decode['n'];
decode['5'] = decode['n'];
decode['6'] = decode['n'];
decode['7'] = decode['n'];
decode['8'] = decode['n'];
decode['9'] = decode['n'];
decode['-'] = decode['n'];
-- positive hex
decode['X'] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local num = tonumber( sub( str, index, finish - 1), 16 );
index = finish + 1;
return index, num;
end
-- negative hex
decode['x'] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local num = -tonumber( sub( str, index, finish - 1), 16 );
index = finish + 1;
return index, num;
end
-- POINTER
decode['('] = function( self, index, str, cache )
local finish = find( str, ')', index, true );
local num = tonumber( sub( str, index, finish - 1), 16 );
index = finish + 1;
return index, cache[ num ];
end
-- BOOLEAN. ONE DATA TYPE FOR YES, ANOTHER FOR NO.
decode[ 't' ] = function( self, index )
return index, true;
end
decode[ 'f' ] = function( self, index )
return index, false;
end
-- VECTOR
decode[ 'v' ] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local vecStr = sub( str, index, finish - 1 );
index = finish + 1; -- update the index.
local segs = Explode( ',', vecStr, false );
return index, Vector( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) );
end
-- ANGLE
decode[ 'a' ] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local angStr = sub( str, index, finish - 1 );
index = finish + 1; -- update the index.
local segs = Explode( ',', angStr, false );
return index, Angle( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) );
end
-- ENTITY
decode[ 'E' ] = function( self, index, str, cache )
if( str[index] == '#' )then
index = index + 1;
return index, NULL ;
else
local finish = find( str, ';', index, true );
local num = tonumber( sub( str, index, finish - 1 ) );
index = finish + 1;
return index, Entity( num );
end
end
-- PLAYER
decode[ 'P' ] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local num = tonumber( sub( str, index, finish - 1 ) );
index = finish + 1;
return index, Entity( num ) or NULL;
end
-- NIL
decode['?'] = function( self, index, str, cache )
return index + 1, nil;
end
function pon.decode( data )
local _, res = decode[sub(data,1,1)]( decode, 2, data, {});
return res;
end
end

View File

@@ -0,0 +1,385 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local tween = {
_VERSION = 'tween 2.1.1',
_DESCRIPTION = 'tweening for lua',
_URL = 'https://github.com/kikito/tween.lua',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
}
-- easing
-- Adapted from https://github.com/EmmanuelOga/easing. See LICENSE.txt for credits.
-- For all easing functions:
-- t = time == how much time has to pass for the tweening to complete
-- b = begin == starting property value
-- c = change == ending - beginning
-- d = duration == running time. How much time has passed *right now*
local pow, sin, cos, pi, sqrt, abs, asin = math.pow, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin
-- linear
local function linear(t, b, c, d) return c * t / d + b end
-- quad
local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end
local function outQuad(t, b, c, d)
t = t / d
return -c * t * (t - 2) + b
end
local function inOutQuad(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 2) + b end
return -c / 2 * ((t - 1) * (t - 3) - 1) + b
end
local function outInQuad(t, b, c, d)
if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end
return inQuad((t * 2) - d, b + c / 2, c / 2, d)
end
-- cubic
local function inCubic (t, b, c, d) return c * pow(t / d, 3) + b end
local function outCubic(t, b, c, d) return c * (pow(t / d - 1, 3) + 1) + b end
local function inOutCubic(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * t * t * t + b end
t = t - 2
return c / 2 * (t * t * t + 2) + b
end
local function outInCubic(t, b, c, d)
if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end
return inCubic((t * 2) - d, b + c / 2, c / 2, d)
end
-- quart
local function inQuart(t, b, c, d) return c * pow(t / d, 4) + b end
local function outQuart(t, b, c, d) return -c * (pow(t / d - 1, 4) - 1) + b end
local function inOutQuart(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 4) + b end
return -c / 2 * (pow(t - 2, 4) - 2) + b
end
local function outInQuart(t, b, c, d)
if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end
return inQuart((t * 2) - d, b + c / 2, c / 2, d)
end
-- quint
local function inQuint(t, b, c, d) return c * pow(t / d, 5) + b end
local function outQuint(t, b, c, d) return c * (pow(t / d - 1, 5) + 1) + b end
local function inOutQuint(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 5) + b end
return c / 2 * (pow(t - 2, 5) + 2) + b
end
local function outInQuint(t, b, c, d)
if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end
return inQuint((t * 2) - d, b + c / 2, c / 2, d)
end
-- sine
local function inSine(t, b, c, d) return -c * cos(t / d * (pi / 2)) + c + b end
local function outSine(t, b, c, d) return c * sin(t / d * (pi / 2)) + b end
local function inOutSine(t, b, c, d) return -c / 2 * (cos(pi * t / d) - 1) + b end
local function outInSine(t, b, c, d)
if t < d / 2 then return outSine(t * 2, b, c / 2, d) end
return inSine((t * 2) -d, b + c / 2, c / 2, d)
end
-- expo
local function inExpo(t, b, c, d)
if t == 0 then return b end
return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001
end
local function outExpo(t, b, c, d)
if t == d then return b + c end
return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b
end
local function inOutExpo(t, b, c, d)
if t == 0 then return b end
if t == d then return b + c end
t = t / d * 2
if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end
return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b
end
local function outInExpo(t, b, c, d)
if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end
return inExpo((t * 2) - d, b + c / 2, c / 2, d)
end
-- circ
local function inCirc(t, b, c, d) return(-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) end
local function outCirc(t, b, c, d) return(c * sqrt(1 - pow(t / d - 1, 2)) + b) end
local function inOutCirc(t, b, c, d)
t = t / d * 2
if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end
t = t - 2
return c / 2 * (sqrt(1 - t * t) + 1) + b
end
local function outInCirc(t, b, c, d)
if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end
return inCirc((t * 2) - d, b + c / 2, c / 2, d)
end
-- elastic
local function calculatePAS(p,a,c,d)
p, a = p or d * 0.3, a or 0
if a < abs(c) then return p, c, p / 4 end -- p, a, s
return p, a, p / (2 * pi) * asin(c/a) -- p,a,s
end
local function inElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
t = t - 1
return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
end
local function outElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b
end
local function inOutElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d * 2
if t == 2 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
t = t - 1
if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b
end
local function outInElastic(t, b, c, d, a, p)
if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end
return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p)
end
-- back
local function inBack(t, b, c, d, s)
s = s or 1.70158
t = t / d
return c * t * t * ((s + 1) * t - s) + b
end
local function outBack(t, b, c, d, s)
s = s or 1.70158
t = t / d - 1
return c * (t * t * ((s + 1) * t + s) + 1) + b
end
local function inOutBack(t, b, c, d, s)
s = (s or 1.70158) * 1.525
t = t / d * 2
if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end
t = t - 2
return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b
end
local function outInBack(t, b, c, d, s)
if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end
return inBack((t * 2) - d, b + c / 2, c / 2, d, s)
end
-- bounce
local function outBounce(t, b, c, d)
t = t / d
if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end
if t < 2 / 2.75 then
t = t - (1.5 / 2.75)
return c * (7.5625 * t * t + 0.75) + b
elseif t < 2.5 / 2.75 then
t = t - (2.25 / 2.75)
return c * (7.5625 * t * t + 0.9375) + b
end
t = t - (2.625 / 2.75)
return c * (7.5625 * t * t + 0.984375) + b
end
local function inBounce(t, b, c, d) return c - outBounce(d - t, 0, c, d) + b end
local function inOutBounce(t, b, c, d)
if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end
return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b
end
local function outInBounce(t, b, c, d)
if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end
return inBounce((t * 2) - d, b + c / 2, c / 2, d)
end
tween.easing = {
linear = linear,
inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad,
inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic,
inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart,
inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint,
inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine,
inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo,
inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc,
inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic,
inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack,
inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce
}
-- private stuff
local function copyTables(destination, keysTable, valuesTable)
valuesTable = valuesTable or keysTable
local mt = getmetatable(keysTable)
if mt and getmetatable(destination) == nil then
setmetatable(destination, mt)
end
for k,v in pairs(keysTable) do
if type(v) == 'table' then
destination[k] = copyTables({}, v, valuesTable[k])
else
destination[k] = valuesTable[k]
end
end
return destination
end
local function checkSubjectAndTargetRecursively(subject, target, path)
path = path or {}
local newPath
for k,targetValue in pairs(target) do
newPath = copyTables({}, path)
table.insert(newPath, tostring(k))
if isnumber(targetValue) then
assert(isnumber(subject[k]), "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number")
elseif istable(targetValue) then
checkSubjectAndTargetRecursively(subject[k], targetValue, newPath)
else
assert(isnumber(targetValue), "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers")
end
end
end
local function checkNewParams(duration, subject, target, easing)
assert(isnumber(duration) and duration > 0, "duration must be a positive number. Was " .. tostring(duration))
assert(istable(target), "target must be a table. Was " .. tostring(target))
assert(isfunction(easing), "easing must be a function. Was " .. tostring(easing))
checkSubjectAndTargetRecursively(subject, target)
end
local function getEasingFunction(easing)
easing = easing or "linear"
if isstring(easing) then
local name = easing
easing = tween.easing[name]
if not isfunction(easing) then
error("The easing function name '" .. name .. "' is invalid")
end
end
return easing
end
local function performEasingOnSubject(subject, target, initial, clock, duration, easing)
local t,b,c,d
for k,v in pairs(target) do
if istable(v) then
performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing)
else
t,b,c,d = clock, initial[k], v - initial[k], duration
subject[k] = easing(t,b,c,d)
end
end
end
local function applyValues(subject, target)
for k, v in pairs(target) do
if (istable(v)) then
applyValues(subject[k], v)
else
subject[k] = v
end
end
end
-- Tween methods
local Tween = {}
local Tween_mt = {__index = Tween}
function Tween:set(clock)
assert(isnumber(clock), "clock must be a positive number or 0")
self.initial = self.initial or copyTables({}, self.target, self.subject)
self.clock = clock
if self.clock <= 0 then
self.clock = 0
applyValues(self.subject, self.initial)
elseif self.clock >= self.duration then -- the tween has expired
self.clock = self.duration
applyValues(self.subject, self.target)
else
performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing)
end
return self.clock >= self.duration
end
function Tween:reset()
return self:set(0)
end
function Tween:update(dt)
assert(isnumber(dt), "dt must be a number")
return self:set(self.clock + dt)
end
-- Public interface
function tween.new(duration, subject, target, easing)
easing = getEasingFunction(easing)
checkNewParams(duration, subject, target, easing)
return setmetatable({
duration = duration,
subject = subject,
target = target,
easing = easing,
clock = 0
}, Tween_mt)
end
ix.tween = tween

View File

@@ -0,0 +1,338 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- $Id: utf8.lua 179 2009-04-03 18:10:03Z pasta $
--
-- Provides UTF-8 aware string functions implemented in pure lua:
-- * string.utf8len(s)
-- * string.utf8sub(s, i, j)
-- * string.utf8reverse(s)
--
-- If utf8data.lua (containing the lower<->upper case mappings) is loaded, these
-- additional functions are available:
-- * string.utf8upper(s)
-- * string.utf8lower(s)
--
-- All functions behave as their non UTF-8 aware counterparts with the exception
-- that UTF-8 characters are used instead of bytes for all units.
--[[
Copyright (c) 2006-2007, Kyle Smith
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--]]
-- ABNF from RFC 3629
--
-- UTF8-octets = *( UTF8-char )
-- UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
-- UTF8-1 = %x00-7F
-- UTF8-2 = %xC2-DF UTF8-tail
-- UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
-- %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
-- UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
-- %xF4 %x80-8F 2( UTF8-tail )
-- UTF8-tail = %x80-BF
--
ix.util.Include("data/sh_utf8_casemap.lua")
-- returns the number of bytes used by the UTF-8 character at byte i in s
-- also doubles as a UTF-8 character validator
local function utf8charbytes (s, i)
-- argument defaults
i = i or 1
-- argument checking
if not isstring(s) then
error("bad argument #1 to 'utf8charbytes' (string expected, got ".. type(s).. ")")
end
if not isnumber(i) then
error("bad argument #2 to 'utf8charbytes' (number expected, got ".. type(i).. ")")
end
local c = s:byte(i)
-- determine bytes needed for character, based on RFC 3629
-- validate byte 1
if c > 0 and c <= 127 then
-- UTF8-1
return 1
elseif c >= 194 and c <= 223 then
-- UTF8-2
local c2 = s:byte(i + 1)
if not c2 then
error("UTF-8 string terminated early")
end
-- validate byte 2
if c2 < 128 or c2 > 191 then
error("Invalid UTF-8 character")
end
return 2
elseif c >= 224 and c <= 239 then
-- UTF8-3
local c2 = s:byte(i + 1)
local c3 = s:byte(i + 2)
if not c2 or not c3 then
error("UTF-8 string terminated early")
end
-- validate byte 2
if c == 224 and (c2 < 160 or c2 > 191) then
error("Invalid UTF-8 character")
elseif c == 237 and (c2 < 128 or c2 > 159) then
error("Invalid UTF-8 character")
elseif c2 < 128 or c2 > 191 then
error("Invalid UTF-8 character")
end
-- validate byte 3
if c3 < 128 or c3 > 191 then
error("Invalid UTF-8 character")
end
return 3
elseif c >= 240 and c <= 244 then
-- UTF8-4
local c2 = s:byte(i + 1)
local c3 = s:byte(i + 2)
local c4 = s:byte(i + 3)
if not c2 or not c3 or not c4 then
error("UTF-8 string terminated early")
end
-- validate byte 2
if c == 240 and (c2 < 144 or c2 > 191) then
error("Invalid UTF-8 character")
elseif c == 244 and (c2 < 128 or c2 > 143) then
error("Invalid UTF-8 character")
elseif c2 < 128 or c2 > 191 then
error("Invalid UTF-8 character")
end
-- validate byte 3
if c3 < 128 or c3 > 191 then
error("Invalid UTF-8 character")
end
-- validate byte 4
if c4 < 128 or c4 > 191 then
error("Invalid UTF-8 character")
end
return 4
else
error("Invalid UTF-8 character")
end
end
-- returns the number of characters in a UTF-8 string
local function utf8len (s)
-- argument checking
if not isstring(s) then
error("bad argument #1 to 'utf8len' (string expected, got ".. type(s).. ")")
end
local pos = 1
local bytes = s:len()
local len = 0
while pos <= bytes do
len = len + 1
pos = pos + utf8charbytes(s, pos)
end
return len
end
-- install in the string library
if not string.utf8bytes then
string.utf8bytes = utf8charbytes
end
-- install in the string library
if not string.utf8len then
string.utf8len = utf8len
end
-- functions identically to string.sub except that i and j are UTF-8 characters
-- instead of bytes
local function utf8sub (s, i, j)
-- argument defaults
j = j or -1
-- argument checking
if not isstring(s) then
error("bad argument #1 to 'utf8sub' (string expected, got ".. type(s).. ")")
end
if not isnumber(i) then
error("bad argument #2 to 'utf8sub' (number expected, got ".. type(i).. ")")
end
if not isnumber(j) then
error("bad argument #3 to 'utf8sub' (number expected, got ".. type(j).. ")")
end
local pos = 1
local bytes = s:len()
local len = 0
-- only set l if i or j is negative
local l = (i >= 0 and j >= 0) or s:utf8len()
local startChar = (i >= 0) and i or l + i + 1
local endChar = (j >= 0) and j or l + j + 1
-- can't have start before end!
if startChar > endChar then
return ""
end
-- byte offsets to pass to string.sub
local startByte, endByte = 1, bytes
while pos <= bytes do
len = len + 1
if len == startChar then
startByte = pos
end
pos = pos + utf8charbytes(s, pos)
if len == endChar then
endByte = pos - 1
break
end
end
return s:sub(startByte, endByte)
end
-- install in the string library
if not string.utf8sub then
string.utf8sub = utf8sub
end
-- replace UTF-8 characters based on a mapping table
local function utf8replace (s, mapping)
-- argument checking
if not isstring(s) then
error("bad argument #1 to 'utf8replace' (string expected, got ".. type(s).. ")")
end
if not istable(mapping) then
error("bad argument #2 to 'utf8replace' (table expected, got ".. type(mapping).. ")")
end
local pos = 1
local bytes = s:len()
local charbytes
local newstr = ""
while pos <= bytes do
charbytes = utf8charbytes(s, pos)
local c = s:sub(pos, pos + charbytes - 1)
newstr = newstr .. (mapping[c] or c)
pos = pos + charbytes
end
return newstr
end
-- identical to string.upper except it knows about unicode simple case conversions
local function utf8upper (s)
return utf8replace(s, utf8_lc_uc)
end
-- install in the string library
if not string.utf8upper and utf8_lc_uc then
string.utf8upper = utf8upper
end
-- identical to string.lower except it knows about unicode simple case conversions
local function utf8lower (s)
return utf8replace(s, utf8_uc_lc)
end
-- install in the string library
if not string.utf8lower and utf8_uc_lc then
string.utf8lower = utf8lower
end
-- identical to string.reverse except that it supports UTF-8
local function utf8reverse (s)
-- argument checking
if not isstring(s) then
error("bad argument #1 to 'utf8reverse' (string expected, got ".. type(s).. ")")
end
local bytes = s:len()
local pos = bytes
local charbytes
local newstr = ""
while pos > 0 do
c = s:byte(pos)
while c >= 128 and c <= 191 do
pos = pos - 1
c = s:byte(pos)
end
charbytes = utf8charbytes(s, pos)
newstr = newstr .. s:sub(pos, pos + charbytes - 1)
pos = pos - 1
end
return newstr
end
-- install in the string library
if not string.utf8reverse then
string.utf8reverse = utf8reverse
end

View File

@@ -0,0 +1,615 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[
(The MIT License)
Copyright (c) 2017 Dominic Letz dominicletz@exosite.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--]]
local table_print_value
table_print_value = function(value, indent, done)
indent = indent or 0
done = done or {}
if istable(value) and not done [value] then
done [value] = true
local list = {}
for key in pairs (value) do
list[#list + 1] = key
end
table.sort(list, function(a, b) return tostring(a) < tostring(b) end)
local last = list[#list]
local rep = "{\n"
local comma
for _, key in ipairs (list) do
if key == last then
comma = ''
else
comma = ','
end
local keyRep
if isnumber(key) then
keyRep = key
else
keyRep = string.format("%q", tostring(key))
end
rep = rep .. string.format(
"%s[%s] = %s%s\n",
string.rep(" ", indent + 2),
keyRep,
table_print_value(value[key], indent + 2, done),
comma
)
end
rep = rep .. string.rep(" ", indent) -- indent it
rep = rep .. "}"
done[value] = false
return rep
elseif isstring(value) then
return string.format("%q", value)
else
return tostring(value)
end
end
local table_print = function(tt)
print('return '..table_print_value(tt))
end
local table_clone = function(t)
local clone = {}
for k,v in pairs(t) do
clone[k] = v
end
return clone
end
local string_trim = function(s, what)
what = what or " "
return s:gsub("^[" .. what .. "]*(.-)["..what.."]*$", "%1")
end
local push = function(stack, item)
stack[#stack + 1] = item
end
local pop = function(stack)
local item = stack[#stack]
stack[#stack] = nil
return item
end
local context = function (str)
if not isstring(str) then
return ""
end
str = str:sub(0,25):gsub("\n","\\n"):gsub("\"","\\\"");
return ", near \"" .. str .. "\""
end
local Parser = {}
function Parser.new (self, tokens)
self.tokens = tokens
self.parse_stack = {}
self.refs = {}
self.current = 0
return self
end
local exports = {version = "1.2"}
local word = function(w) return "^("..w..")([%s$%c])" end
local tokens = {
{"comment", "^#[^\n]*"},
{"indent", "^\n( *)"},
{"space", "^ +"},
{"true", word("enabled"), const = true, value = true},
{"true", word("true"), const = true, value = true},
{"true", word("yes"), const = true, value = true},
{"true", word("on"), const = true, value = true},
{"false", word("disabled"), const = true, value = false},
{"false", word("false"), const = true, value = false},
{"false", word("no"), const = true, value = false},
{"false", word("off"), const = true, value = false},
{"null", word("null"), const = true, value = nil},
{"null", word("Null"), const = true, value = nil},
{"null", word("NULL"), const = true, value = nil},
{"null", word("~"), const = true, value = nil},
{"id", "^\"([^\"]-)\" *(:[%s%c])"},
{"id", "^'([^']-)' *(:[%s%c])"},
{"string", "^\"([^\"]-)\"", force_text = true},
{"string", "^'([^']-)'", force_text = true},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)%s+(%-?%d%d?):(%d%d)"},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)%s+(%-?%d%d?)"},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)"},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d)"},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?)"},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)"},
{"doc", "^%-%-%-[^%c]*"},
{",", "^,"},
{"string", "^%b{} *[^,%c]+", noinline = true},
{"{", "^{"},
{"}", "^}"},
{"string", "^%b[] *[^,%c]+", noinline = true},
{"[", "^%["},
{"]", "^%]"},
{"-", "^%-"},
{":", "^:"},
{"pipe", "^(|)(%d*[+%-]?)", sep = "\n"},
{"pipe", "^(>)(%d*[+%-]?)", sep = " "},
{"id", "^([%w][%w %-_]*)(:[%s%c])"},
{"string", "^[^%c]+", noinline = true},
{"string", "^[^,%c ]+"}
};
exports.tokenize = function (str)
local token
local row = 0
local ignore
local indents = 0
local lastIndents
local stack = {}
local indentAmount = 0
local inline = false
str = str:gsub("\r\n","\010")
while #str > 0 do
for i in ipairs(tokens) do
local captures = {}
if not inline or tokens[i].noinline == nil then
captures = {str:match(tokens[i][2])}
end
if #captures > 0 then
captures.input = str:sub(0, 25)
token = table_clone(tokens[i])
token[2] = captures
local str2 = str:gsub(tokens[i][2], "", 1)
token.raw = str:sub(1, #str - #str2)
str = str2
if token[1] == "{" or token[1] == "[" then
inline = true
elseif token.const then
-- Since word pattern contains last char we're re-adding it
str = token[2][2] .. str
token.raw = token.raw:sub(1, #token.raw - #token[2][2])
elseif token[1] == "id" then
-- Since id pattern contains last semi-colon we're re-adding it
str = token[2][2] .. str
token.raw = token.raw:sub(1, #token.raw - #token[2][2])
-- Trim
token[2][1] = string_trim(token[2][1])
elseif token[1] == "string" then
-- Finding numbers
local snip = token[2][1]
if not token.force_text then
if snip:match("^(%d+%.%d+)$") or snip:match("^(%d+)$") then
token[1] = "number"
end
end
elseif token[1] == "comment" then
ignore = true;
elseif token[1] == "indent" then
row = row + 1
inline = false
lastIndents = indents
if indentAmount == 0 then
indentAmount = #token[2][1]
end
if indentAmount ~= 0 then
indents = (#token[2][1] / indentAmount);
else
indents = 0
end
if indents == lastIndents then
ignore = true;
elseif indents > lastIndents + 2 then
error("SyntaxError: invalid indentation, got " .. tostring(indents)
.. " instead of " .. tostring(lastIndents) .. context(token[2].input))
elseif indents > lastIndents + 1 then
push(stack, token)
elseif indents < lastIndents then
local input = token[2].input
token = {"dedent", {"", input = ""}}
token.input = input
while lastIndents > indents + 1 do
lastIndents = lastIndents - 1
push(stack, token)
end
end
end -- if token[1] == XXX
token.row = row
break
end -- if #captures > 0
end
if not ignore then
if token then
push(stack, token)
token = nil
else
error("SyntaxError " .. context(str))
end
end
ignore = false;
end
return stack
end
Parser.peek = function (self, offset)
offset = offset or 1
return self.tokens[offset + self.current]
end
Parser.advance = function (self)
self.current = self.current + 1
return self.tokens[self.current]
end
Parser.advanceValue = function (self)
return self:advance()[2][1]
end
Parser.accept = function (self, type)
if self:peekType(type) then
return self:advance()
end
end
Parser.expect = function (self, type, msg)
return self:accept(type) or
error(msg .. context(self:peek()[1].input))
end
Parser.expectDedent = function (self, msg)
return self:accept("dedent") or (self:peek() == nil) or
error(msg .. context(self:peek()[2].input))
end
Parser.peekType = function (self, val, offset)
return self:peek(offset) and self:peek(offset)[1] == val
end
Parser.ignore = function (self, items)
local advanced
repeat
advanced = false
for _,v in pairs(items) do
if self:peekType(v) then
self:advance()
advanced = true
end
end
until advanced == false
end
Parser.ignoreSpace = function (self)
self:ignore{"space"}
end
Parser.ignoreWhitespace = function (self)
self:ignore{"space", "indent", "dedent"}
end
Parser.parse = function (self)
local ref = nil
if self:peekType("string") and not self:peek().force_text then
local char = self:peek()[2][1]:sub(1,1)
if char == "&" then
ref = self:peek()[2][1]:sub(2)
self:advanceValue()
self:ignoreSpace()
elseif char == "*" then
ref = self:peek()[2][1]:sub(2)
return self.refs[ref]
end
end
local result
local c = {
indent = self:accept("indent") and 1 or 0,
token = self:peek()
}
push(self.parse_stack, c)
if c.token[1] == "doc" then
result = self:parseDoc()
elseif c.token[1] == "-" then
result = self:parseList()
elseif c.token[1] == "{" then
result = self:parseInlineHash()
elseif c.token[1] == "[" then
result = self:parseInlineList()
elseif c.token[1] == "id" then
result = self:parseHash()
elseif c.token[1] == "string" then
result = self:parseString("\n")
elseif c.token[1] == "timestamp" then
result = self:parseTimestamp()
elseif c.token[1] == "number" then
result = tonumber(self:advanceValue())
elseif c.token[1] == "pipe" then
result = self:parsePipe()
elseif c.token.const == true then
self:advanceValue();
result = c.token.value
else
error("ParseError: unexpected token '" .. c.token[1] .. "'" .. context(c.token.input))
end
pop(self.parse_stack)
while c.indent > 0 do
c.indent = c.indent - 1
local term = "term "..c.token[1]..": '"..c.token[2][1].."'"
self:expectDedent("last ".. term .." is not properly dedented")
end
if ref then
self.refs[ref] = result
end
return result
end
Parser.parseDoc = function (self)
self:accept("doc")
return self:parse()
end
Parser.inline = function (self)
local current = self:peek(0)
if not current then
return {}, 0
end
local inline = {}
local i = 0
while self:peek(i) and not self:peekType("indent", i) and current.row == self:peek(i).row do
inline[self:peek(i)[1]] = true
i = i - 1
end
return inline, -i
end
Parser.isInline = function (self)
local _, i = self:inline()
return i > 0
end
Parser.parent = function(self, level)
level = level or 1
return self.parse_stack[#self.parse_stack - level]
end
Parser.parentType = function(self, type, level)
return self:parent(level) and self:parent(level).token[1] == type
end
Parser.parseString = function (self)
if self:isInline() then
local result = self:advanceValue()
--[[
- a: this looks
flowing: but is
no: string
--]]
local types = self:inline()
if types["id"] and types["-"] then
if not self:peekType("indent") or not self:peekType("indent", 2) then
return result
end
end
--[[
a: 1
b: this is
a flowing string
example
c: 3
--]]
if self:peekType("indent") then
self:expect("indent", "text block needs to start with indent")
local addtl = self:accept("indent")
result = result .. "\n" .. self:parseTextBlock("\n")
self:expectDedent("text block ending dedent missing")
if addtl then
self:expectDedent("text block ending dedent missing")
end
end
return result
else
--[[
a: 1
b:
this is also
a flowing string
example
c: 3
--]]
return self:parseTextBlock("\n")
end
end
Parser.parsePipe = function (self)
local pipe = self:expect("pipe")
self:expect("indent", "text block needs to start with indent")
local result = self:parseTextBlock(pipe.sep)
self:expectDedent("text block ending dedent missing")
return result
end
Parser.parseTextBlock = function (self, sep)
local token = self:advance()
local result = string_trim(token.raw, "\n")
local indents = 0
while self:peek() ~= nil and ( indents > 0 or not self:peekType("dedent") ) do
local newtoken = self:advance()
while token.row < newtoken.row do
result = result .. sep
token.row = token.row + 1
end
if newtoken[1] == "indent" then
indents = indents + 1
elseif newtoken[1] == "dedent" then
indents = indents - 1
else
result = result .. string_trim(newtoken.raw, "\n")
end
end
return result
end
Parser.parseHash = function (self, hash)
hash = hash or {}
local indents = 0
if self:isInline() then
local id = self:advanceValue()
self:expect(":", "expected semi-colon after id")
self:ignoreSpace()
if self:accept("indent") then
indents = indents + 1
hash[id] = self:parse()
else
hash[id] = self:parse()
if self:accept("indent") then
indents = indents + 1
end
end
self:ignoreSpace();
end
while self:peekType("id") do
local id = self:advanceValue()
self:expect(":","expected semi-colon after id")
self:ignoreSpace()
hash[id] = self:parse()
self:ignoreSpace();
end
while indents > 0 do
self:expectDedent("expected dedent")
indents = indents - 1
end
return hash
end
Parser.parseInlineHash = function (self)
local id
local hash = {}
local i = 0
self:accept("{")
while not self:accept("}") do
self:ignoreSpace()
if i > 0 then
self:expect(",","expected comma")
end
self:ignoreWhitespace()
if self:peekType("id") then
id = self:advanceValue()
if id then
self:expect(":","expected semi-colon after id")
self:ignoreSpace()
hash[id] = self:parse()
self:ignoreWhitespace()
end
end
i = i + 1
end
return hash
end
Parser.parseList = function (self)
local list = {}
while self:accept("-") do
self:ignoreSpace()
list[#list + 1] = self:parse()
self:ignoreSpace()
end
return list
end
Parser.parseInlineList = function (self)
local list = {}
local i = 0
self:accept("[")
while not self:accept("]") do
self:ignoreSpace()
if i > 0 then
self:expect(",","expected comma")
end
self:ignoreSpace()
list[#list + 1] = self:parse()
self:ignoreSpace()
i = i + 1
end
return list
end
Parser.parseTimestamp = function (self)
local capture = self:advance()[2]
return os.time{
year = capture[1],
month = capture[2],
day = capture[3],
hour = capture[4] or 0,
min = capture[5] or 0,
sec = capture[6] or 0
}
end
exports.Eval = function (str)
return Parser:new(exports.tokenize(str)):parse()
end
exports.Read = function(file_name)
if file.Exists(file_name, 'GAME') then
local local_name = file_name:gsub('%.y([a]?)ml', '.local.y%1ml')
if file.Exists(local_name, 'GAME') then
file_name = local_name
end
return Parser:new(exports.tokenize(file.Read(file_name, 'GAME'))):parse()
end
end
exports.Dump = table_print
ix.yaml = exports

View File

@@ -0,0 +1,663 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[
mysql - 1.0.3
A simple MySQL wrapper for Garry's Mod.
Alexander Grist-Hucker
http://www.alexgrist.com
The MIT License (MIT)
Copyright (c) 2014 Alex Grist-Hucker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--]]
mysql = mysql or {
module = "sqlite"
}
local QueueTable = {}
local tostring = tostring
local table = table
--[[
Replacement tables
--]]
local Replacements = {
sqlite = {
Create = {
{"UNSIGNED ", ""},
{"NOT NULL AUTO_INCREMENT", ""}, -- assuming primary key
{"AUTO_INCREMENT", ""},
{"INT%(%d*%)", "INTEGER"},
{"INT ", "INTEGER"}
}
}
}
--[[
Phrases
--]]
local MODULE_NOT_EXIST = "[mysql] The %s module does not exist!\n"
--[[
Begin Query Class.
--]]
local QUERY_CLASS = {}
QUERY_CLASS.__index = QUERY_CLASS
function QUERY_CLASS:New(tableName, queryType)
local newObject = setmetatable({}, QUERY_CLASS)
newObject.queryType = queryType
newObject.tableName = tableName
newObject.selectList = {}
newObject.insertList = {}
newObject.updateList = {}
newObject.createList = {}
newObject.whereList = {}
newObject.orderByList = {}
return newObject
end
function QUERY_CLASS:Escape(text)
return mysql:Escape(tostring(text))
end
function QUERY_CLASS:ForTable(tableName)
self.tableName = tableName
end
function QUERY_CLASS:Where(key, value)
self:WhereEqual(key, value)
end
function QUERY_CLASS:WhereEqual(key, value)
self.whereList[#self.whereList + 1] = "`"..key.."` = '"..self:Escape(value).."'"
end
function QUERY_CLASS:WhereNotEqual(key, value)
self.whereList[#self.whereList + 1] = "`"..key.."` != '"..self:Escape(value).."'"
end
function QUERY_CLASS:WhereLike(key, value, format)
format = format or "%%%s%%"
self.whereList[#self.whereList + 1] = "`"..key.."` LIKE '"..string.format(format, self:Escape(value)).."'"
end
function QUERY_CLASS:WhereNotLike(key, value, format)
format = format or "%%%s%%"
self.whereList[#self.whereList + 1] = "`"..key.."` NOT LIKE '"..string.format(format, self:Escape(value)).."'"
end
function QUERY_CLASS:WhereGT(key, value)
self.whereList[#self.whereList + 1] = "`"..key.."` > '"..self:Escape(value).."'"
end
function QUERY_CLASS:WhereLT(key, value)
self.whereList[#self.whereList + 1] = "`"..key.."` < '"..self:Escape(value).."'"
end
function QUERY_CLASS:WhereGTE(key, value)
self.whereList[#self.whereList + 1] = "`"..key.."` >= '"..self:Escape(value).."'"
end
function QUERY_CLASS:WhereLTE(key, value)
self.whereList[#self.whereList + 1] = "`"..key.."` <= '"..self:Escape(value).."'"
end
function QUERY_CLASS:WhereIn(key, value)
value = istable(value) and value or {value}
local values = ""
local bFirst = true
for k, v in pairs(value) do
values = values .. (bFirst and "" or ", ") .. "'" .. self:Escape(v) .. "'"
bFirst = false
end
self.whereList[#self.whereList + 1] = "`"..key.."` IN ("..values..")"
end
function QUERY_CLASS:OrderByDesc(key)
self.orderByList[#self.orderByList + 1] = "`"..key.."` DESC"
end
function QUERY_CLASS:OrderByAsc(key)
self.orderByList[#self.orderByList + 1] = "`"..key.."` ASC"
end
function QUERY_CLASS:Callback(queryCallback)
self.callback = queryCallback
end
function QUERY_CLASS:Select(fieldName)
self.selectList[#self.selectList + 1] = "`"..fieldName.."`"
end
function QUERY_CLASS:Insert(key, value)
self.insertList[#self.insertList + 1] = {"`"..key.."`", "'"..self:Escape(value).."'"}
end
function QUERY_CLASS:Update(key, value)
self.updateList[#self.updateList + 1] = {"`"..key.."`", "'"..self:Escape(value).."'"}
end
function QUERY_CLASS:Create(key, value)
self.createList[#self.createList + 1] = {"`"..key.."`", value}
end
function QUERY_CLASS:Add(key, value)
self.add = {"`"..key.."`", value}
end
function QUERY_CLASS:Drop(key)
self.drop = "`"..key.."`"
end
function QUERY_CLASS:PrimaryKey(key)
self.primaryKey = "`"..key.."`"
end
function QUERY_CLASS:Limit(value)
self.limit = value
end
function QUERY_CLASS:Offset(value)
self.offset = value
end
local function ApplyQueryReplacements(mode, query)
if (!Replacements[mysql.module]) then
return query
end
local result = query
local entries = Replacements[mysql.module][mode]
for i = 1, #entries do
result = string.gsub(result, entries[i][1], entries[i][2])
end
return result
end
local function BuildSelectQuery(queryObj)
local queryString = {"SELECT"}
if (!istable(queryObj.selectList) or #queryObj.selectList == 0) then
queryString[#queryString + 1] = " *"
else
queryString[#queryString + 1] = " "..table.concat(queryObj.selectList, ", ")
end
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " FROM `"..queryObj.tableName.."` "
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
if (istable(queryObj.whereList) and #queryObj.whereList > 0) then
queryString[#queryString + 1] = " WHERE "
queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND ")
end
if (istable(queryObj.orderByList) and #queryObj.orderByList > 0) then
queryString[#queryString + 1] = " ORDER BY "
queryString[#queryString + 1] = table.concat(queryObj.orderByList, ", ")
end
if (isnumber(queryObj.limit)) then
queryString[#queryString + 1] = " LIMIT "
queryString[#queryString + 1] = queryObj.limit
if (isnumber(queryObj.offset)) then
queryString[#queryString + 1] = " OFFSET "
queryString[#queryString + 1] = queryObj.offset
end
end
return table.concat(queryString)
end
local function BuildInsertQuery(queryObj, bIgnore)
local suffix = (bIgnore and (mysql.module == "sqlite" and "INSERT OR IGNORE INTO" or "INSERT IGNORE INTO") or "INSERT INTO")
local queryString = {suffix}
local keyList = {}
local valueList = {}
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
for i = 1, #queryObj.insertList do
keyList[#keyList + 1] = queryObj.insertList[i][1]
valueList[#valueList + 1] = queryObj.insertList[i][2]
end
if (#keyList == 0) then
return
end
queryString[#queryString + 1] = " ("..table.concat(keyList, ", ")..")"
queryString[#queryString + 1] = " VALUES ("..table.concat(valueList, ", ")..")"
return table.concat(queryString)
end
local function BuildUpdateQuery(queryObj)
local queryString = {"UPDATE"}
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
if (istable(queryObj.updateList) and #queryObj.updateList > 0) then
local updateList = {}
queryString[#queryString + 1] = " SET"
for i = 1, #queryObj.updateList do
updateList[#updateList + 1] = queryObj.updateList[i][1].." = "..queryObj.updateList[i][2]
end
queryString[#queryString + 1] = " "..table.concat(updateList, ", ")
end
if (istable(queryObj.whereList) and #queryObj.whereList > 0) then
queryString[#queryString + 1] = " WHERE "
queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND ")
end
if (isnumber(queryObj.offset)) then
queryString[#queryString + 1] = " OFFSET "
queryString[#queryString + 1] = queryObj.offset
end
return table.concat(queryString)
end
local function BuildDeleteQuery(queryObj)
local queryString = {"DELETE FROM"}
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
if (istable(queryObj.whereList) and #queryObj.whereList > 0) then
queryString[#queryString + 1] = " WHERE "
queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND ")
end
if (isnumber(queryObj.limit)) then
queryString[#queryString + 1] = " LIMIT "
queryString[#queryString + 1] = queryObj.limit
end
return table.concat(queryString)
end
local function BuildDropQuery(queryObj)
local queryString = {"DROP TABLE"}
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
return table.concat(queryString)
end
local function BuildTruncateQuery(queryObj)
local queryString = {"TRUNCATE TABLE"}
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
return table.concat(queryString)
end
local function BuildCreateQuery(queryObj)
local queryString = {"CREATE TABLE IF NOT EXISTS"}
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
queryString[#queryString + 1] = " ("
if (istable(queryObj.createList) and #queryObj.createList > 0) then
local createList = {}
for i = 1, #queryObj.createList do
if (mysql.module == "sqlite") then
createList[#createList + 1] = queryObj.createList[i][1].." "..ApplyQueryReplacements("Create", queryObj.createList[i][2])
else
createList[#createList + 1] = queryObj.createList[i][1].." "..queryObj.createList[i][2]
end
end
queryString[#queryString + 1] = " "..table.concat(createList, ", ")
end
if (isstring(queryObj.primaryKey)) then
queryString[#queryString + 1] = ", PRIMARY KEY"
queryString[#queryString + 1] = " ("..queryObj.primaryKey..")"
end
queryString[#queryString + 1] = " )"
return table.concat(queryString)
end
local function BuildAlterQuery(queryObj)
local queryString = {"ALTER TABLE"}
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
if (istable(queryObj.add)) then
queryString[#queryString + 1] = " ADD "..queryObj.add[1].." "..ApplyQueryReplacements("Create", queryObj.add[2])
elseif (isstring(queryObj.drop)) then
if (mysql.module == "sqlite") then
ErrorNoHalt("[mysql] Cannot drop columns in sqlite!\n")
return
end
queryString[#queryString + 1] = " DROP COLUMN "..queryObj.drop
end
return table.concat(queryString)
end
function QUERY_CLASS:Execute(bQueueQuery)
local queryString = nil
local queryType = string.lower(self.queryType)
if (queryType == "select") then
queryString = BuildSelectQuery(self)
elseif (queryType == "insert") then
queryString = BuildInsertQuery(self)
elseif (queryType == "insert ignore") then
queryString = BuildInsertQuery(self, true)
elseif (queryType == "update") then
queryString = BuildUpdateQuery(self)
elseif (queryType == "delete") then
queryString = BuildDeleteQuery(self)
elseif (queryType == "drop") then
queryString = BuildDropQuery(self)
elseif (queryType == "truncate") then
queryString = BuildTruncateQuery(self)
elseif (queryType == "create") then
queryString = BuildCreateQuery(self)
elseif (queryType == "alter") then
queryString = BuildAlterQuery(self)
end
if (isstring(queryString)) then
if (!bQueueQuery) then
return mysql:RawQuery(queryString, self.callback)
else
return mysql:Queue(queryString, self.callback)
end
end
end
--[[
End Query Class.
--]]
function mysql:Select(tableName)
return QUERY_CLASS:New(tableName, "SELECT")
end
function mysql:Insert(tableName)
return QUERY_CLASS:New(tableName, "INSERT")
end
function mysql:InsertIgnore(tableName)
return QUERY_CLASS:New(tableName, "INSERT IGNORE")
end
function mysql:Update(tableName)
return QUERY_CLASS:New(tableName, "UPDATE")
end
function mysql:Delete(tableName)
return QUERY_CLASS:New(tableName, "DELETE")
end
function mysql:Drop(tableName)
return QUERY_CLASS:New(tableName, "DROP")
end
function mysql:Truncate(tableName)
return QUERY_CLASS:New(tableName, "TRUNCATE")
end
function mysql:Create(tableName)
return QUERY_CLASS:New(tableName, "CREATE")
end
function mysql:Alter(tableName)
return QUERY_CLASS:New(tableName, "ALTER")
end
local UTF8MB4 = "ALTER DATABASE %s CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci"
-- A function to connect to the MySQL database.
function mysql:Connect(host, username, password, database, port, socket, flags)
port = port or 3306
if (self.module == "mysqloo") then
if (!istable(mysqloo)) then
require("mysqloo")
end
if (mysqloo) then
if (self.connection and self.connection:ping()) then
return
end
local clientFlag = flags or 0
if (!isstring(socket)) then
self.connection = mysqloo.connect(host, username, password, database, port)
else
self.connection = mysqloo.connect(host, username, password, database, port, socket, clientFlag)
end
self.connection.onConnected = function(connection)
local success, error_message = connection:setCharacterSet("utf8mb4")
if (!success) then
ErrorNoHalt("Failed to set MySQL encoding!\n")
ErrorNoHalt(error_message .. "\n")
else
self:RawQuery(string.format(UTF8MB4, database))
end
mysql:OnConnected()
end
self.connection.onConnectionFailed = function(database, errorText)
mysql:OnConnectionFailed(errorText)
end
self.connection:connect()
timer.Create("mysql.KeepAlive", 300, 0, function()
self.connection:ping()
end)
else
ErrorNoHalt(string.format(MODULE_NOT_EXIST, self.module))
end
elseif (self.module == "sqlite") then
timer.Simple(0, function()
mysql:OnConnected()
end)
end
end
-- A function to query the MySQL database.
function mysql:RawQuery(query, callback, flags, ...)
if (self.module == "mysqloo") then
local queryObj = self.connection:query(query)
queryObj:setOption(mysqloo.OPTION_NAMED_FIELDS)
queryObj.onSuccess = function(queryObj, result)
if (callback) then
local bStatus, value = pcall(callback, result, true, tonumber(queryObj:lastInsert()))
if (!bStatus) then
error(string.format("[mysql] MySQL Callback Error!\n%s\n", value))
end
end
end
queryObj.onError = function(queryObj, errorText)
ErrorNoHalt(string.format("[mysql] MySQL Query Error!\nQuery: %s\n%s\n", query, errorText))
end
queryObj:start()
elseif (self.module == "sqlite") then
local result = sql.Query(query)
if (result == false) then
error(string.format("[mysql] SQL Query Error!\nQuery: %s\n%s\n", query, sql.LastError()))
else
if (callback) then
local bStatus, value = pcall(callback, result, true, tonumber(sql.QueryValue("SELECT last_insert_rowid()")))
if (!bStatus) then
error(string.format("[mysql] SQL Callback Error!\n%s\n", value))
end
end
end
else
ErrorNoHalt(string.format("[mysql] Unsupported module \"%s\"!\n", self.module))
end
end
-- A function to add a query to the queue.
function mysql:Queue(queryString, callback)
if (isstring(queryString)) then
QueueTable[#QueueTable + 1] = {queryString, callback}
end
end
-- A function to escape a string for MySQL.
function mysql:Escape(text)
if (self.connection) then
if (self.module == "mysqloo") then
return self.connection:escape(text)
end
else
return sql.SQLStr(text, true)
end
end
-- A function to disconnect from the MySQL database.
function mysql:Disconnect()
if (self.connection) then
if (self.module == "mysqloo") then
self.connection:disconnect(true)
end
end
end
function mysql:Think()
if (#QueueTable > 0) then
if (istable(QueueTable[1])) then
local queueObj = QueueTable[1]
local queryString = queueObj[1]
local callback = queueObj[2]
if (isstring(queryString)) then
self:RawQuery(queryString, callback)
end
table.remove(QueueTable, 1)
end
end
end
-- A function to set the module that should be used.
function mysql:SetModule(moduleName)
self.module = moduleName
end
-- Called when the database connects sucessfully.
function mysql:OnConnected()
MsgC(Color(25, 235, 25), "[mysql] Connected to the database!\n")
hook.Run("DatabaseConnected")
end
-- Called when the database connection fails.
function mysql:OnConnectionFailed(errorText)
ErrorNoHalt(string.format("[mysql] Unable to connect to the database!\n%s\n", errorText))
hook.Run("DatabaseConnectionFailed", errorText)
end
-- A function to check whether or not the module is connected to a database.
function mysql:IsConnected()
return self.module == "mysqloo" and (self.connection and self.connection:ping()) or self.module == "sqlite"
end
return mysql