mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 13:53:45 +03:00
Upload
This commit is contained in:
131
gamemodes/helix/gamemode/core/libs/cl_bar.lua
Normal file
131
gamemodes/helix/gamemode/core/libs/cl_bar.lua
Normal file
@@ -0,0 +1,131 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
ix.bar = ix.bar or {}
|
||||
ix.bar.list = {}
|
||||
ix.bar.delta = ix.bar.delta or {}
|
||||
ix.bar.actionText = ""
|
||||
ix.bar.actionStart = 0
|
||||
ix.bar.actionEnd = 0
|
||||
ix.bar.totalHeight = 0
|
||||
|
||||
-- luacheck: globals BAR_HEIGHT
|
||||
BAR_HEIGHT = 10
|
||||
|
||||
function ix.bar.Get(identifier)
|
||||
for _, v in ipairs(ix.bar.list) do
|
||||
if (v.identifier == identifier) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ix.bar.Remove(identifier)
|
||||
local bar = ix.bar.Get(identifier)
|
||||
|
||||
if (bar) then
|
||||
table.remove(ix.bar.list, bar.index)
|
||||
|
||||
if (IsValid(ix.gui.bars)) then
|
||||
ix.gui.bars:RemoveBar(bar.panel)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ix.bar.Add(getValue, color, priority, identifier)
|
||||
if (identifier) then
|
||||
ix.bar.Remove(identifier)
|
||||
end
|
||||
|
||||
local index = #ix.bar.list + 1
|
||||
|
||||
color = color or Color(math.random(150, 255), math.random(150, 255), math.random(150, 255))
|
||||
priority = priority or index
|
||||
|
||||
ix.bar.list[index] = {
|
||||
index = index,
|
||||
color = color,
|
||||
priority = priority,
|
||||
GetValue = getValue,
|
||||
identifier = identifier,
|
||||
panel = IsValid(ix.gui.bars) and ix.gui.bars:AddBar(index, color, priority)
|
||||
}
|
||||
|
||||
return priority
|
||||
end
|
||||
|
||||
local gradientD = ix.util.GetMaterial("vgui/gradient-d")
|
||||
|
||||
local TEXT_COLOR = Color(240, 240, 240)
|
||||
local SHADOW_COLOR = Color(20, 20, 20)
|
||||
|
||||
function ix.bar.DrawAction()
|
||||
local start, finish = ix.bar.actionStart, ix.bar.actionEnd
|
||||
local curTime = CurTime()
|
||||
local scrW, scrH = ScrW(), ScrH()
|
||||
|
||||
if (finish > curTime) then
|
||||
local fraction = 1 - math.TimeFraction(start, finish, curTime)
|
||||
local alpha = fraction * 255
|
||||
|
||||
if (alpha > 0) then
|
||||
local w, h = scrW * 0.35, 28
|
||||
local x, y = (scrW * 0.5) - (w * 0.5), (scrH * 0.725) - (h * 0.5)
|
||||
|
||||
ix.util.DrawBlurAt(x, y, w, h)
|
||||
|
||||
surface.SetDrawColor(35, 35, 35, 100)
|
||||
surface.DrawRect(x, y, w, h)
|
||||
|
||||
surface.SetDrawColor(0, 0, 0, 120)
|
||||
surface.DrawOutlinedRect(x, y, w, h)
|
||||
|
||||
surface.SetDrawColor(ix.config.Get("color"))
|
||||
surface.DrawRect(x + 4, y + 4, math.max(w * fraction, 8) - 8, h - 8)
|
||||
|
||||
surface.SetDrawColor(200, 200, 200, 20)
|
||||
surface.SetMaterial(gradientD)
|
||||
surface.DrawTexturedRect(x + 4, y + 4, math.max(w * fraction, 8) - 8, h - 8)
|
||||
|
||||
draw.SimpleText(ix.bar.actionText, "ixMediumFont", x + 2, y - 22, SHADOW_COLOR)
|
||||
draw.SimpleText(ix.bar.actionText, "ixMediumFont", x, y - 24, TEXT_COLOR)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
ix.bar.Add(function()
|
||||
return math.max(LocalPlayer():Health() / LocalPlayer():GetMaxHealth(), 0)
|
||||
end, Color(200, 50, 40), nil, "health")
|
||||
|
||||
ix.bar.Add(function()
|
||||
return math.min(LocalPlayer():Armor() / 100, 1)
|
||||
end, Color(30, 70, 180), nil, "armor")
|
||||
end
|
||||
|
||||
net.Receive("ixActionBar", function()
|
||||
local start, finish = net.ReadFloat(), net.ReadFloat()
|
||||
local text = net.ReadString()
|
||||
|
||||
if (text:sub(1, 1) == "@") then
|
||||
text = L2(text:sub(2)) or text
|
||||
end
|
||||
|
||||
ix.bar.actionStart = start
|
||||
ix.bar.actionEnd = finish
|
||||
ix.bar.actionText = text:utf8upper()
|
||||
end)
|
||||
|
||||
net.Receive("ixActionBarReset", function()
|
||||
ix.bar.actionStart = 0
|
||||
ix.bar.actionEnd = 0
|
||||
ix.bar.actionText = ""
|
||||
end)
|
||||
146
gamemodes/helix/gamemode/core/libs/cl_hud.lua
Normal file
146
gamemodes/helix/gamemode/core/libs/cl_hud.lua
Normal file
@@ -0,0 +1,146 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
ix.hud = {}
|
||||
|
||||
local owner, w, h, ceil, ft, clmp
|
||||
ceil = math.ceil
|
||||
clmp = math.Clamp
|
||||
local aprg, aprg2 = 0, 0
|
||||
|
||||
function ix.hud.DrawDeath()
|
||||
owner = LocalPlayer()
|
||||
ft = FrameTime()
|
||||
w, h = ScrW(), ScrH()
|
||||
|
||||
if (owner:GetCharacter()) then
|
||||
if (owner:Alive()) then
|
||||
if (aprg != 0) then
|
||||
aprg2 = clmp(aprg2 - ft*1.3, 0, 1)
|
||||
if (aprg2 == 0) then
|
||||
aprg = clmp(aprg - ft*.7, 0, 1)
|
||||
end
|
||||
end
|
||||
else
|
||||
if (aprg2 != 1) then
|
||||
aprg = clmp(aprg + ft*.5, 0, 1)
|
||||
if (aprg == 1) then
|
||||
aprg2 = clmp(aprg2 + ft*.4, 0, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (IsValid(ix.gui.characterMenu) and ix.gui.characterMenu:IsVisible() or !owner:GetCharacter()) then
|
||||
return
|
||||
end
|
||||
|
||||
surface.SetDrawColor(0, 0, 0, ceil((aprg^.5) * 255))
|
||||
surface.DrawRect(-1, -1, w+2, h+2)
|
||||
|
||||
ix.util.DrawText(
|
||||
string.utf8upper(L"youreDead"), w/2, h/2, ColorAlpha(color_white, aprg2 * 255), 1, 1, "ixMenuButtonHugeFont", aprg2 * 255
|
||||
)
|
||||
end
|
||||
|
||||
function ix.hud.DrawItemPickup()
|
||||
local pickupTime = ix.config.Get("itemPickupTime", 0.5)
|
||||
|
||||
if (pickupTime == 0) then
|
||||
return
|
||||
end
|
||||
|
||||
local client = LocalPlayer()
|
||||
local entity = client.ixInteractionTarget
|
||||
local startTime = client.ixInteractionStartTime
|
||||
|
||||
if (IsValid(entity) and startTime) then
|
||||
local sysTime = SysTime()
|
||||
local endTime = startTime + pickupTime
|
||||
|
||||
if (sysTime >= endTime or client:GetEyeTrace().Entity != entity) then
|
||||
client.ixInteractionTarget = nil
|
||||
client.ixInteractionStartTime = nil
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local fraction = math.min((endTime - sysTime) / pickupTime, 1)
|
||||
local x, y = ScrW() / 2, ScrH() / 2
|
||||
local radius, thickness = 32, 6
|
||||
local startAngle = 90
|
||||
local endAngle = startAngle + (1 - fraction) * 360
|
||||
local color = ColorAlpha(color_white, fraction * 255)
|
||||
|
||||
ix.util.DrawArc(x, y, radius, thickness, startAngle, endAngle, 2, color)
|
||||
end
|
||||
end
|
||||
|
||||
function ix.hud.PopulateItemTooltip(tooltip, item)
|
||||
local name = tooltip:AddRow("name")
|
||||
name:SetImportant()
|
||||
name:SetText(item.GetName and item:GetName() or L(item.name))
|
||||
name:SetMaxWidth(math.max(name:GetMaxWidth(), ScrW() * 0.5))
|
||||
name:SizeToContents()
|
||||
|
||||
local description = tooltip:AddRow("description")
|
||||
description:SetText(item:GetDescription() or "")
|
||||
description:SizeToContents()
|
||||
|
||||
if (item.PopulateTooltip) then
|
||||
item:PopulateTooltip(tooltip)
|
||||
end
|
||||
|
||||
hook.Run("PopulateItemTooltip", tooltip, item)
|
||||
end
|
||||
|
||||
function ix.hud.PopulatePlayerTooltip(tooltip, client)
|
||||
local name = tooltip:AddRow("name")
|
||||
name:SetImportant()
|
||||
name:SetText(client:SteamName())
|
||||
name:SetBackgroundColor(team.GetColor(client:Team()))
|
||||
name:SizeToContents()
|
||||
|
||||
local nameHeight = name:GetTall()
|
||||
name:SetTextInset(nameHeight + 4, 0)
|
||||
name:SetWide(name:GetWide() + nameHeight + 4)
|
||||
|
||||
local avatar = name:Add("AvatarImage")
|
||||
avatar:Dock(LEFT)
|
||||
avatar:SetPlayer(client, nameHeight)
|
||||
avatar:SetSize(name:GetTall(), name:GetTall())
|
||||
|
||||
local currentPing = client:Ping()
|
||||
|
||||
local ping = tooltip:AddRow("ping")
|
||||
ping:SetText(L("ping", currentPing))
|
||||
ping.Paint = function(_, width, height)
|
||||
surface.SetDrawColor(ColorAlpha(derma.GetColor(
|
||||
currentPing < 110 and "Success" or (currentPing < 165 and "Warning" or "Error")
|
||||
, tooltip), 22))
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
ping:SizeToContents()
|
||||
|
||||
hook.Run("PopulatePlayerTooltip", client, tooltip)
|
||||
end
|
||||
|
||||
hook.Add("GetCrosshairAlpha", "ixCrosshair", function(alpha)
|
||||
return alpha * (1 - aprg)
|
||||
end)
|
||||
|
||||
function ix.hud.DrawAll(postHook)
|
||||
if (postHook) then
|
||||
ix.hud.DrawDeath()
|
||||
end
|
||||
|
||||
ix.hud.DrawItemPickup()
|
||||
end
|
||||
506
gamemodes/helix/gamemode/core/libs/cl_markup.lua
Normal file
506
gamemodes/helix/gamemode/core/libs/cl_markup.lua
Normal file
@@ -0,0 +1,506 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
-- luacheck: ignore
|
||||
ix.markup = ix.markup or {}
|
||||
|
||||
-- Temporary information used when building text frames.
|
||||
local colour_stack = { {r=255,g=255,b=255,a=255} }
|
||||
local font_stack = { "DermaDefault" }
|
||||
local curtag = nil
|
||||
local blocks = {}
|
||||
|
||||
local colourmap = {
|
||||
-- it's all black and white
|
||||
["black"] = { r=0, g=0, b=0, a=255 },
|
||||
["white"] = { r=255, g=255, b=255, a=255 },
|
||||
-- it's greys
|
||||
["dkgrey"] = { r=64, g=64, b=64, a=255 },
|
||||
["grey"] = { r=128, g=128, b=128, a=255 },
|
||||
["ltgrey"] = { r=192, g=192, b=192, a=255 },
|
||||
-- account for speeling mistakes
|
||||
["dkgray"] = { r=64, g=64, b=64, a=255 },
|
||||
["gray"] = { r=128, g=128, b=128, a=255 },
|
||||
["ltgray"] = { r=192, g=192, b=192, a=255 },
|
||||
-- normal colours
|
||||
["red"] = { r=255, g=0, b=0, a=255 },
|
||||
["green"] = { r=0, g=255, b=0, a=255 },
|
||||
["blue"] = { r=0, g=0, b=255, a=255 },
|
||||
["yellow"] = { r=255, g=255, b=0, a=255 },
|
||||
["purple"] = { r=255, g=0, b=255, a=255 },
|
||||
["cyan"] = { r=0, g=255, b=255, a=255 },
|
||||
["turq"] = { r=0, g=255, b=255, a=255 },
|
||||
-- dark variations
|
||||
["dkred"] = { r=128, g=0, b=0, a=255 },
|
||||
["dkgreen"] = { r=0, g=128, b=0, a=255 },
|
||||
["dkblue"] = { r=0, g=0, b=128, a=255 },
|
||||
["dkyellow"] = { r=128, g=128, b=0, a=255 },
|
||||
["dkpurple"] = { r=128, g=0, b=128, a=255 },
|
||||
["dkcyan"] = { r=0, g=128, b=128, a=255 },
|
||||
["dkturq"] = { r=0, g=128, b=128, a=255 },
|
||||
-- light variations
|
||||
["ltred"] = { r=255, g=128, b=128, a=255 },
|
||||
["ltgreen"] = { r=128, g=255, b=128, a=255 },
|
||||
["ltblue"] = { r=128, g=128, b=255, a=255 },
|
||||
["ltyellow"] = { r=255, g=255, b=128, a=255 },
|
||||
["ltpurple"] = { r=255, g=128, b=255, a=255 },
|
||||
["ltcyan"] = { r=128, g=255, b=255, a=255 },
|
||||
["ltturq"] = { r=128, g=255, b=255, a=255 },
|
||||
}
|
||||
|
||||
--[[
|
||||
Name: colourMatch(c)
|
||||
Desc: Match a colour name to an rgb value.
|
||||
Usage: ** INTERNAL ** Do not use!
|
||||
]]
|
||||
local function colourMatch(c)
|
||||
c = string.lower(c)
|
||||
|
||||
return colourmap[c]
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: ExtractParams(p1,p2,p3)
|
||||
Desc: This function is used to extract the tag information.
|
||||
Usage: ** INTERNAL ** Do not use!
|
||||
]]
|
||||
local function ExtractParams(p1,p2,p3)
|
||||
|
||||
if (string.utf8sub(p1, 1, 1) == "/") then
|
||||
|
||||
local tag = string.utf8sub(p1, 2)
|
||||
|
||||
if (tag == "color" or tag == "colour") then
|
||||
table.remove(colour_stack)
|
||||
elseif (tag == "font" or tag == "face") then
|
||||
table.remove(font_stack)
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
if (p1 == "color" or p1 == "colour") then
|
||||
|
||||
local rgba = colourMatch(p2)
|
||||
|
||||
if (rgba == nil) then
|
||||
rgba = {}
|
||||
local x = { "r", "g", "b", "a" }
|
||||
n = 1
|
||||
for k, v in string.gmatch(p2, "(%d+),?") do
|
||||
rgba[ x[n] ] = k
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(colour_stack, rgba)
|
||||
|
||||
elseif (p1 == "font" or p1 == "face") then
|
||||
|
||||
table.insert(font_stack, tostring(p2))
|
||||
elseif (p1 == "img" and p2) then
|
||||
local exploded = string.Explode(",", p2)
|
||||
local material = exploded[1] or p2
|
||||
local p3 = exploded[2]
|
||||
|
||||
local found = file.Find("materials/"..material..".*", "GAME")
|
||||
|
||||
if (found[1] and found[1]:find("%.png")) then
|
||||
material = material..".png"
|
||||
end
|
||||
|
||||
local texture = Material(material)
|
||||
local sizeData = string.Explode("x", p3 or "16x16")
|
||||
w = tonumber(sizeData[1]) or 16
|
||||
h = tonumber(sizeData[2]) or 16
|
||||
|
||||
if (texture) then
|
||||
table.insert(blocks, {
|
||||
texture = texture,
|
||||
w = w,
|
||||
h = h
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: CheckTextOrTag(p)
|
||||
Desc: This function places data in the "blocks" table
|
||||
depending of if p is a tag, or some text
|
||||
Usage: ** INTERNAL ** Do not use!
|
||||
]]
|
||||
local function CheckTextOrTag(p)
|
||||
if (p == "") then return end
|
||||
if (p == nil) then return end
|
||||
|
||||
if (string.utf8sub(p, 1, 1) == "<") then
|
||||
string.gsub(p, "<([/%a]*)=?([^>]*)", ExtractParams)
|
||||
else
|
||||
|
||||
local text_block = {}
|
||||
text_block.text = p
|
||||
text_block.colour = colour_stack[#colour_stack]
|
||||
text_block.font = font_stack[#font_stack]
|
||||
table.insert(blocks, text_block)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: ProcessMatches(p1,p2,p3)
|
||||
Desc: CheckTextOrTag for 3 parameters. Called by string.gsub
|
||||
Usage: ** INTERNAL ** Do not use!
|
||||
]]
|
||||
local function ProcessMatches(p1,p2,p3)
|
||||
if (p1) then CheckTextOrTag(p1) end
|
||||
if (p2) then CheckTextOrTag(p2) end
|
||||
if (p3) then CheckTextOrTag(p3) end
|
||||
end
|
||||
|
||||
local MarkupObject = {}
|
||||
|
||||
--[[
|
||||
Name: MarkupObject:Create()
|
||||
Desc: Called by Parse. Creates a new table, and setups the
|
||||
metatable.
|
||||
Usage: ** INTERNAL ** Do not use!
|
||||
]]
|
||||
function MarkupObject:create()
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
|
||||
return o
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: MarkupObject:GetWidth()
|
||||
Desc: Returns the width of a markup block
|
||||
Usage: ml:GetWidth()
|
||||
]]
|
||||
function MarkupObject:GetWidth()
|
||||
return self.totalWidth
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: MarkupObject:GetHeight()
|
||||
Desc: Returns the height of a markup block
|
||||
Usage: ml:GetHeight()
|
||||
]]
|
||||
function MarkupObject:GetHeight()
|
||||
return self.totalHeight
|
||||
end
|
||||
|
||||
function MarkupObject:size()
|
||||
return self.totalWidth, self.totalHeight
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: MarkupObject:Draw(xOffset, yOffset, halign, valign, alphaoverride)
|
||||
Desc: Draw the markup text to the screen as position
|
||||
xOffset, yOffset. Halign and Valign can be used
|
||||
to align the text. Alphaoverride can be used to override
|
||||
the alpha value of the text-colour.
|
||||
Usage: MarkupObject:Draw(100, 100)
|
||||
]]
|
||||
function MarkupObject:draw(xOffset, yOffset, halign, valign, alphaoverride)
|
||||
for i = 1, #self.blocks do
|
||||
local blk = self.blocks[i]
|
||||
|
||||
if (blk.texture) then
|
||||
local y = yOffset + blk.offset.y
|
||||
local x = xOffset + blk.offset.x
|
||||
|
||||
if (halign == TEXT_ALIGN_CENTER) then
|
||||
x = x - (self.totalWidth * 0.5)
|
||||
elseif (halign == TEXT_ALIGN_RIGHT) then
|
||||
x = x - (self.totalWidth)
|
||||
end
|
||||
|
||||
surface.SetDrawColor(blk.colour.r, blk.colour.g, blk.colour.b, alphaoverride or blk.colour.a or 255)
|
||||
surface.SetMaterial(blk.texture)
|
||||
surface.DrawTexturedRect(x, y, blk.w, blk.h)
|
||||
else
|
||||
local y = yOffset + (blk.height - blk.thisY) + blk.offset.y
|
||||
local x = xOffset
|
||||
|
||||
if (halign == TEXT_ALIGN_CENTER) then x = x - (self.totalWidth / 2)
|
||||
elseif (halign == TEXT_ALIGN_RIGHT) then x = x - (self.totalWidth)
|
||||
end
|
||||
|
||||
x = x + blk.offset.x
|
||||
|
||||
if (self.onDrawText) then
|
||||
self.onDrawText(blk.text, blk.font, x, y, blk.colour, halign, valign, alphaoverride, blk)
|
||||
else
|
||||
if (valign == TEXT_ALIGN_CENTER) then y = y - (self.totalHeight / 2)
|
||||
elseif (valign == TEXT_ALIGN_BOTTOM) then y = y - (self.totalHeight)
|
||||
end
|
||||
|
||||
local alpha = blk.colour.a
|
||||
if (alphaoverride) then alpha = alphaoverride end
|
||||
|
||||
surface.SetFont( blk.font )
|
||||
surface.SetTextColor( blk.colour.r, blk.colour.g, blk.colour.b, alpha )
|
||||
surface.SetTextPos( x, y )
|
||||
surface.DrawText( blk.text )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: Parse(ml, maxwidth)
|
||||
Desc: Parses the pseudo-html markup language, and creates a
|
||||
MarkupObject, which can be used to the draw the
|
||||
text to the screen. Valid tags are: font and colour.
|
||||
\n and \t are also available to move to the next line,
|
||||
or insert a tab character.
|
||||
Maxwidth can be used to make the text wrap to a specific
|
||||
width.
|
||||
Usage: markup.Parse("<font=Default>changed font</font>\n<colour=255,0,255,255>changed colour</colour>")
|
||||
]]
|
||||
function ix.markup.Parse(ml, maxwidth)
|
||||
|
||||
ml = utf8.force(ml)
|
||||
|
||||
colour_stack = { {r=255,g=255,b=255,a=255} }
|
||||
font_stack = { "DermaDefault" }
|
||||
blocks = {}
|
||||
|
||||
if (not string.find(ml, "<")) then
|
||||
ml = ml .. "<nop>"
|
||||
end
|
||||
|
||||
string.gsub(ml, "([^<>]*)(<[^>]+.)([^<>]*)", ProcessMatches)
|
||||
|
||||
local xOffset = 0
|
||||
local yOffset = 0
|
||||
local xSize = 0
|
||||
local xMax = 0
|
||||
local thisMaxY = 0
|
||||
local new_block_list = {}
|
||||
local ymaxes = {}
|
||||
local texOffset = 0
|
||||
|
||||
local lineHeight = 0
|
||||
for i = 1, #blocks do
|
||||
local block = blocks[i]
|
||||
|
||||
if (block.text) then
|
||||
surface.SetFont(block.font)
|
||||
|
||||
local thisY = 0
|
||||
local curString = ""
|
||||
block.text = string.gsub(block.text, ">", ">")
|
||||
block.text = string.gsub(block.text, "<", "<")
|
||||
block.text = string.gsub(block.text, "&", "&")
|
||||
|
||||
for j=1,string.utf8len(block.text) do
|
||||
local ch = string.utf8sub(block.text,j,j)
|
||||
|
||||
if (ch == "\n") then
|
||||
if (thisY == 0) then
|
||||
thisY = lineHeight + texOffset;
|
||||
thisMaxY = lineHeight + texOffset;
|
||||
else
|
||||
lineHeight = thisY + texOffset
|
||||
end
|
||||
|
||||
if (string.utf8len(curString) > 0) then
|
||||
local x1,y1 = surface.GetTextSize(curString)
|
||||
|
||||
local new_block = {}
|
||||
new_block.text = curString
|
||||
new_block.font = block.font
|
||||
new_block.colour = block.colour
|
||||
new_block.thisY = thisY
|
||||
new_block.thisX = x1
|
||||
new_block.offset = {}
|
||||
new_block.offset.x = xOffset
|
||||
new_block.offset.y = yOffset
|
||||
table.insert(new_block_list, new_block)
|
||||
if (xOffset + x1 > xMax) then
|
||||
xMax = xOffset + x1
|
||||
end
|
||||
end
|
||||
|
||||
xOffset = 0
|
||||
xSize = 0
|
||||
yOffset = yOffset + thisMaxY;
|
||||
thisY = 0
|
||||
curString = ""
|
||||
thisMaxY = 0
|
||||
elseif (ch == "\t") then
|
||||
|
||||
if (string.utf8len(curString) > 0) then
|
||||
local x1,y1 = surface.GetTextSize(curString)
|
||||
|
||||
local new_block = {}
|
||||
new_block.text = curString
|
||||
new_block.font = block.font
|
||||
new_block.colour = block.colour
|
||||
new_block.thisY = thisY
|
||||
new_block.thisX = x1
|
||||
new_block.offset = {}
|
||||
new_block.offset.x = xOffset
|
||||
new_block.offset.y = yOffset
|
||||
table.insert(new_block_list, new_block)
|
||||
if (xOffset + x1 > xMax) then
|
||||
xMax = xOffset + x1
|
||||
end
|
||||
end
|
||||
|
||||
local xOldSize = xSize
|
||||
xSize = 0
|
||||
curString = ""
|
||||
local xOldOffset = xOffset
|
||||
xOffset = math.ceil( (xOffset + xOldSize) / 50 ) * 50
|
||||
|
||||
if (xOffset == xOldOffset) then
|
||||
xOffset = xOffset + 50
|
||||
end
|
||||
else
|
||||
local x,y = surface.GetTextSize(ch)
|
||||
|
||||
if (x == nil) then return end
|
||||
|
||||
if (maxwidth and maxwidth > x) then
|
||||
if (xOffset + xSize + x >= maxwidth) then
|
||||
|
||||
-- need to: find the previous space in the curString
|
||||
-- if we can't find one, take off the last character
|
||||
-- and add a -. add the character to ch
|
||||
-- and insert as a new block, incrementing the y etc
|
||||
|
||||
local lastSpacePos = string.utf8len(curString)
|
||||
for k=1,string.utf8len(curString) do
|
||||
local chspace = string.utf8sub(curString,k,k)
|
||||
if (chspace == " ") then
|
||||
lastSpacePos = k
|
||||
end
|
||||
end
|
||||
|
||||
if (lastSpacePos == string.utf8len(curString)) then
|
||||
ch = string.utf8sub(curString,lastSpacePos,lastSpacePos) .. ch
|
||||
j = lastSpacePos
|
||||
curString = string.utf8sub(curString, 1, lastSpacePos-1)
|
||||
else
|
||||
ch = string.utf8sub(curString,lastSpacePos+1) .. ch
|
||||
j = lastSpacePos+1
|
||||
curString = string.utf8sub(curString, 1, lastSpacePos)
|
||||
end
|
||||
|
||||
local m = 1
|
||||
while string.utf8sub(ch, m, m) == " " and m <= string.utf8len(ch) do
|
||||
m = m + 1
|
||||
end
|
||||
ch = string.utf8sub(ch, m)
|
||||
|
||||
local x1,y1 = surface.GetTextSize(curString)
|
||||
|
||||
if (y1 > thisMaxY) then thisMaxY = y1; ymaxes[yOffset] = thisMaxY; lineHeight = y1; end
|
||||
|
||||
local new_block = {}
|
||||
new_block.text = curString
|
||||
new_block.font = block.font
|
||||
new_block.colour = block.colour
|
||||
new_block.thisY = thisY
|
||||
new_block.thisX = x1
|
||||
new_block.offset = {}
|
||||
new_block.offset.x = xOffset
|
||||
new_block.offset.y = yOffset
|
||||
table.insert(new_block_list, new_block)
|
||||
|
||||
if (xOffset + x1 > xMax) then
|
||||
xMax = xOffset + x1
|
||||
end
|
||||
|
||||
xOffset = 0
|
||||
xSize = 0
|
||||
x,y = surface.GetTextSize(ch)
|
||||
yOffset = yOffset + thisMaxY;
|
||||
thisY = 0
|
||||
curString = ""
|
||||
thisMaxY = 0
|
||||
end
|
||||
end
|
||||
|
||||
curString = curString .. ch
|
||||
|
||||
thisY = y
|
||||
xSize = xSize + x
|
||||
|
||||
if (y > thisMaxY) then thisMaxY = y; ymaxes[yOffset] = thisMaxY; lineHeight = y; end
|
||||
end
|
||||
end
|
||||
|
||||
if (string.utf8len(curString) > 0) then
|
||||
|
||||
local x1,y1 = surface.GetTextSize(curString)
|
||||
|
||||
local new_block = {}
|
||||
new_block.text = curString
|
||||
new_block.font = block.font
|
||||
new_block.colour = block.colour
|
||||
new_block.thisY = thisY
|
||||
new_block.thisX = x1
|
||||
new_block.offset = {}
|
||||
new_block.offset.x = xOffset
|
||||
new_block.offset.y = yOffset
|
||||
table.insert(new_block_list, new_block)
|
||||
|
||||
lineHeight = thisY
|
||||
|
||||
if (xOffset + x1 > xMax) then
|
||||
xMax = xOffset + x1
|
||||
end
|
||||
xOffset = xOffset + x1
|
||||
end
|
||||
xSize = 0
|
||||
elseif (block.texture) then
|
||||
local newBlock = table.Copy(block)
|
||||
newBlock.colour = block.colour or {r = 255, g = 255, b = 255, a = 255}
|
||||
newBlock.thisX = block.w
|
||||
newBlock.thisY = block.h
|
||||
newBlock.offset = {
|
||||
x = xOffset,
|
||||
y = 0
|
||||
}
|
||||
|
||||
table.insert(new_block_list, newBlock)
|
||||
xOffset = xOffset + block.w + 1
|
||||
texOffset = block.h / 2
|
||||
end
|
||||
end
|
||||
|
||||
local totalHeight = 0
|
||||
for i = 1, #new_block_list do
|
||||
local block = new_block_list[i]
|
||||
block.height = ymaxes[block.offset.y]
|
||||
|
||||
if (block.texture) then
|
||||
block.offset.y = ymaxes[0] * 0.5 - block.h * 0.5
|
||||
end
|
||||
|
||||
if (block.height and block.offset.y + block.height > totalHeight) then
|
||||
totalHeight = block.offset.y + block.height
|
||||
end
|
||||
end
|
||||
|
||||
local newObject = MarkupObject:create()
|
||||
newObject.totalHeight = totalHeight
|
||||
newObject.totalWidth = xMax
|
||||
newObject.blocks = new_block_list
|
||||
return newObject
|
||||
end
|
||||
68
gamemodes/helix/gamemode/core/libs/cl_networking.lua
Normal file
68
gamemodes/helix/gamemode/core/libs/cl_networking.lua
Normal file
@@ -0,0 +1,68 @@
|
||||
--[[
|
||||
| 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 entityMeta = FindMetaTable("Entity")
|
||||
local playerMeta = FindMetaTable("Player")
|
||||
|
||||
ix.net = ix.net or {}
|
||||
ix.net.globals = ix.net.globals or {}
|
||||
|
||||
net.Receive("ixGlobalVarSet", function()
|
||||
ix.net.globals[net.ReadString()] = net.ReadType()
|
||||
end)
|
||||
|
||||
net.Receive("ixNetVarSet", function()
|
||||
local index = net.ReadUInt(16)
|
||||
|
||||
ix.net[index] = ix.net[index] or {}
|
||||
ix.net[index][net.ReadString()] = net.ReadType()
|
||||
end)
|
||||
|
||||
net.Receive("ixNetStatics", function()
|
||||
for _ = 1, net.ReadUInt(16) do
|
||||
local id = net.ReadUInt(16)
|
||||
if (id == 0) then continue end
|
||||
ix.net[id] = ix.net[id] or {}
|
||||
ix.net[id].Persistent = true
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixNetVarDelete", function()
|
||||
ix.net[net.ReadUInt(16)] = nil
|
||||
end)
|
||||
|
||||
net.Receive("ixLocalVarSet", function()
|
||||
local key = net.ReadString()
|
||||
local var = net.ReadType()
|
||||
|
||||
ix.net[LocalPlayer():EntIndex()] = ix.net[LocalPlayer():EntIndex()] or {}
|
||||
ix.net[LocalPlayer():EntIndex()][key] = var
|
||||
|
||||
hook.Run("OnLocalVarSet", key, var)
|
||||
end)
|
||||
|
||||
function GetNetVar(key, default) -- luacheck: globals GetNetVar
|
||||
local value = ix.net.globals[key]
|
||||
|
||||
return value != nil and value or default
|
||||
end
|
||||
|
||||
function entityMeta:GetNetVar(key, default)
|
||||
local index = self:EntIndex()
|
||||
|
||||
if (ix.net[index] and ix.net[index][key] != nil) then
|
||||
return ix.net[index][key]
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
playerMeta.GetLocalVar = entityMeta.GetNetVar
|
||||
160
gamemodes/helix/gamemode/core/libs/sh_animation.lua
Normal file
160
gamemodes/helix/gamemode/core/libs/sh_animation.lua
Normal file
@@ -0,0 +1,160 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
function ix.util.InstallAnimationMethods(meta)
|
||||
local function TweenAnimationThink(object)
|
||||
for k, v in pairs(object.tweenAnimations) do
|
||||
if (!v.bShouldPlay) then
|
||||
continue
|
||||
end
|
||||
|
||||
local bComplete = v:update(FrameTime())
|
||||
|
||||
if (v.Think) then
|
||||
v:Think(object)
|
||||
end
|
||||
|
||||
if (bComplete) then
|
||||
v.bShouldPlay = nil
|
||||
|
||||
v:ForceComplete()
|
||||
|
||||
if (v.OnComplete) then
|
||||
v:OnComplete(object)
|
||||
end
|
||||
|
||||
if (v.bRemoveOnComplete) then
|
||||
object.tweenAnimations[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function meta:GetTweenAnimation(index, bNoPlay)
|
||||
-- if we don't need to check if the animation is playing we can just return the animation
|
||||
if (bNoPlay) then
|
||||
return self.tweenAnimations[index]
|
||||
else
|
||||
for k, v in pairs(self.tweenAnimations or {}) do
|
||||
if (k == index and v.bShouldPlay) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function meta:IsPlayingTweenAnimation(index)
|
||||
for k, v in pairs(self.tweenAnimations or {}) do
|
||||
if (v.bShouldPlay and index == k) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function meta:StopAnimations(bRemove)
|
||||
for k, v in pairs(self.tweenAnimations or {}) do
|
||||
if (v.bShouldPlay) then
|
||||
v:ForceComplete()
|
||||
|
||||
if (bRemove) then
|
||||
self.tweenAnimations[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function meta:CreateAnimation(length, data)
|
||||
local animations = self.tweenAnimations or {}
|
||||
self.tweenAnimations = animations
|
||||
|
||||
if (self.SetAnimationEnabled) then
|
||||
self:SetAnimationEnabled(true)
|
||||
end
|
||||
|
||||
local index = data.index or 1
|
||||
local bCancelPrevious = data.bCancelPrevious == nil and false or data.bCancelPrevious
|
||||
local bIgnoreConfig = SERVER or (data.bIgnoreConfig == nil and false or data.bIgnoreConfig)
|
||||
|
||||
if (bCancelPrevious and self:IsPlayingTweenAnimation()) then
|
||||
for _, v in pairs(animations) do
|
||||
v:set(v.duration)
|
||||
end
|
||||
end
|
||||
|
||||
local animation = ix.tween.new(
|
||||
((length == 0 and 1 or length) or 1) * (bIgnoreConfig and 1 or ix.option.Get("animationScale", 1)),
|
||||
data.subject or self,
|
||||
data.target or {},
|
||||
data.easing or "linear"
|
||||
)
|
||||
|
||||
animation.index = index
|
||||
animation.bIgnoreConfig = bIgnoreConfig
|
||||
animation.bAutoFire = (data.bAutoFire == nil and true or data.bAutoFire)
|
||||
animation.bRemoveOnComplete = (data.bRemoveOnComplete == nil and true or data.bRemoveOnComplete)
|
||||
animation.Think = data.Think
|
||||
animation.OnComplete = data.OnComplete
|
||||
|
||||
animation.ForceComplete = function(anim)
|
||||
anim:set(anim.duration)
|
||||
end
|
||||
|
||||
-- @todo don't use ridiculous method chaining
|
||||
animation.CreateAnimation = function(currentAnimation, newLength, newData)
|
||||
newData.bAutoFire = false
|
||||
newData.index = currentAnimation.index + 1
|
||||
|
||||
local oldOnComplete = currentAnimation.OnComplete
|
||||
local newAnimation = currentAnimation.subject:CreateAnimation(newLength, newData)
|
||||
|
||||
currentAnimation.OnComplete = function(...)
|
||||
if (oldOnComplete) then
|
||||
oldOnComplete(...)
|
||||
end
|
||||
|
||||
newAnimation:Fire()
|
||||
end
|
||||
|
||||
return newAnimation
|
||||
end
|
||||
|
||||
if (length == 0 or (!animation.bIgnoreConfig and ix.option.Get("disableAnimations", false))) then
|
||||
animation.Fire = function(anim)
|
||||
anim:set(anim.duration)
|
||||
anim.bShouldPlay = true
|
||||
end
|
||||
else
|
||||
animation.Fire = function(anim)
|
||||
anim:set(0)
|
||||
anim.bShouldPlay = true
|
||||
end
|
||||
end
|
||||
|
||||
-- we can assume if we're using this library, we're not going to use the built-in
|
||||
-- AnimationTo functions, so override AnimationThink with our own
|
||||
self.AnimationThink = TweenAnimationThink
|
||||
|
||||
-- fire right away if autofire is enabled
|
||||
if (animation.bAutoFire) then
|
||||
animation:Fire()
|
||||
end
|
||||
|
||||
self.tweenAnimations[index] = animation
|
||||
return animation
|
||||
end
|
||||
end
|
||||
|
||||
if (CLIENT) then
|
||||
local panelMeta = FindMetaTable("Panel")
|
||||
ix.util.InstallAnimationMethods(panelMeta)
|
||||
end
|
||||
571
gamemodes/helix/gamemode/core/libs/sh_anims.lua
Normal file
571
gamemodes/helix/gamemode/core/libs/sh_anims.lua
Normal file
@@ -0,0 +1,571 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Player model animation.
|
||||
|
||||
Helix comes with support for using NPC animations/models as regular player models by manually translating animations. There are
|
||||
a few standard animation sets that are built-in that should cover most non-player models:
|
||||
citizen_male
|
||||
citizen_female
|
||||
metrocop
|
||||
overwatch
|
||||
vortigaunt
|
||||
player
|
||||
zombie
|
||||
fastZombie
|
||||
|
||||
If you find that your models are T-posing when they work elsewhere, you'll probably need to set the model class for your
|
||||
model with `ix.anim.SetModelClass` in order for the correct animations to be used. If you'd like to add your own animation
|
||||
class, simply add to the `ix.anim` table with a model class name and the required animation translation table.
|
||||
]]
|
||||
-- @module ix.anim
|
||||
|
||||
ix.anim = ix.anim or {}
|
||||
ix.anim.citizen_male = {
|
||||
normal = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
pistol = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_PISTOL, ACT_IDLE_ANGRY_PISTOL},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_ATTACK_PISTOL_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_PISTOL,
|
||||
reload = ACT_RELOAD_PISTOL
|
||||
},
|
||||
smg = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1_RELAXED, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_SMG1,
|
||||
reload = ACT_GESTURE_RELOAD_SMG1
|
||||
},
|
||||
shotgun = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SHOTGUN_RELAXED, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_SHOTGUN
|
||||
},
|
||||
grenade = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_RANGE_ATTACK_THROW
|
||||
},
|
||||
melee = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_MELEE},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_MELEE_ATTACK_SWING
|
||||
},
|
||||
glide = ACT_GLIDE,
|
||||
vehicle = {
|
||||
["prop_vehicle_prisoner_pod"] = {"podpose", Vector(-3, 0, 0)},
|
||||
["prop_vehicle_jeep"] = {ACT_BUSY_SIT_CHAIR, Vector(14, 0, -14)},
|
||||
["prop_vehicle_airboat"] = {ACT_BUSY_SIT_CHAIR, Vector(8, 0, -20)},
|
||||
chair = {ACT_BUSY_SIT_CHAIR, Vector(1, 0, -23)}
|
||||
},
|
||||
}
|
||||
|
||||
ix.anim.citizen_female = {
|
||||
normal = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
pistol = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_PISTOL, ACT_IDLE_ANGRY_PISTOL},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_PISTOL,
|
||||
reload = ACT_RELOAD_PISTOL
|
||||
},
|
||||
smg = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1_RELAXED, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_SMG1,
|
||||
reload = ACT_GESTURE_RELOAD_SMG1
|
||||
},
|
||||
shotgun = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SHOTGUN_RELAXED, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_SHOTGUN
|
||||
},
|
||||
grenade = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_RANGE_ATTACK_THROW
|
||||
},
|
||||
melee = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_MELEE_ATTACK_SWING
|
||||
},
|
||||
glide = ACT_GLIDE,
|
||||
vehicle = ix.anim.citizen_male.vehicle
|
||||
}
|
||||
ix.anim.metrocop = {
|
||||
normal = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
pistol = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_PISTOL, ACT_IDLE_ANGRY_PISTOL},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_PISTOL_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_PISTOL, ACT_WALK_AIM_PISTOL},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN_PISTOL, ACT_RUN_AIM_PISTOL},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_PISTOL,
|
||||
reload = ACT_GESTURE_RELOAD_PISTOL
|
||||
},
|
||||
smg = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_SMG1_LOW, ACT_COVER_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_RIFLE},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
shotgun = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_SMG1_LOW, ACT_COVER_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_RIFLE},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
grenade = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_MELEE},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_PISTOL_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_ANGRY},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_COMBINE_THROW_GRENADE
|
||||
},
|
||||
melee = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_MELEE},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_PISTOL_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_ANGRY},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_MELEE_ATTACK_SWING_GESTURE
|
||||
},
|
||||
glide = ACT_GLIDE,
|
||||
vehicle = {
|
||||
chair = {ACT_COVER_PISTOL_LOW, Vector(5, 0, -5)},
|
||||
["prop_vehicle_airboat"] = {ACT_COVER_PISTOL_LOW, Vector(10, 0, 0)},
|
||||
["prop_vehicle_jeep"] = {ACT_COVER_PISTOL_LOW, Vector(18, -2, 4)},
|
||||
["prop_vehicle_prisoner_pod"] = {ACT_IDLE, Vector(-4, -0.5, 0)}
|
||||
}
|
||||
}
|
||||
|
||||
ix.anim.metrocop_female = {
|
||||
normal = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
pistol = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_PISTOL, ACT_IDLE_ANGRY_PISTOL},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_PISTOL,
|
||||
reload = ACT_RELOAD_PISTOL
|
||||
},
|
||||
smg = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1_RELAXED, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_SMG1,
|
||||
reload = ACT_GESTURE_RELOAD_SMG1
|
||||
},
|
||||
shotgun = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SHOTGUN_RELAXED, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_SHOTGUN
|
||||
},
|
||||
grenade = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_RANGE_ATTACK_THROW
|
||||
},
|
||||
melee = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_MELEE_ATTACK_SWING
|
||||
},
|
||||
glide = ACT_GLIDE,
|
||||
vehicle = ix.anim.citizen_male.vehicle
|
||||
}
|
||||
|
||||
ix.anim.overwatch = {
|
||||
normal = {
|
||||
[ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
|
||||
[ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
pistol = {
|
||||
[ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
|
||||
[ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
smg = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_RIFLE},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
shotgun = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SHOTGUN},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_SHOTGUN},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_SHOTGUN},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
grenade = {
|
||||
[ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
|
||||
[ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
melee = {
|
||||
[ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
|
||||
[ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_MELEE_ATTACK_SWING_GESTURE
|
||||
},
|
||||
glide = ACT_GLIDE
|
||||
}
|
||||
ix.anim.vortigaunt = {
|
||||
melee = {
|
||||
["attack"] = ACT_MELEE_ATTACK1,
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, "ActionIdle"},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM},
|
||||
},
|
||||
grenade = {
|
||||
["attack"] = ACT_MELEE_ATTACK1,
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, "ActionIdle"},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM}
|
||||
},
|
||||
normal = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM},
|
||||
["attack"] = ACT_MELEE_ATTACK1
|
||||
},
|
||||
pistol = { -- beam
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM},
|
||||
["attack"] = ACT_GESTURE_RANGE_ATTACK_PISTOL,
|
||||
["reload"] = ACT_IDLE,
|
||||
["glide"] = {ACT_RUN, ACT_RUN}
|
||||
},
|
||||
shotgun = { -- broom
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, "sweep_idle"},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_WALK] = {"Walk_all_HoldBroom", "Walk_all_HoldBroom"},
|
||||
["attack"] = ACT_IDLE,
|
||||
["reload"] = ACT_IDLE,
|
||||
["glide"] = {ACT_RUN, ACT_RUN}
|
||||
},
|
||||
smg = { -- heal
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM},
|
||||
["attack"] = ACT_IDLE,
|
||||
["reload"] = ACT_IDLE,
|
||||
["glide"] = {ACT_RUN, ACT_RUN}
|
||||
},
|
||||
glide = "jump_holding_glide"
|
||||
}
|
||||
ix.anim.player = {
|
||||
normal = {
|
||||
[ACT_MP_STAND_IDLE] = ACT_HL2MP_IDLE,
|
||||
[ACT_MP_CROUCH_IDLE] = ACT_HL2MP_IDLE_CROUCH,
|
||||
[ACT_MP_WALK] = ACT_HL2MP_WALK,
|
||||
[ACT_MP_RUN] = ACT_HL2MP_RUN,
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
passive = {
|
||||
[ACT_MP_STAND_IDLE] = ACT_HL2MP_IDLE_PASSIVE,
|
||||
[ACT_MP_WALK] = ACT_HL2MP_WALK_PASSIVE,
|
||||
[ACT_MP_CROUCHWALK] = ACT_HL2MP_WALK_CROUCH_PASSIVE,
|
||||
[ACT_MP_RUN] = ACT_HL2MP_RUN_PASSIVE,
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
}
|
||||
}
|
||||
ix.anim.zombie = {
|
||||
[ACT_MP_STAND_IDLE] = ACT_HL2MP_IDLE_ZOMBIE,
|
||||
[ACT_MP_CROUCH_IDLE] = ACT_HL2MP_IDLE_CROUCH_ZOMBIE,
|
||||
[ACT_MP_CROUCHWALK] = ACT_HL2MP_WALK_CROUCH_ZOMBIE_01,
|
||||
[ACT_MP_WALK] = ACT_HL2MP_WALK_ZOMBIE_02,
|
||||
[ACT_MP_RUN] = ACT_HL2MP_RUN_ZOMBIE,
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
}
|
||||
ix.anim.fastZombie = {
|
||||
[ACT_MP_STAND_IDLE] = ACT_HL2MP_WALK_ZOMBIE,
|
||||
[ACT_MP_CROUCH_IDLE] = ACT_HL2MP_IDLE_CROUCH_ZOMBIE,
|
||||
[ACT_MP_CROUCHWALK] = ACT_HL2MP_WALK_CROUCH_ZOMBIE_05,
|
||||
[ACT_MP_WALK] = ACT_HL2MP_WALK_ZOMBIE_06,
|
||||
[ACT_MP_RUN] = ACT_HL2MP_RUN_ZOMBIE_FAST,
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
}
|
||||
|
||||
local translations = {}
|
||||
|
||||
--- Sets a model's animation class.
|
||||
-- @realm shared
|
||||
-- @string model Model name to set the animation class for
|
||||
-- @string class Animation class to assign to the model
|
||||
-- @usage ix.anim.SetModelClass("models/police.mdl", "metrocop")
|
||||
function ix.anim.SetModelClass(model, class)
|
||||
if (!ix.anim[class]) then
|
||||
error("'" .. tostring(class) .. "' is not a valid animation class!")
|
||||
end
|
||||
|
||||
translations[model:lower()] = class
|
||||
end
|
||||
|
||||
--- Gets a model's animation class.
|
||||
-- @realm shared
|
||||
-- @string model Model to get the animation class for
|
||||
-- @treturn[1] string Animation class of the model
|
||||
-- @treturn[2] nil If there was no animation associated with the given model
|
||||
-- @usage ix.anim.GetModelClass("models/police.mdl")
|
||||
-- > metrocop
|
||||
function ix.anim.GetModelClass(model)
|
||||
model = string.lower(model)
|
||||
local class = translations[model]
|
||||
|
||||
if (!class and string.find(model, "/player")) then
|
||||
return "player"
|
||||
end
|
||||
|
||||
class = class or "citizen_male"
|
||||
|
||||
if (class == "citizen_male" and (
|
||||
string.find(model, "female") or
|
||||
string.find(model, "alyx") or
|
||||
string.find(model, "mossman"))) then
|
||||
class = "citizen_female"
|
||||
end
|
||||
|
||||
return class
|
||||
end
|
||||
|
||||
ix.anim.SetModelClass("models/police.mdl", "metrocop")
|
||||
ix.anim.SetModelClass("models/combine_super_soldier.mdl", "overwatch")
|
||||
ix.anim.SetModelClass("models/combine_soldier_prisonGuard.mdl", "overwatch")
|
||||
ix.anim.SetModelClass("models/combine_soldier.mdl", "overwatch")
|
||||
ix.anim.SetModelClass("models/vortigaunt.mdl", "vortigaunt")
|
||||
ix.anim.SetModelClass("models/vortigaunt_blue.mdl", "vortigaunt")
|
||||
ix.anim.SetModelClass("models/vortigaunt_doctor.mdl", "vortigaunt")
|
||||
ix.anim.SetModelClass("models/vortigaunt_slave.mdl", "vortigaunt")
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixSequenceSet")
|
||||
util.AddNetworkString("ixSequenceReset")
|
||||
|
||||
local playerMeta = FindMetaTable("Player")
|
||||
|
||||
--- Player anim methods
|
||||
-- @classmod Player
|
||||
|
||||
--- Forces this player's model to play an animation sequence. It also prevents the player from firing their weapon while the
|
||||
-- animation is playing.
|
||||
-- @realm server
|
||||
-- @string sequence Name of the animation sequence to play
|
||||
-- @func[opt=nil] callback Function to call when the animation finishes. This is also called immediately if the animation
|
||||
-- fails to play
|
||||
-- @number[opt=nil] time How long to play the animation for. This defaults to the duration of the animation
|
||||
-- @bool[opt=false] bNoFreeze Whether or not to avoid freezing this player in place while the animation is playing
|
||||
-- @see LeaveSequence
|
||||
function playerMeta:ForceSequence(sequence, callback, time, bNoFreeze)
|
||||
hook.Run("PlayerEnterSequence", self, sequence, callback, time, bNoFreeze)
|
||||
|
||||
if (!sequence) then
|
||||
net.Start("ixSequenceReset")
|
||||
net.WriteEntity(self)
|
||||
net.Broadcast()
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
sequence = self:LookupSequence(tostring(sequence))
|
||||
|
||||
if (sequence and sequence > 0) then
|
||||
time = time or self:SequenceDuration(sequence)
|
||||
|
||||
self.ixCouldShoot = self:GetNetVar("canShoot", false)
|
||||
self.ixSeqCallback = callback
|
||||
self:SetCycle(0)
|
||||
self:SetPlaybackRate(1)
|
||||
self:SetNetVar("forcedSequence", sequence)
|
||||
self:SetNetVar("canShoot", false)
|
||||
|
||||
if (!bNoFreeze) then
|
||||
self:SetMoveType(MOVETYPE_NONE)
|
||||
end
|
||||
|
||||
if (time > 0) then
|
||||
timer.Create("ixSeq"..self:EntIndex(), time, 1, function()
|
||||
if (IsValid(self)) then
|
||||
self:LeaveSequence()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
net.Start("ixSequenceSet")
|
||||
net.WriteEntity(self)
|
||||
net.Broadcast()
|
||||
|
||||
return time
|
||||
elseif (callback) then
|
||||
callback()
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Forcefully stops this player's model from playing an animation that was started by `ForceSequence`.
|
||||
-- @realm server
|
||||
function playerMeta:LeaveSequence()
|
||||
hook.Run("PlayerLeaveSequence", self)
|
||||
|
||||
net.Start("ixSequenceReset")
|
||||
net.WriteEntity(self)
|
||||
net.Broadcast()
|
||||
|
||||
self:SetNetVar("canShoot", self.ixCouldShoot)
|
||||
self:SetNetVar("forcedSequence", nil)
|
||||
self:SetMoveType(MOVETYPE_WALK)
|
||||
self.ixCouldShoot = nil
|
||||
|
||||
if (self.ixSeqCallback) then
|
||||
self:ixSeqCallback()
|
||||
end
|
||||
end
|
||||
else
|
||||
net.Receive("ixSequenceSet", function()
|
||||
local entity = net.ReadEntity()
|
||||
|
||||
if (IsValid(entity)) then
|
||||
hook.Run("PlayerEnterSequence", entity)
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixSequenceReset", function()
|
||||
local entity = net.ReadEntity()
|
||||
|
||||
if (IsValid(entity)) then
|
||||
hook.Run("PlayerLeaveSequence", entity)
|
||||
end
|
||||
end)
|
||||
end
|
||||
201
gamemodes/helix/gamemode/core/libs/sh_attribs.lua
Normal file
201
gamemodes/helix/gamemode/core/libs/sh_attribs.lua
Normal file
@@ -0,0 +1,201 @@
|
||||
--[[
|
||||
| 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 ix.attributes
|
||||
|
||||
if (!ix.char) then
|
||||
include("sh_character.lua")
|
||||
end
|
||||
|
||||
ix.attributes = ix.attributes or {}
|
||||
ix.attributes.list = ix.attributes.list or {}
|
||||
|
||||
function ix.attributes.LoadFromDir(directory)
|
||||
for _, v in ipairs(file.Find(directory.."/*.lua", "LUA")) do
|
||||
local niceName = v:sub(4, -5)
|
||||
|
||||
ATTRIBUTE = ix.attributes.list[niceName] or {}
|
||||
if (PLUGIN) then
|
||||
ATTRIBUTE.plugin = PLUGIN.uniqueID
|
||||
end
|
||||
|
||||
ix.util.Include(directory.."/"..v)
|
||||
|
||||
ATTRIBUTE.name = ATTRIBUTE.name or "Unknown"
|
||||
ATTRIBUTE.description = ATTRIBUTE.description or "No description availalble."
|
||||
|
||||
ix.attributes.list[niceName] = ATTRIBUTE
|
||||
ATTRIBUTE = nil
|
||||
end
|
||||
end
|
||||
|
||||
function ix.attributes.Setup(client)
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if (character) then
|
||||
for k, v in pairs(ix.attributes.list) do
|
||||
if (v.OnSetup) then
|
||||
v:OnSetup(client, character:GetAttribute(k, 0))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
--- Character attribute methods
|
||||
-- @classmod Character
|
||||
local charMeta = ix.meta.character
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixAttributeUpdate")
|
||||
|
||||
--- Increments one of this character's attributes by the given amount.
|
||||
-- @realm server
|
||||
-- @string key Name of the attribute to update
|
||||
-- @number value Amount to add to the attribute
|
||||
function charMeta:UpdateAttrib(key, value)
|
||||
local attribute = ix.attributes.list[key]
|
||||
local client = self:GetPlayer()
|
||||
|
||||
if (attribute) then
|
||||
local attrib = self:GetAttributes()
|
||||
|
||||
attrib[key] = math.min((attrib[key] or 0) + value, attribute.maxValue or ix.config.Get("maxAttributes", 100))
|
||||
|
||||
if (IsValid(client)) then
|
||||
net.Start("ixAttributeUpdate")
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteString(key)
|
||||
net.WriteFloat(attrib[key])
|
||||
net.Send(client)
|
||||
|
||||
if (attribute.Setup) then
|
||||
attribute.Setup(attrib[key])
|
||||
end
|
||||
end
|
||||
|
||||
self:SetAttributes(attrib)
|
||||
end
|
||||
|
||||
hook.Run("CharacterAttributeUpdated", client, self, key, value)
|
||||
end
|
||||
|
||||
--- Sets the value of an attribute for this character.
|
||||
-- @realm server
|
||||
-- @string key Name of the attribute to update
|
||||
-- @number value New value for the attribute
|
||||
function charMeta:SetAttrib(key, value)
|
||||
local attribute = ix.attributes.list[key]
|
||||
local client = self:GetPlayer()
|
||||
|
||||
if (attribute) then
|
||||
local attrib = self:GetAttributes()
|
||||
|
||||
attrib[key] = value
|
||||
|
||||
if (IsValid(client)) then
|
||||
net.Start("ixAttributeUpdate")
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteString(key)
|
||||
net.WriteFloat(attrib[key])
|
||||
net.Send(client)
|
||||
|
||||
if (attribute.Setup) then
|
||||
attribute.Setup(attrib[key])
|
||||
end
|
||||
end
|
||||
|
||||
self:SetAttributes(attrib)
|
||||
end
|
||||
|
||||
hook.Run("CharacterAttributeUpdated", client, self, key, value)
|
||||
end
|
||||
|
||||
--- Temporarily increments one of this character's attributes. Useful for things like consumable items.
|
||||
-- @realm server
|
||||
-- @string boostID Unique ID to use for the boost to remove it later
|
||||
-- @string attribID Name of the attribute to boost
|
||||
-- @number boostAmount Amount to increase the attribute by
|
||||
function charMeta:AddBoost(boostID, attribID, boostAmount)
|
||||
local boosts = self:GetVar("boosts", {})
|
||||
|
||||
boosts[attribID] = boosts[attribID] or {}
|
||||
boosts[attribID][boostID] = boostAmount
|
||||
|
||||
hook.Run("CharacterAttributeBoosted", self:GetPlayer(), self, attribID, boostID, boostAmount)
|
||||
|
||||
return self:SetVar("boosts", boosts, nil, self:GetPlayer())
|
||||
end
|
||||
|
||||
--- Removes a temporary boost from this character.
|
||||
-- @realm server
|
||||
-- @string boostID Unique ID of the boost to remove
|
||||
-- @string attribID Name of the attribute that was boosted
|
||||
function charMeta:RemoveBoost(boostID, attribID)
|
||||
local boosts = self:GetVar("boosts", {})
|
||||
|
||||
boosts[attribID] = boosts[attribID] or {}
|
||||
boosts[attribID][boostID] = nil
|
||||
|
||||
hook.Run("CharacterAttributeBoosted", self:GetPlayer(), self, attribID, boostID, true)
|
||||
|
||||
return self:SetVar("boosts", boosts, nil, self:GetPlayer())
|
||||
end
|
||||
else
|
||||
net.Receive("ixAttributeUpdate", function()
|
||||
local id = net.ReadUInt(32)
|
||||
local character = ix.char.loaded[id]
|
||||
|
||||
if (character) then
|
||||
local key = net.ReadString()
|
||||
local value = net.ReadFloat()
|
||||
|
||||
character:GetAttributes()[key] = value
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--- Returns all boosts that this character has for the given attribute. This is only valid on the server and owning client.
|
||||
-- @realm shared
|
||||
-- @string attribID Name of the attribute to find boosts for
|
||||
-- @treturn[1] table Table of boosts that this character has for the attribute
|
||||
-- @treturn[2] nil If the character has no boosts for the given attribute
|
||||
function charMeta:GetBoost(attribID)
|
||||
local boosts = self:GetBoosts()
|
||||
|
||||
return boosts[attribID]
|
||||
end
|
||||
|
||||
--- Returns all boosts that this character has. This is only valid on the server and owning client.
|
||||
-- @realm shared
|
||||
-- @treturn table Table of boosts this character has
|
||||
function charMeta:GetBoosts()
|
||||
return self:GetVar("boosts", {})
|
||||
end
|
||||
|
||||
--- Returns the current value of an attribute. This is only valid on the server and owning client.
|
||||
-- @realm shared
|
||||
-- @string key Name of the attribute to get
|
||||
-- @number default Value to return if the attribute doesn't exist
|
||||
-- @treturn number Value of the attribute
|
||||
function charMeta:GetAttribute(key, default)
|
||||
local att = self:GetAttributes()[key] or default
|
||||
local boosts = self:GetBoosts()[key]
|
||||
|
||||
if (boosts) then
|
||||
for _, v in pairs(boosts) do
|
||||
att = att + v
|
||||
end
|
||||
end
|
||||
|
||||
return att
|
||||
end
|
||||
end
|
||||
161
gamemodes/helix/gamemode/core/libs/sh_business.lua
Normal file
161
gamemodes/helix/gamemode/core/libs/sh_business.lua
Normal file
@@ -0,0 +1,161 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixBusinessBuy")
|
||||
util.AddNetworkString("ixBusinessResponse")
|
||||
util.AddNetworkString("ixShipmentUse")
|
||||
util.AddNetworkString("ixShipmentOpen")
|
||||
util.AddNetworkString("ixShipmentClose")
|
||||
|
||||
net.Receive("ixBusinessBuy", function(length, client)
|
||||
if (client.ixNextBusiness and client.ixNextBusiness > CurTime()) then
|
||||
client:NotifyLocalized("businessTooFast")
|
||||
return
|
||||
end
|
||||
|
||||
local char = client:GetCharacter()
|
||||
|
||||
if (!char) then
|
||||
return
|
||||
end
|
||||
|
||||
local indicies = net.ReadUInt(8)
|
||||
local items = {}
|
||||
|
||||
for _ = 1, indicies do
|
||||
items[net.ReadString()] = net.ReadUInt(8)
|
||||
end
|
||||
|
||||
if (table.IsEmpty(items)) then
|
||||
return
|
||||
end
|
||||
|
||||
local cost = 0
|
||||
|
||||
for k, v in pairs(items) do
|
||||
local itemTable = ix.item.list[k]
|
||||
|
||||
if (itemTable and hook.Run("CanPlayerUseBusiness", client, k) != false) then
|
||||
local amount = math.Clamp(tonumber(v) or 0, 0, 10)
|
||||
items[k] = amount
|
||||
|
||||
if (amount == 0) then
|
||||
items[k] = nil
|
||||
else
|
||||
cost = cost + (amount * (itemTable.price or 0))
|
||||
end
|
||||
else
|
||||
items[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
if (table.IsEmpty(items)) then
|
||||
return
|
||||
end
|
||||
|
||||
if (char:HasMoney(cost)) then
|
||||
char:TakeMoney(cost)
|
||||
|
||||
local entity = ents.Create("ix_shipment")
|
||||
entity:Spawn()
|
||||
entity:SetPos(client:GetItemDropPos(entity))
|
||||
entity:SetItems(items)
|
||||
entity:SetNetVar("owner", char:GetID())
|
||||
|
||||
local shipments = char:GetVar("charEnts") or {}
|
||||
table.insert(shipments, entity)
|
||||
char:SetVar("charEnts", shipments, true)
|
||||
|
||||
net.Start("ixBusinessResponse")
|
||||
net.Send(client)
|
||||
|
||||
hook.Run("CreateShipment", client, entity)
|
||||
|
||||
client.ixNextBusiness = CurTime() + 0.5
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixShipmentUse", function(length, client)
|
||||
local uniqueID = net.ReadString()
|
||||
local drop = net.ReadBool()
|
||||
|
||||
local entity = client.ixShipment
|
||||
local itemTable = ix.item.list[uniqueID]
|
||||
|
||||
if (itemTable and IsValid(entity)) then
|
||||
if (entity:GetPos():Distance(client:GetPos()) > 128) then
|
||||
client.ixShipment = nil
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local amount = entity.items[uniqueID]
|
||||
|
||||
if (amount and amount > 0) then
|
||||
if (entity.items[uniqueID] <= 0) then
|
||||
entity.items[uniqueID] = nil
|
||||
end
|
||||
|
||||
if (drop) then
|
||||
ix.item.Spawn(uniqueID, entity:GetPos() + Vector(0, 0, 16), function(item, itemEntity)
|
||||
if (IsValid(client)) then
|
||||
itemEntity.ixSteamID = client:SteamID()
|
||||
itemEntity.ixCharID = client:GetCharacter():GetID()
|
||||
end
|
||||
end)
|
||||
else
|
||||
if entity.itemData then
|
||||
local item, _ = client:GetCharacter():GetInventory():Add(uniqueID, 1, {
|
||||
data = entity.itemData
|
||||
})
|
||||
|
||||
if (!item) then
|
||||
return client:NotifyLocalized("noFit")
|
||||
end
|
||||
else
|
||||
local status, _ = client:GetCharacter():GetInventory():Add(uniqueID)
|
||||
|
||||
if (!status) then
|
||||
return client:NotifyLocalized("noFit")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
hook.Run("ShipmentItemTaken", client, uniqueID, amount)
|
||||
|
||||
entity.items[uniqueID] = entity.items[uniqueID] - 1
|
||||
|
||||
if (entity:GetItemCount() < 1) then
|
||||
entity:GibBreakServer(Vector(0, 0, 0.5))
|
||||
entity:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixShipmentClose", function(length, client)
|
||||
local entity = client.ixShipment
|
||||
|
||||
if (IsValid(entity)) then
|
||||
entity.ixInteractionDirty = false
|
||||
client.ixShipment = nil
|
||||
end
|
||||
end)
|
||||
else
|
||||
net.Receive("ixShipmentOpen", function()
|
||||
local entity = net.ReadEntity()
|
||||
local items = net.ReadTable()
|
||||
|
||||
ix.gui.shipment = vgui.Create("ixShipment")
|
||||
ix.gui.shipment:SetItems(entity, items)
|
||||
end)
|
||||
end
|
||||
1282
gamemodes/helix/gamemode/core/libs/sh_character.lua
Normal file
1282
gamemodes/helix/gamemode/core/libs/sh_character.lua
Normal file
File diff suppressed because it is too large
Load Diff
758
gamemodes/helix/gamemode/core/libs/sh_chatbox.lua
Normal file
758
gamemodes/helix/gamemode/core/libs/sh_chatbox.lua
Normal file
@@ -0,0 +1,758 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Chat manipulation and helper functions.
|
||||
|
||||
Chat messages are a core part of the framework - it's takes up a good chunk of the gameplay, and is also used to interact with
|
||||
the framework. Chat messages can have types or "classes" that describe how the message should be interpreted. All chat messages
|
||||
will have some type of class: `ic` for regular in-character speech, `me` for actions, `ooc` for out-of-character, etc. These
|
||||
chat classes can affect how the message is displayed in each player's chatbox. See `ix.chat.Register` and `ChatClassStructure`
|
||||
to create your own chat classes.
|
||||
]]
|
||||
-- @module ix.chat
|
||||
|
||||
ix.chat = ix.chat or {}
|
||||
|
||||
--- List of all chat classes that have been registered by the framework, where each key is the name of the chat class, and value
|
||||
-- is the chat class data. Accessing a chat class's data is useful for when you want to copy some functionality or properties
|
||||
-- to use in your own. Note that if you're accessing this table, you should do so inside of the `InitializedChatClasses` hook.
|
||||
-- @realm shared
|
||||
-- @table ix.chat.classes
|
||||
-- @usage print(ix.chat.classes.ic.format)
|
||||
-- > "%s says \"%s\""
|
||||
ix.chat.classes = ix.chat.classes or {}
|
||||
|
||||
if (!ix.command) then
|
||||
include("sh_command.lua")
|
||||
end
|
||||
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = "Helix - Bypass OOC Timer",
|
||||
MinAccess = "admin"
|
||||
})
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = "Helix - OOC See IC Name",
|
||||
MinAccess = "admin"
|
||||
})
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = "Helix - Icon Incognito Mode",
|
||||
MinAccess = "admin"
|
||||
})
|
||||
|
||||
-- note we can't use commas in the "color" field's default value since the metadata is separated by commas which will break the
|
||||
-- formatting for that field
|
||||
|
||||
--- Chat messages can have different classes or "types" of messages that have different properties. This can include how the
|
||||
-- text is formatted, color, hearing distance, etc.
|
||||
-- @realm shared
|
||||
-- @table ChatClassStructure
|
||||
-- @see ix.chat.Register
|
||||
-- @field[type=string] prefix What the player must type before their message in order to use this chat class. For example,
|
||||
-- having a prefix of `/Y` will require to type `/Y I am yelling` in order to send a message with this chat class. This can also
|
||||
-- be a table of strings if you want to allow multiple prefixes, such as `{"//", "/OOC"}`.
|
||||
--
|
||||
-- **NOTE:** the prefix should usually start with a `/` to be consistent with the rest of the framework. However, you are able
|
||||
-- to use something different like the `LOOC` chat class where the prefixes are `.//`, `[[`, and `/LOOC`.
|
||||
-- @field[type=bool,opt=false] noSpaceAfter Whether or not the `prefix` can be used without a space after it. For example, the
|
||||
-- `OOC` chat class allows you to type `//my message` instead of `// my message`. **NOTE:** this only works if the last
|
||||
-- character in the prefix is non-alphanumeric (i.e `noSpaceAfter` with `/Y` will not work, but `/!` will).
|
||||
-- @field[type=string,opt] description Description to show to the user in the chatbox when they're using this chat class
|
||||
-- @field[type=string,opt="%s: \"%s\""] format How to format a message with this chat class. The first `%s` will be the speaking
|
||||
-- player's name, and the second one will be their message
|
||||
-- @field[type=color,opt=Color(242 230 160)] color Color to use when displaying a message with this chat class
|
||||
-- @field[type=string,opt="chatTyping"] indicator Language phrase to use when displaying the typing indicator above the
|
||||
-- speaking player's head
|
||||
-- @field[type=bool,opt=false] bNoIndicator Whether or not to avoid showing the typing indicator above the speaking player's
|
||||
-- head
|
||||
-- @field[type=string,opt=ixChatFont] font Font to use for displaying a message with this chat class
|
||||
-- @field[type=bool,opt=false] deadCanChat Whether or not a dead player can send a message with this chat class
|
||||
-- @field[type=number] CanHear This can be either a `number` representing how far away another player can hear this message.
|
||||
-- IC messages will use the `chatRange` config, for example. This can also be a function, which returns `true` if the given
|
||||
-- listener can hear the message emitted from a speaker.
|
||||
-- -- message can be heard by any player 1000 units away from the speaking player
|
||||
-- CanHear = 1000
|
||||
-- OR
|
||||
-- CanHear = function(self, speaker, listener)
|
||||
-- -- the speaking player will be heard by everyone
|
||||
-- return true
|
||||
-- end
|
||||
-- @field[type=function,opt] CanSay Function to run to check whether or not a player can send a message with this chat class.
|
||||
-- By default, it will return `false` if the player is dead and `deadCanChat` is `false`. Overriding this function will prevent
|
||||
-- `deadCanChat` from working, and you must implement this functionality manually.
|
||||
-- CanSay = function(self, speaker, text)
|
||||
-- -- the speaker will never be able to send a message with this chat class
|
||||
-- return false
|
||||
-- end
|
||||
-- @field[type=function,opt] GetColor Function to run to set the color of a message with this chat class. You should generally
|
||||
-- stick to using `color`, but this is useful for when you want the color of the message to change with some criteria.
|
||||
-- GetColor = function(self, speaker, text)
|
||||
-- -- each message with this chat class will be colored a random shade of red
|
||||
-- return Color(math.random(120, 200), 0, 0)
|
||||
-- end
|
||||
-- @field[type=function,opt] OnChatAdd Function to run when a message with this chat class should be added to the chatbox. If
|
||||
-- using this function, make sure you end the function by calling `chat.AddText` in order for the text to show up.
|
||||
--
|
||||
-- **NOTE:** using your own `OnChatAdd` function will prevent `color`, `GetColor`, or `format` from being used since you'll be
|
||||
-- overriding the base function that uses those properties. In such cases you'll need to add that functionality back in
|
||||
-- manually. In general, you should avoid overriding this function where possible. The `data` argument in the function is
|
||||
-- whatever is passed into the same `data` argument in `ix.chat.Send`.
|
||||
--
|
||||
-- OnChatAdd = function(self, speaker, text, bAnonymous, data)
|
||||
-- -- adds white text in the form of "Player Name: Message contents"
|
||||
-- chat.AddText(color_white, speaker:GetName(), ": ", text)
|
||||
-- end
|
||||
|
||||
--- Registers a new chat type with the information provided. Chat classes should usually be created inside of the
|
||||
-- `InitializedChatClasses` hook.
|
||||
-- @realm shared
|
||||
-- @string chatType Name of the chat type
|
||||
-- @tparam ChatClassStructure data Properties and functions to assign to this chat class
|
||||
-- @usage -- this is the "me" chat class taken straight from the framework as an example
|
||||
-- ix.chat.Register("me", {
|
||||
-- format = "** %s %s",
|
||||
-- color = Color(255, 50, 50),
|
||||
-- CanHear = ix.config.Get("chatRange", 280) * 2,
|
||||
-- prefix = {"/Me", "/Action"},
|
||||
-- description = "@cmdMe",
|
||||
-- indicator = "chatPerforming",
|
||||
-- deadCanChat = true
|
||||
-- })
|
||||
-- @see ChatClassStructure
|
||||
function ix.chat.Register(chatType, data)
|
||||
chatType = string.lower(chatType)
|
||||
|
||||
if (!data.CanHear) then
|
||||
-- Have a substitute if the canHear property is not found.
|
||||
function data:CanHear(speaker, listener)
|
||||
-- The speaker will be heard by everyone.
|
||||
return true
|
||||
end
|
||||
elseif (isnumber(data.CanHear)) then
|
||||
-- Use the value as a range and create a function to compare distances.
|
||||
local range = data.CanHear * data.CanHear
|
||||
data.range = range
|
||||
|
||||
function data:CanHear(speaker, listener)
|
||||
-- Length2DSqr is faster than Length2D, so just check the squares.
|
||||
return (speaker:GetPos() - listener:GetPos()):LengthSqr() <= self.range
|
||||
end
|
||||
end
|
||||
|
||||
-- Allow players to use this chat type by default.
|
||||
if (!data.CanSay) then
|
||||
function data:CanSay(speaker, text)
|
||||
if (!self.deadCanChat and !speaker:Alive()) then
|
||||
speaker:NotifyLocalized("noPerm")
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- Chat text color.
|
||||
data.color = data.color or Color(242, 230, 160)
|
||||
|
||||
if (!data.OnChatAdd) then
|
||||
data.format = data.format or "%s: \"%s\""
|
||||
data.icon = data.icon or nil
|
||||
|
||||
function data:OnChatAdd(speaker, text, anonymous, info)
|
||||
local color = self.color
|
||||
local name = anonymous and
|
||||
L"someone" or hook.Run("GetCharacterName", speaker, chatType) or
|
||||
(IsValid(speaker) and speaker:Name() or "Console")
|
||||
|
||||
if (self.GetColor) then
|
||||
color = self:GetColor(speaker, text, info)
|
||||
end
|
||||
|
||||
if self.bigfont then
|
||||
local oldFont = self.font
|
||||
local font = hook.Run("GetSpeakerYellFont", speaker)
|
||||
self.font = font
|
||||
elseif self.specialfont then
|
||||
local oldFont = self.font
|
||||
local font = hook.Run("GetSpeakerFont", speaker)
|
||||
self.font = font
|
||||
else
|
||||
local oldFont = self.font
|
||||
local font = "ixChatFont"
|
||||
self.font = font
|
||||
end
|
||||
|
||||
local translated = L2(chatType.."Format", name, text)
|
||||
|
||||
if self.icon and ix.option.Get("standardIconsEnabled") then
|
||||
chat.AddText(ix.util.GetMaterial(self.icon), color, translated or string.format(self.format, name, text))
|
||||
else
|
||||
chat.AddText(color, translated or string.format(self.format, name, text))
|
||||
end
|
||||
|
||||
if self.font then
|
||||
self.font = oldFont
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (CLIENT and data.prefix) then
|
||||
if (istable(data.prefix)) then
|
||||
for _, v in ipairs(data.prefix) do
|
||||
if (v:utf8sub(1, 1) == "/") then
|
||||
ix.command.Add(v:utf8sub(2), {
|
||||
description = data.description,
|
||||
arguments = ix.type.text,
|
||||
indicator = data.indicator,
|
||||
bNoIndicator = data.bNoIndicator,
|
||||
chatClass = data,
|
||||
OnCheckAccess = function() return true end,
|
||||
OnRun = function(self, client, message) end
|
||||
})
|
||||
end
|
||||
end
|
||||
else
|
||||
ix.command.Add(isstring(data.prefix) and data.prefix:utf8sub(2) or chatType, {
|
||||
description = data.description,
|
||||
arguments = ix.type.text,
|
||||
indicator = data.indicator,
|
||||
bNoIndicator = data.bNoIndicator,
|
||||
chatClass = data,
|
||||
OnCheckAccess = function() return true end,
|
||||
OnRun = function(self, client, message) end
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
data.uniqueID = chatType
|
||||
ix.chat.classes[chatType] = data
|
||||
end
|
||||
|
||||
--- Identifies which chat mode should be used.
|
||||
-- @realm shared
|
||||
-- @player client Player who is speaking
|
||||
-- @string message Message to parse
|
||||
-- @bool[opt=false] bNoSend Whether or not to send the chat message after parsing
|
||||
-- @treturn string Name of the chat type
|
||||
-- @treturn string Message that was parsed
|
||||
-- @treturn bool Whether or not the speaker should be anonymous
|
||||
function ix.chat.Parse(client, message, bNoSend)
|
||||
local anonymous = false
|
||||
local chatType = "ic"
|
||||
|
||||
-- Loop through all chat classes and see if the message contains their prefix.
|
||||
for k, v in pairs(ix.chat.classes) do
|
||||
local isChosen = false
|
||||
local chosenPrefix = ""
|
||||
local noSpaceAfter = v.noSpaceAfter
|
||||
|
||||
-- Check through all prefixes if the chat type has more than one.
|
||||
if (istable(v.prefix)) then
|
||||
for _, prefix in ipairs(v.prefix) do
|
||||
prefix = prefix:utf8lower()
|
||||
local fullPrefix = prefix .. (noSpaceAfter and "" or " ")
|
||||
|
||||
-- Checking if the start of the message has the prefix.
|
||||
if (message:utf8sub(1, prefix:utf8len() + (noSpaceAfter and 0 or 1)):utf8lower() == fullPrefix:utf8lower()) then
|
||||
isChosen = true
|
||||
chosenPrefix = fullPrefix
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
-- Otherwise the prefix itself is checked.
|
||||
elseif (isstring(v.prefix)) then
|
||||
local prefix = v.prefix:utf8lower()
|
||||
local fullPrefix = prefix .. (noSpaceAfter and "" or " ")
|
||||
|
||||
isChosen = message:utf8sub(1, prefix:utf8len() + (noSpaceAfter and 0 or 1)):utf8lower() == fullPrefix:utf8lower()
|
||||
chosenPrefix = fullPrefix
|
||||
end
|
||||
|
||||
-- If the checks say we have the proper chat type, then the chat type is the chosen one!
|
||||
-- If this is not chosen, the loop continues. If the loop doesn't find the correct chat
|
||||
-- type, then it falls back to IC chat as seen by the chatType variable above.
|
||||
if (isChosen) then
|
||||
-- Set the chat type to the chosen one.
|
||||
chatType = k
|
||||
-- Remove the prefix from the chat type so it does not show in the message.
|
||||
message = message:utf8sub(chosenPrefix:utf8len() + 1)
|
||||
|
||||
if (ix.chat.classes[k].noSpaceAfter and message:utf8sub(1, 1):match("%s")) then
|
||||
message = message:utf8sub(2)
|
||||
end
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if (!message:find("%S")) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Only send if needed.
|
||||
if (SERVER and !bNoSend) then
|
||||
-- Send the correct chat type out so other player see the message.
|
||||
ix.chat.Send(client, chatType, hook.Run("PlayerMessageSend", client, chatType, message, anonymous) or message, anonymous)
|
||||
end
|
||||
|
||||
-- Return the chosen chat type and the message that was sent if needed for some reason.
|
||||
-- This would be useful if you want to send the message on your own.
|
||||
return chatType, message, anonymous
|
||||
end
|
||||
|
||||
--- Formats a string to fix basic grammar - removing extra spacing at the beginning and end, capitalizing the first character,
|
||||
-- and making sure it ends in punctuation.
|
||||
-- @realm shared
|
||||
-- @string text String to format
|
||||
-- @treturn string Formatted string
|
||||
-- @usage print(ix.chat.Format("hello"))
|
||||
-- > Hello.
|
||||
-- @usage print(ix.chat.Format("wow!"))
|
||||
-- > Wow!
|
||||
function ix.chat.Format(text)
|
||||
text = string.Trim(text)
|
||||
local last = text:utf8sub(-1)
|
||||
|
||||
if (last != "." and last != "?" and last != "!" and last != "-" and last != "\"") then
|
||||
text = text .. "."
|
||||
end
|
||||
|
||||
return text:utf8sub(1, 1):utf8upper() .. text:utf8sub(2)
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixChatMessage")
|
||||
|
||||
--- Send a chat message using the specified chat type.
|
||||
-- @realm server
|
||||
-- @player speaker Player who is speaking
|
||||
-- @string chatType Name of the chat type
|
||||
-- @string text Message to send
|
||||
-- @bool[opt=false] bAnonymous Whether or not the speaker should be anonymous
|
||||
-- @tab[opt=nil] receivers The players to replicate send the message to
|
||||
-- @tab[opt=nil] data Additional data for this chat message
|
||||
function ix.chat.Send(speaker, chatType, text, bAnonymous, receivers, data)
|
||||
if (!chatType) then
|
||||
return
|
||||
end
|
||||
|
||||
data = data or {}
|
||||
chatType = string.lower(chatType)
|
||||
|
||||
if (IsValid(speaker) and hook.Run("PrePlayerMessageSend", speaker, chatType, text, bAnonymous, receivers, data) == false) then
|
||||
return
|
||||
end
|
||||
|
||||
local class = ix.chat.classes[chatType]
|
||||
|
||||
if (class and class:CanSay(speaker, text, data) != false) then
|
||||
if (class.CanHear and !receivers) then
|
||||
receivers = {}
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (v:GetCharacter() and class:CanHear(speaker, v, data) != false) then
|
||||
receivers[#receivers + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
if (#receivers == 0) then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Format the message if needed before we run the hook.
|
||||
local rawText = text
|
||||
local maxLength = ix.config.Get("chatMax")
|
||||
|
||||
if (text:utf8len() > maxLength) then
|
||||
text = text:utf8sub(0, maxLength)
|
||||
end
|
||||
|
||||
if (ix.config.Get("chatAutoFormat") and hook.Run("CanAutoFormatMessage", speaker, chatType, text)) then
|
||||
text = ix.chat.Format(text)
|
||||
end
|
||||
|
||||
local iconIncognitoMode = false
|
||||
if speaker and IsValid(speaker) and !speaker:IsBot() then
|
||||
iconIncognitoMode = ix.option.Get(speaker, "iconIncognitoMode", false)
|
||||
end
|
||||
|
||||
text = hook.Run("PlayerMessageSend", speaker, chatType, text, bAnonymous, receivers, rawText, data) or text
|
||||
|
||||
net.Start("ixChatMessage")
|
||||
net.WriteEntity(speaker)
|
||||
net.WriteString(chatType)
|
||||
net.WriteString(text)
|
||||
net.WriteBool(bAnonymous or false)
|
||||
net.WriteBool(iconIncognitoMode)
|
||||
net.WriteTable(data or {})
|
||||
net.Send(receivers)
|
||||
|
||||
return text
|
||||
end
|
||||
end
|
||||
else
|
||||
function ix.chat.Send(speaker, chatType, text, anonymous, data, iconIncognitoMode)
|
||||
local class = ix.chat.classes[chatType]
|
||||
|
||||
if (class) then
|
||||
-- luacheck: globals CHAT_CLASS
|
||||
CHAT_CLASS = class
|
||||
class:OnChatAdd(speaker, text, anonymous, data, iconIncognitoMode)
|
||||
CHAT_CLASS = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Call OnChatAdd for the appropriate chatType.
|
||||
net.Receive("ixChatMessage", function()
|
||||
local client = net.ReadEntity()
|
||||
local chatType = net.ReadString()
|
||||
local text = net.ReadString()
|
||||
local anonymous = net.ReadBool()
|
||||
local iconIncognitoMode = net.ReadBool()
|
||||
local data = net.ReadTable()
|
||||
local info = {
|
||||
chatType = chatType,
|
||||
text = text,
|
||||
anonymous = anonymous,
|
||||
iconIncognitoMode = iconIncognitoMode,
|
||||
data = data
|
||||
}
|
||||
|
||||
if (IsValid(client)) then
|
||||
hook.Run("MessageReceived", client, info)
|
||||
ix.chat.Send(client, info.chatType or chatType, info.text or text, info.anonymous or anonymous, info.data,
|
||||
info.iconIncognitoMode)
|
||||
else
|
||||
if client and client.IsWorld and client:IsWorld() then
|
||||
hook.Run("MessageReceived", nil, info)
|
||||
end
|
||||
|
||||
ix.chat.Send(nil, chatType, text, anonymous, data)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Add the default chat types here.
|
||||
do
|
||||
-- Load the chat types after the configs so we can access changed configs.
|
||||
hook.Add("InitializedConfig", "ixChatTypes", function()
|
||||
-- The default in-character chat.
|
||||
ix.chat.Register("ic", {
|
||||
format = "%s says \"%s\"",
|
||||
icon = "willardnetworks/chat/message_icon.png",
|
||||
indicator = "chatTalking",
|
||||
color = Color(255, 254, 153, 255),
|
||||
CanHear = ix.config.Get("chatRange", 280)
|
||||
})
|
||||
|
||||
ix.option.Add("iconIncognitoMode", ix.type.bool, false, {
|
||||
bNetworked = true,
|
||||
category = "chat",
|
||||
hidden = function()
|
||||
return !CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Icon Incognito Mode")
|
||||
end
|
||||
})
|
||||
|
||||
if (CLIENT) then
|
||||
ix.option.Add("standardIconsEnabled", ix.type.bool, true, {
|
||||
category = "chat"
|
||||
})
|
||||
|
||||
ix.option.Add("seeGlobalOOC", ix.type.bool, true, {
|
||||
category = "chat"
|
||||
})
|
||||
|
||||
ix.option.Add("enablePrivateMessageSound", ix.type.bool, true, {
|
||||
category = "chat"
|
||||
})
|
||||
end
|
||||
|
||||
-- Actions and such.
|
||||
ix.chat.Register("me", {
|
||||
format = "*** %s %s",
|
||||
color = Color(214, 254, 137, 255),
|
||||
CanHear = ix.config.Get("chatRange", 280) * 2,
|
||||
prefix = {"/Me", "/Action"},
|
||||
description = "@cmdMe",
|
||||
indicator = "chatPerforming",
|
||||
CanSay = function(self, speaker, text)
|
||||
if (!speaker:Alive() and speaker.lastMeExpended) then
|
||||
speaker:NotifyLocalized("noPerm")
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
-- Actions and such.
|
||||
ix.chat.Register("it", {
|
||||
OnChatAdd = function(self, speaker, text)
|
||||
chat.AddText(ix.config.Get("chatColor"), "***' "..text)
|
||||
end,
|
||||
CanHear = ix.config.Get("chatRange", 280) * 2,
|
||||
prefix = {"/It"},
|
||||
description = "@cmdIt",
|
||||
indicator = "chatPerforming",
|
||||
deadCanChat = true
|
||||
})
|
||||
|
||||
-- Whisper chat.
|
||||
ix.chat.Register("w", {
|
||||
format = "%s whispers \"%s\"",
|
||||
icon = "willardnetworks/chat/whisper_icon.png",
|
||||
color = Color(158, 162, 191, 255),
|
||||
CanHear = ix.config.Get("chatRange", 280) * 0.25,
|
||||
prefix = {"/W", "/Whisper"},
|
||||
description = "@cmdW",
|
||||
indicator = "chatWhispering",
|
||||
specialfont = true
|
||||
})
|
||||
|
||||
-- Yelling out loud.
|
||||
ix.chat.Register("y", {
|
||||
format = "%s yells \"%s\"",
|
||||
color = Color(254, 171, 103, 255),
|
||||
icon = "willardnetworks/chat/yell_icon.png",
|
||||
CanHear = ix.config.Get("chatRange", 280) * 2,
|
||||
prefix = {"/Y", "/Yell"},
|
||||
description = "@cmdY",
|
||||
indicator = "chatYelling",
|
||||
bigfont = true
|
||||
})
|
||||
|
||||
-- REMEMBER TO UPDATE THESE WHEN WE CHANGE RANKS IN SAM
|
||||
local chatIconMap = {
|
||||
["willardnetworks/chat/star.png"] = {
|
||||
["community_manager"] = true,
|
||||
["head_of_staff"] = true,
|
||||
["server_council"] = true,
|
||||
["willard_management"] = true,
|
||||
["superadmin"] = true
|
||||
},
|
||||
["willardnetworks/chat/hammer.png"] = {
|
||||
["senior_admin"] = true,
|
||||
["server_admin"] = true,
|
||||
["trial_admin"] = true,
|
||||
["admin"] = true
|
||||
},
|
||||
["willardnetworks/chat/paintbrush.png"] = {
|
||||
["gamemaster"] = true
|
||||
},
|
||||
["willardnetworks/chat/wrench.png"] = {
|
||||
["developer"] = true
|
||||
},
|
||||
["willardnetworks/chat/leaf.png"] = {
|
||||
["mentor"] = true
|
||||
},
|
||||
["willardnetworks/chat/heart.png"] = {
|
||||
["premium1"] = true,
|
||||
["premium2"] = true,
|
||||
["premium3"] = true
|
||||
}
|
||||
}
|
||||
local function GetIcon(speaker, iconIncognitoMode)
|
||||
local icon = "willardnetworks/chat/ooc_icon.png"
|
||||
if !speaker or speaker and !IsValid(speaker) then
|
||||
return ix.util.GetMaterial(icon)
|
||||
end
|
||||
|
||||
if iconIncognitoMode then return ix.util.GetMaterial(icon) end
|
||||
|
||||
for mat, rankGroup in pairs(chatIconMap) do
|
||||
if (rankGroup[speaker:GetUserGroup()]) then
|
||||
icon = mat
|
||||
end
|
||||
end
|
||||
|
||||
return ix.util.GetMaterial(hook.Run("GetPlayerIcon", speaker) or icon)
|
||||
end
|
||||
-- Out of character.
|
||||
ix.chat.Register("ooc", {
|
||||
CanSay = function(self, speaker, text)
|
||||
if (!ix.config.Get("allowGlobalOOC")) then
|
||||
speaker:NotifyLocalized("GOOCIsDisabled")
|
||||
return false
|
||||
else
|
||||
local delay = ix.config.Get("oocDelay", 10)
|
||||
|
||||
-- Only need to check the time if they have spoken in OOC chat before.
|
||||
if (delay > 0 and speaker.ixLastOOC) then
|
||||
local lastOOC = CurTime() - speaker.ixLastOOC
|
||||
|
||||
-- Use this method of checking time in case the oocDelay config changes.
|
||||
if (lastOOC <= delay and !CAMI.PlayerHasAccess(speaker, "Helix - Bypass OOC Timer", nil)) then
|
||||
speaker:NotifyLocalized("oocDelay", delay - math.ceil(lastOOC))
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- Save the last time they spoke in OOC.
|
||||
speaker.ixLastOOC = CurTime()
|
||||
end
|
||||
end,
|
||||
OnChatAdd = function(self, speaker, text, _, _, iconIncognitoMode)
|
||||
-- @todo remove and fix actual cause of speaker being nil
|
||||
if (!IsValid(speaker) or !ix.option.Get("seeGlobalOOC", true)) then
|
||||
return
|
||||
end
|
||||
|
||||
local icon = GetIcon(speaker, iconIncognitoMode)
|
||||
if (CAMI.PlayerHasAccess(LocalPlayer(), "Helix - OOC See IC Name")) then
|
||||
chat.AddText(icon, Color(255, 66, 66), "[OOC] ", Color(192, 192, 196), speaker:SteamName()
|
||||
, " (", speaker:Name(), ")", color_white, ": ", text)
|
||||
else
|
||||
chat.AddText(icon, Color(255, 66, 66), "[OOC] ", Color(192, 192, 196), speaker:SteamName(), color_white, ": ", text)
|
||||
end
|
||||
end,
|
||||
prefix = {"//", "/OOC"},
|
||||
description = "@cmdOOC",
|
||||
noSpaceAfter = true
|
||||
})
|
||||
|
||||
-- Local out of character.
|
||||
ix.chat.Register("looc", {
|
||||
CanSay = function(self, speaker, text)
|
||||
local delay = ix.config.Get("loocDelay", 0)
|
||||
|
||||
-- Only need to check the time if they have spoken in OOC chat before.
|
||||
if (delay > 0 and speaker.ixLastLOOC) then
|
||||
local lastLOOC = CurTime() - speaker.ixLastLOOC
|
||||
|
||||
-- Use this method of checking time in case the oocDelay config changes.
|
||||
if (lastLOOC <= delay and !CAMI.PlayerHasAccess(speaker, "Helix - Bypass OOC Timer", nil)) then
|
||||
speaker:NotifyLocalized("loocDelay", delay - math.ceil(lastLOOC))
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- Save the last time they spoke in OOC.
|
||||
speaker.ixLastLOOC = CurTime()
|
||||
end,
|
||||
OnChatAdd = function(self, speaker, text, _, _, iconIncognitoMode)
|
||||
local icon = GetIcon(speaker, iconIncognitoMode)
|
||||
local name = hook.Run("GetCharacterName", speaker, "ic") or
|
||||
(IsValid(speaker) and speaker:Name() or "Console")
|
||||
|
||||
chat.AddText(icon, Color(255, 66, 66), "[LOOC] ", Color(255, 254, 153, 255), name..": "..text)
|
||||
end,
|
||||
CanHear = ix.config.Get("chatRange", 280),
|
||||
prefix = {".//", "[[", "/LOOC"},
|
||||
description = "@cmdLOOC",
|
||||
noSpaceAfter = true
|
||||
})
|
||||
|
||||
-- Roll information in chat.
|
||||
ix.chat.Register("roll", {
|
||||
format = "*** %s has rolled %s out of %s.",
|
||||
color = Color(155, 111, 176),
|
||||
CanHear = ix.config.Get("chatRange", 280),
|
||||
deadCanChat = true,
|
||||
OnChatAdd = function(self, speaker, text, bAnonymous, data)
|
||||
local max = data.max or 100
|
||||
local translated = L2(self.uniqueID.."Format", speaker:Name(), text, max)
|
||||
|
||||
chat.AddText(self.color, translated and "*** "..translated or string.format(self.format,
|
||||
speaker:Name(), text, max
|
||||
))
|
||||
end
|
||||
})
|
||||
|
||||
-- run a hook after we add the basic chat classes so schemas/plugins can access their info as soon as possible if needed
|
||||
hook.Run("InitializedChatClasses")
|
||||
end)
|
||||
end
|
||||
|
||||
-- Private messages between players.
|
||||
ix.chat.Register("pm", {
|
||||
format = "%s (%s): %s",
|
||||
color = Color(255, 255, 239, 61),
|
||||
deadCanChat = true,
|
||||
|
||||
OnChatAdd = function(self, speaker, text, bAnonymous, data)
|
||||
local client = LocalPlayer()
|
||||
|
||||
if (ix.option.Get("standardIconsEnabled")) then
|
||||
if (speaker and client == speaker) then
|
||||
if !data.target or data.target and !IsValid(data.target) then return end
|
||||
|
||||
chat.AddText(ix.util.GetMaterial("willardnetworks/chat/pm_icon.png"), Color(254, 238, 60), "[PM] »"
|
||||
, self.color, string.format(self.format, data.target:GetName(), data.target:SteamName(), text))
|
||||
else
|
||||
chat.AddText(ix.util.GetMaterial("willardnetworks/chat/pm_icon.png"), Color(254, 238, 60), "[PM] "
|
||||
, self.color, string.format(self.format, speaker:GetName(), speaker:SteamName(), text))
|
||||
end
|
||||
else
|
||||
if (client == speaker) then
|
||||
chat.AddText(Color(254, 238, 60), "[PM] »"
|
||||
, self.color, string.format(self.format, data.target:GetName(), data.target:SteamName(), text))
|
||||
else
|
||||
chat.AddText(Color(254, 238, 60), "[PM] "
|
||||
, self.color, string.format(self.format, speaker:GetName(), speaker:SteamName(), text))
|
||||
end
|
||||
end
|
||||
|
||||
if (client != speaker and ix.option.Get("enablePrivateMessageSound", true)) then
|
||||
surface.PlaySound("hl1/fvox/bell.wav")
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
-- Global events.
|
||||
ix.chat.Register("event", {
|
||||
CanHear = 1000000,
|
||||
OnChatAdd = function(self, speaker, text)
|
||||
chat.AddText(Color(254, 138, 0), text)
|
||||
end,
|
||||
indicator = "chatPerforming"
|
||||
})
|
||||
|
||||
ix.chat.Register("connect", {
|
||||
CanSay = function(self, speaker, text)
|
||||
return !IsValid(speaker)
|
||||
end,
|
||||
OnChatAdd = function(self, speaker, text)
|
||||
local icon = ix.util.GetMaterial("willardnetworks/chat/connected_icon.png")
|
||||
|
||||
chat.AddText(icon, Color(151, 153, 152), L("playerConnected", text))
|
||||
end,
|
||||
noSpaceAfter = true
|
||||
})
|
||||
|
||||
ix.chat.Register("disconnect", {
|
||||
CanSay = function(self, speaker, text)
|
||||
return !IsValid(speaker)
|
||||
end,
|
||||
OnChatAdd = function(self, speaker, text)
|
||||
local icon = ix.util.GetMaterial("willardnetworks/chat/disconnected_icon.png")
|
||||
|
||||
chat.AddText(icon, Color(151, 153, 152), L("playerDisconnected", text))
|
||||
end,
|
||||
noSpaceAfter = true
|
||||
})
|
||||
|
||||
ix.chat.Register("notice", {
|
||||
CanSay = function(self, speaker, text)
|
||||
return !IsValid(speaker)
|
||||
end,
|
||||
OnChatAdd = function(self, speaker, text, bAnonymous, data)
|
||||
local icon = ix.util.GetMaterial(data.bError and "icon16/comment_delete.png" or "icon16/comment.png")
|
||||
chat.AddText(icon, data.bError and Color(200, 175, 200, 255) or Color(175, 200, 255), text)
|
||||
end,
|
||||
noSpaceAfter = true
|
||||
})
|
||||
|
||||
-- Why does ULX even have a /me command?
|
||||
hook.Remove("PlayerSay", "ULXMeCheck")
|
||||
212
gamemodes/helix/gamemode/core/libs/sh_class.lua
Normal file
212
gamemodes/helix/gamemode/core/libs/sh_class.lua
Normal file
@@ -0,0 +1,212 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Helper library for loading/getting class information.
|
||||
|
||||
Classes are temporary assignments for characters - analogous to a "job" in a faction. For example, you may have a police faction
|
||||
in your schema, and have "police recruit" and "police chief" as different classes in your faction. Anyone can join a class in
|
||||
their faction by default, but you can restrict this as you need with `CLASS.CanSwitchTo`.
|
||||
]]
|
||||
-- @module ix.class
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixClassUpdate")
|
||||
end
|
||||
|
||||
ix.class = ix.class or {}
|
||||
ix.class.list = {}
|
||||
|
||||
local charMeta = ix.meta.character
|
||||
|
||||
--- Loads classes from a directory.
|
||||
-- @realm shared
|
||||
-- @internal
|
||||
-- @string directory The path to the class files.
|
||||
function ix.class.LoadFromDir(directory)
|
||||
for _, v in ipairs(file.Find(directory.."/*.lua", "LUA")) do
|
||||
-- Get the name without the "sh_" prefix and ".lua" suffix.
|
||||
local niceName = v:sub(4, -5)
|
||||
-- Determine a numeric identifier for this class.
|
||||
local index = #ix.class.list + 1
|
||||
local halt
|
||||
|
||||
for _, v2 in ipairs(ix.class.list) do
|
||||
if (v2.uniqueID == niceName) then
|
||||
halt = true
|
||||
end
|
||||
end
|
||||
|
||||
if (halt == true) then
|
||||
continue
|
||||
end
|
||||
|
||||
-- Set up a global table so the file has access to the class table.
|
||||
CLASS = {index = index, uniqueID = niceName}
|
||||
CLASS.name = "Unknown"
|
||||
CLASS.description = "No description available."
|
||||
CLASS.limit = 0
|
||||
|
||||
-- For future use with plugins.
|
||||
if (PLUGIN) then
|
||||
CLASS.plugin = PLUGIN.uniqueID
|
||||
end
|
||||
|
||||
ix.util.Include(directory.."/"..v, "shared")
|
||||
|
||||
-- Why have a class without a faction?
|
||||
if (!CLASS.faction or !team.Valid(CLASS.faction)) then
|
||||
ErrorNoHalt("Class '"..niceName.."' does not have a valid faction!\n")
|
||||
CLASS = nil
|
||||
|
||||
continue
|
||||
end
|
||||
|
||||
-- Allow classes to be joinable by default.
|
||||
if (!CLASS.CanSwitchTo) then
|
||||
CLASS.CanSwitchTo = function(client)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
ix.class.list[index] = CLASS
|
||||
CLASS = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Determines if a player is allowed to join a specific class.
|
||||
-- @realm shared
|
||||
-- @player client Player to check
|
||||
-- @number class Index of the class
|
||||
-- @treturn bool Whether or not the player can switch to the class
|
||||
function ix.class.CanSwitchTo(client, class)
|
||||
-- Get the class table by its numeric identifier.
|
||||
local info = ix.class.list[class]
|
||||
|
||||
-- See if the class exists.
|
||||
if (!info) then
|
||||
return false, "no info"
|
||||
end
|
||||
|
||||
-- If the player's faction matches the class's faction.
|
||||
if (client:Team() != info.faction) then
|
||||
return false, "not correct team"
|
||||
end
|
||||
|
||||
if (client:GetCharacter():GetClass() == class) then
|
||||
return false, "same class request"
|
||||
end
|
||||
|
||||
if (info.limit > 0) then
|
||||
if (#ix.class.GetPlayers(info.index) >= info.limit) then
|
||||
return false, "class is full"
|
||||
end
|
||||
end
|
||||
|
||||
if (hook.Run("CanPlayerJoinClass", client, class, info) == false) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- See if the class allows the player to join it.
|
||||
return info:CanSwitchTo(client)
|
||||
end
|
||||
|
||||
--- Retrieves a class table.
|
||||
-- @realm shared
|
||||
-- @number identifier Index of the class
|
||||
-- @treturn table Class table
|
||||
function ix.class.Get(identifier)
|
||||
return ix.class.list[identifier]
|
||||
end
|
||||
|
||||
--- Retrieves the players in a class
|
||||
-- @realm shared
|
||||
-- @number class Index of the class
|
||||
-- @treturn table Table of players in the class
|
||||
function ix.class.GetPlayers(class)
|
||||
local players = {}
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
local char = v:GetCharacter()
|
||||
|
||||
if (char and char:GetClass() == class) then
|
||||
table.insert(players, v)
|
||||
end
|
||||
end
|
||||
|
||||
return players
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
--- Character class methods
|
||||
-- @classmod Character
|
||||
|
||||
--- Makes this character join a class. This automatically calls `KickClass` for you.
|
||||
-- @realm server
|
||||
-- @number class Index of the class to join
|
||||
-- @treturn bool Whether or not the character has successfully joined the class
|
||||
function charMeta:JoinClass(class)
|
||||
if (!class) then
|
||||
self:KickClass()
|
||||
return false
|
||||
end
|
||||
|
||||
local oldClass = self:GetClass()
|
||||
local client = self:GetPlayer()
|
||||
|
||||
if (ix.class.CanSwitchTo(client, class)) then
|
||||
self:SetClass(class)
|
||||
hook.Run("PlayerJoinedClass", client, class, oldClass)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Kicks this character out of the class they are currently in.
|
||||
-- @realm server
|
||||
function charMeta:KickClass()
|
||||
local client = self:GetPlayer()
|
||||
if (!client) then return end
|
||||
|
||||
local goClass
|
||||
|
||||
for k, v in pairs(ix.class.list) do
|
||||
if (v.faction == client:Team() and v.isDefault) then
|
||||
goClass = k
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
self:JoinClass(goClass)
|
||||
|
||||
hook.Run("PlayerJoinedClass", client, goClass)
|
||||
end
|
||||
|
||||
function GM:PlayerJoinedClass(client, class, oldClass)
|
||||
local info = ix.class.list[class]
|
||||
local info2 = ix.class.list[oldClass]
|
||||
|
||||
if (info.OnSet) then
|
||||
info:OnSet(client)
|
||||
end
|
||||
|
||||
if (info2 and info2.OnLeave) then
|
||||
info2:OnLeave(client)
|
||||
end
|
||||
|
||||
net.Start("ixClassUpdate")
|
||||
net.WriteEntity(client)
|
||||
net.Broadcast()
|
||||
end
|
||||
end
|
||||
636
gamemodes/helix/gamemode/core/libs/sh_command.lua
Normal file
636
gamemodes/helix/gamemode/core/libs/sh_command.lua
Normal file
@@ -0,0 +1,636 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Registration, parsing, and handling of commands.
|
||||
|
||||
Commands can be ran through the chat with slash commands or they can be executed through the console. Commands can be manually
|
||||
restricted to certain usergroups using a [CAMI](https://github.com/glua/CAMI)-compliant admin mod.
|
||||
]]
|
||||
-- @module ix.command
|
||||
|
||||
--- When registering commands with `ix.command.Add`, you'll need to pass in a valid command structure. This is simply a table
|
||||
-- with various fields defined to describe the functionality of the command.
|
||||
-- @realm shared
|
||||
-- @table CommandStructure
|
||||
-- @field[type=function] OnRun This function is called when the command has passed all the checks and can execute. The first two
|
||||
-- arguments will be the running command table and the calling player. If the arguments field has been specified, the arguments
|
||||
-- will be passed as regular function parameters rather than in a table.
|
||||
--
|
||||
-- When the arguments field is defined: `OnRun(self, client, target, length, message)`
|
||||
--
|
||||
-- When the arguments field is NOT defined: `OnRun(self, client, arguments)`
|
||||
-- @field[type=string,opt="@noDesc"] description The help text that appears when the user types in the command. If the string is
|
||||
-- prefixed with `"@"`, it will use a language phrase.
|
||||
-- @field[type=table,opt=nil] argumentNames An array of strings corresponding to each argument of the command. This ignores the
|
||||
-- name that's specified in the `OnRun` function arguments and allows you to use any string to change the text that displays
|
||||
-- in the command's syntax help. When using this field, make sure that the amount is equal to the amount of arguments, as such:
|
||||
-- COMMAND.arguments = {ix.type.character, ix.type.number}
|
||||
-- COMMAND.argumentNames = {"target char", "cash (1-1000)"}
|
||||
-- @field[type=table,opt] arguments If this field is defined, then additional checks will be performed to ensure that the
|
||||
-- arguments given to the command are valid. This removes extra boilerplate code since all the passed arguments are guaranteed
|
||||
-- to be valid. See `CommandArgumentsStructure` for more information.
|
||||
-- @field[type=boolean,opt=false] adminOnly Provides an additional check to see if the user is an admin before running.
|
||||
-- @field[type=boolean,opt=false] superAdminOnly Provides an additional check to see if the user is a superadmin before running.
|
||||
-- @field[type=string,opt=nil] privilege Manually specify a privilege name for this command. It will always be prefixed with
|
||||
-- `"Helix - "`. This is used in the case that you want to group commands under the same privilege, or use a privilege that
|
||||
-- you've already defined (i.e grouping `/CharBan` and `/CharUnban` into the `Helix - Ban Character` privilege).
|
||||
-- @field[type=function,opt=nil] OnCheckAccess This callback checks whether or not the player is allowed to run the command.
|
||||
-- This callback should **NOT** be used in conjunction with `adminOnly` or `superAdminOnly`, as populating those
|
||||
-- fields create a custom a `OnCheckAccess` callback for you internally. This is used in cases where you want more fine-grained
|
||||
-- access control for your command.
|
||||
--
|
||||
-- Keep in mind that this is a **SHARED** callback; the command will not show up the client if the callback returns `false`.
|
||||
|
||||
--- Rather than checking the validity for arguments in your command's `OnRun` function, you can have Helix do it for you to
|
||||
-- reduce the amount of boilerplate code that needs to be written. This can be done by populating the `arguments` field.
|
||||
--
|
||||
-- When using the `arguments` field in your command, you are specifying specific types that you expect to receive when the
|
||||
-- command is ran successfully. This means that before `OnRun` is called, the arguments passed to the command from a user will
|
||||
-- be verified to be valid. Each argument is an `ix.type` entry that specifies the expected type for that argument. Optional
|
||||
-- arguments can be specified by using a bitwise OR with the special `ix.type.optional` type. When specified as optional, the
|
||||
-- argument can be `nil` if the user has not entered anything for that argument - otherwise it will be valid.
|
||||
--
|
||||
-- Note that optional arguments must always be at the end of a list of arguments - or rather, they must not follow a required
|
||||
-- argument. The `syntax` field will be automatically populated when using strict arguments, which means you shouldn't fill out
|
||||
-- the `syntax` field yourself. The arguments you specify will have the same names as the arguments in your OnRun function.
|
||||
--
|
||||
-- Consider this example command:
|
||||
-- ix.command.Add("CharSlap", {
|
||||
-- description = "Slaps a character with a large trout.",
|
||||
-- adminOnly = true,
|
||||
-- arguments = {
|
||||
-- ix.type.character,
|
||||
-- bit.bor(ix.type.number, ix.type.optional)
|
||||
-- },
|
||||
-- OnRun = function(self, client, target, damage)
|
||||
-- -- WHAM!
|
||||
-- end
|
||||
-- })
|
||||
-- Here, we've specified the first argument called `target` to be of type `character`, and the second argument called `damage`
|
||||
-- to be of type `number`. The `damage` argument is optional, meaning that the command will still run if the user has not
|
||||
-- specified any value for the damage. In this case, we'll need to check if it was specified by doing a simple
|
||||
-- `if (damage) then`. The syntax field will be automatically populated with the value `"<target: character> [damage: number]"`.
|
||||
-- @realm shared
|
||||
-- @table CommandArgumentsStructure
|
||||
|
||||
ix.command = ix.command or {}
|
||||
ix.command.list = ix.command.list or {}
|
||||
|
||||
local COMMAND_PREFIX = "/"
|
||||
|
||||
local function ArgumentCheckStub(command, client, given)
|
||||
local arguments = command.arguments
|
||||
local result = {}
|
||||
|
||||
for i = 1, #arguments do
|
||||
local bOptional = bit.band(arguments[i], ix.type.optional) == ix.type.optional
|
||||
local argType = bOptional and bit.bxor(arguments[i], ix.type.optional) or arguments[i]
|
||||
local argument = given[i]
|
||||
|
||||
if (!argument and !bOptional) then
|
||||
return L("invalidArg", client, i)
|
||||
end
|
||||
|
||||
if (argType == ix.type.string) then
|
||||
if (!argument and bOptional) then
|
||||
result[#result + 1] = nil
|
||||
else
|
||||
result[#result + 1] = tostring(argument)
|
||||
end
|
||||
elseif (argType == ix.type.text) then
|
||||
result[#result + 1] = table.concat(given, " ", i) or ""
|
||||
break
|
||||
elseif (argType == ix.type.number) then
|
||||
local value = tonumber(argument)
|
||||
|
||||
if (!bOptional and !value) then
|
||||
return L("invalidArg", client, i)
|
||||
end
|
||||
|
||||
result[#result + 1] = value
|
||||
elseif (argType == ix.type.player or argType == ix.type.character) then
|
||||
local bPlayer = argType == ix.type.player
|
||||
local value
|
||||
|
||||
if argument == "^" then
|
||||
value = client
|
||||
else
|
||||
value = ix.util.FindPlayer(argument)
|
||||
end
|
||||
|
||||
-- FindPlayer emits feedback for us
|
||||
if (!value and !bOptional) then
|
||||
return L(bPlayer and "plyNoExist" or "charNoExist", client)
|
||||
end
|
||||
|
||||
-- check for the character if we're using the character type
|
||||
if (!bPlayer) then
|
||||
local character = value:GetCharacter()
|
||||
|
||||
if (!character) then
|
||||
return L("charNoExist", client)
|
||||
end
|
||||
|
||||
value = character
|
||||
end
|
||||
|
||||
result[#result + 1] = value
|
||||
elseif (argType == ix.type.steamid) then
|
||||
local value = argument:match("STEAM_(%d+):(%d+):(%d+)")
|
||||
|
||||
if (!value and bOptional) then
|
||||
return L("invalidArg", client, i)
|
||||
end
|
||||
|
||||
result[#result + 1] = value
|
||||
elseif (argType == ix.type.bool) then
|
||||
if (argument == nil and bOptional) then
|
||||
result[#result + 1] = nil
|
||||
else
|
||||
result[#result + 1] = tobool(argument)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
--- Creates a new command.
|
||||
-- @realm shared
|
||||
-- @string command Name of the command (recommended in UpperCamelCase)
|
||||
-- @tparam CommandStructure data Data describing the command
|
||||
-- @see CommandStructure
|
||||
-- @see CommandArgumentsStructure
|
||||
function ix.command.Add(command, data)
|
||||
data.name = string.gsub(command, "%s", "")
|
||||
data.description = data.description or ""
|
||||
|
||||
command = command:lower()
|
||||
data.uniqueID = command
|
||||
|
||||
-- Why bother adding a command if it doesn't do anything.
|
||||
if (!data.OnRun) then
|
||||
return ErrorNoHalt("Command '"..command.."' does not have a callback, not adding!\n")
|
||||
end
|
||||
|
||||
-- Add a function to get the description that can be overridden.
|
||||
if (!data.GetDescription) then
|
||||
-- Check if the description is using a language string.
|
||||
if (data.description:sub(1, 1) == "@") then
|
||||
function data:GetDescription()
|
||||
return L(self.description:sub(2))
|
||||
end
|
||||
else
|
||||
-- Otherwise just return the raw description.
|
||||
function data:GetDescription()
|
||||
return self.description
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- OnCheckAccess by default will rely on CAMI for access information with adminOnly/superAdminOnly being fallbacks
|
||||
if (!data.OnCheckAccess) then
|
||||
if (data.group) then
|
||||
ErrorNoHalt("Command '" .. data.name .. "' tried to use the deprecated field 'group'!\n")
|
||||
return
|
||||
end
|
||||
|
||||
local privilege = "Helix - " .. (isstring(data.privilege) and data.privilege or data.name)
|
||||
|
||||
-- we could be using a previously-defined privilege
|
||||
if (!CAMI.GetPrivilege(privilege)) then
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = privilege,
|
||||
MinAccess = data.superAdminOnly and "superadmin" or (data.adminOnly and "admin" or "user"),
|
||||
Description = data.description
|
||||
})
|
||||
end
|
||||
|
||||
function data:OnCheckAccess(client)
|
||||
local bHasAccess, _ = CAMI.PlayerHasAccess(client, privilege, nil)
|
||||
return bHasAccess
|
||||
end
|
||||
end
|
||||
|
||||
-- if we have an arguments table, then we're using the new command format
|
||||
if (data.arguments) then
|
||||
local bFirst = true
|
||||
local bLastOptional = false
|
||||
local bHasArgumentNames = istable(data.argumentNames)
|
||||
|
||||
data.syntax = "" -- @todo deprecate this in favour of argumentNames
|
||||
data.argumentNames = bHasArgumentNames and data.argumentNames or {}
|
||||
|
||||
-- if one argument is supplied by itself, put it into a table
|
||||
if (!istable(data.arguments)) then
|
||||
data.arguments = {data.arguments}
|
||||
end
|
||||
|
||||
if (bHasArgumentNames and #data.argumentNames != #data.arguments) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' doesn't have argument names that correspond to each argument\n", command
|
||||
))
|
||||
end
|
||||
|
||||
-- check the arguments table to see if its entries are valid
|
||||
for i = 1, #data.arguments do
|
||||
local argument = data.arguments[i]
|
||||
local argumentName = debug.getlocal(data.OnRun, 2 + i)
|
||||
|
||||
if (argument == ix.type.optional) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' tried to use an optional argument for #%d without specifying type\n", command, i
|
||||
))
|
||||
elseif (!isnumber(argument)) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' tried to use an invalid type for argument #%d\n", command, i
|
||||
))
|
||||
elseif (argument == ix.type.array or bit.band(argument, ix.type.array) > 0) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' tried to use an unsupported type 'array' for argument #%d\n", command, i
|
||||
))
|
||||
end
|
||||
|
||||
local bOptional = bit.band(argument, ix.type.optional) > 0
|
||||
argument = bOptional and bit.bxor(argument, ix.type.optional) or argument
|
||||
|
||||
if (!ix.type[argument]) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' tried to use an invalid type for argument #%d\n", command, i
|
||||
))
|
||||
elseif (!isstring(argumentName)) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' is missing function argument for command argument #%d\n", command, i
|
||||
))
|
||||
elseif (argument == ix.type.text and i != #data.arguments) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' tried to use a text argument outside of the last argument\n", command
|
||||
))
|
||||
elseif (!bOptional and bLastOptional) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' tried to use an required argument after an optional one\n", command
|
||||
))
|
||||
end
|
||||
|
||||
-- text is always optional and will return an empty string if nothing is specified, rather than nil
|
||||
if (argument == ix.type.text) then
|
||||
data.arguments[i] = bit.bor(ix.type.text, ix.type.optional)
|
||||
bOptional = true
|
||||
end
|
||||
|
||||
if (!bHasArgumentNames) then
|
||||
data.argumentNames[i] = argumentName
|
||||
end
|
||||
|
||||
data.syntax = data.syntax .. (bFirst and "" or " ") ..
|
||||
string.format((bOptional and "[%s: %s]" or "<%s: %s>"), argumentName, ix.type[argument])
|
||||
|
||||
bFirst = false
|
||||
bLastOptional = bOptional
|
||||
end
|
||||
|
||||
if (data.syntax:utf8len() == 0) then
|
||||
data.syntax = "<none>"
|
||||
end
|
||||
else
|
||||
data.syntax = data.syntax or "<none>"
|
||||
end
|
||||
|
||||
-- Add the command to the list of commands.
|
||||
local alias = data.alias
|
||||
|
||||
if (alias) then
|
||||
if (istable(alias)) then
|
||||
for _, v in ipairs(alias) do
|
||||
ix.command.list[v:lower()] = data
|
||||
end
|
||||
elseif (isstring(alias)) then
|
||||
ix.command.list[alias:lower()] = data
|
||||
end
|
||||
end
|
||||
|
||||
ix.command.list[command] = data
|
||||
end
|
||||
|
||||
--- Returns true if a player is allowed to run a certain command.
|
||||
-- @realm shared
|
||||
-- @player client Player to check access for
|
||||
-- @string command Name of the command to check access for
|
||||
-- @treturn bool Whether or not the player is allowed to run the command
|
||||
function ix.command.HasAccess(client, command)
|
||||
command = ix.command.list[command:lower()]
|
||||
|
||||
if (command) then
|
||||
if (command.OnCheckAccess) then
|
||||
return command:OnCheckAccess(client)
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Returns a table of arguments from a given string.
|
||||
-- Words separated by spaces will be considered one argument. To have an argument containing multiple words, they must be
|
||||
-- contained within quotation marks.
|
||||
-- @realm shared
|
||||
-- @string text String to extract arguments from
|
||||
-- @treturn table Arguments extracted from string
|
||||
-- @usage PrintTable(ix.command.ExtractArgs("these are \"some arguments\""))
|
||||
-- > 1 = these
|
||||
-- > 2 = are
|
||||
-- > 3 = some arguments
|
||||
function ix.command.ExtractArgs(text)
|
||||
local skip = 0
|
||||
local arguments = {}
|
||||
local curString = ""
|
||||
|
||||
for i = 1, text:utf8len() do
|
||||
if (i <= skip) then continue end
|
||||
|
||||
local c = text:utf8sub(i, i)
|
||||
|
||||
if (c == "\"") then
|
||||
local match = text:utf8sub(i):match("%b\"\"")
|
||||
|
||||
if (match) then
|
||||
curString = ""
|
||||
skip = i + match:utf8len()
|
||||
arguments[#arguments + 1] = match:utf8sub(2, -2)
|
||||
else
|
||||
curString = curString..c
|
||||
end
|
||||
elseif (c == " " and curString != "") then
|
||||
arguments[#arguments + 1] = curString
|
||||
curString = ""
|
||||
else
|
||||
if (c == " " and curString == "") then
|
||||
continue
|
||||
end
|
||||
|
||||
curString = curString..c
|
||||
end
|
||||
end
|
||||
|
||||
if (curString != "") then
|
||||
arguments[#arguments + 1] = curString
|
||||
end
|
||||
|
||||
return arguments
|
||||
end
|
||||
|
||||
--- Returns an array of potential commands by unique id.
|
||||
-- When bSorted is true, the commands will be sorted by name. When bReorganize is true, it will move any exact match to the top
|
||||
-- of the array. When bRemoveDupes is true, it will remove any commands that have the same NAME.
|
||||
-- @realm shared
|
||||
-- @string identifier Search query
|
||||
-- @bool[opt=false] bSorted Whether or not to sort the commands by name
|
||||
-- @bool[opt=false] bReorganize Whether or not any exact match will be moved to the top of the array
|
||||
-- @bool[opt=false] bRemoveDupes Whether or not to remove any commands that have the same name
|
||||
-- @treturn table Array of command tables whose name partially or completely matches the search query
|
||||
function ix.command.FindAll(identifier, bSorted, bReorganize, bRemoveDupes)
|
||||
local result = {}
|
||||
local iterator = bSorted and SortedPairs or pairs
|
||||
local fullMatch
|
||||
|
||||
identifier = identifier:lower()
|
||||
|
||||
if (identifier == "/") then
|
||||
-- we don't simply copy because we need numeric indices
|
||||
for _, v in iterator(ix.command.list) do
|
||||
result[#result + 1] = v
|
||||
end
|
||||
|
||||
return result
|
||||
elseif (identifier:utf8sub(1, 1) == "/") then
|
||||
identifier = identifier:utf8sub(2)
|
||||
end
|
||||
|
||||
for k, v in iterator(ix.command.list) do
|
||||
if (k:match(identifier)) then
|
||||
local index = #result + 1
|
||||
result[index] = v
|
||||
|
||||
if (k == identifier) then
|
||||
fullMatch = index
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (bReorganize and fullMatch and fullMatch != 1) then
|
||||
result[1], result[fullMatch] = result[fullMatch], result[1]
|
||||
end
|
||||
|
||||
if (bRemoveDupes) then
|
||||
local commandNames = {}
|
||||
|
||||
-- using pairs intead of ipairs because we might remove from array
|
||||
for k, v in pairs(result) do
|
||||
if (commandNames[v.name]) then
|
||||
table.remove(result, k)
|
||||
end
|
||||
|
||||
commandNames[v.name] = true
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixCommand")
|
||||
|
||||
--- Attempts to find a player by an identifier. If unsuccessful, a notice will be displayed to the specified player. The
|
||||
-- search criteria is derived from `ix.util.FindPlayer`.
|
||||
-- @realm server
|
||||
-- @player client Player to give a notification to if the player could not be found
|
||||
-- @string name Search query
|
||||
-- @treturn[1] player Player that matches the given search query
|
||||
-- @treturn[2] nil If a player could not be found
|
||||
-- @see ix.util.FindPlayer
|
||||
function ix.command.FindPlayer(client, name)
|
||||
local target = isstring(name) and ix.util.FindPlayer(name) or NULL
|
||||
|
||||
if (IsValid(target)) then
|
||||
return target
|
||||
else
|
||||
client:NotifyLocalized("plyNoExist")
|
||||
end
|
||||
end
|
||||
|
||||
--- Forces a player to execute a command by name.
|
||||
-- @realm server
|
||||
-- @player client Player who is executing the command
|
||||
-- @string command Full name of the command to be executed. This string gets lowered, but it's good practice to stick with
|
||||
-- the exact name of the command
|
||||
-- @tab arguments Array of arguments to be passed to the command
|
||||
-- @usage ix.command.Run(player.GetByID(1), "Roll", {10})
|
||||
function ix.command.Run(client, command, arguments)
|
||||
if ((client.ixCommandCooldown or 0) > RealTime()) then
|
||||
return
|
||||
end
|
||||
|
||||
command = ix.command.list[tostring(command):lower()]
|
||||
|
||||
if (!command) then
|
||||
return
|
||||
end
|
||||
|
||||
-- we throw it into a table since arguments get unpacked and only
|
||||
-- the arguments table gets passed in by default
|
||||
local argumentsTable = arguments
|
||||
arguments = {argumentsTable}
|
||||
|
||||
-- if feedback is non-nil, we can assume that the command failed
|
||||
-- and is a phrase string
|
||||
local feedback
|
||||
|
||||
-- check for group access
|
||||
if (command.OnCheckAccess) then
|
||||
local bSuccess, phrase = command:OnCheckAccess(client)
|
||||
feedback = !bSuccess and L(phrase and phrase or "noPerm", client) or nil
|
||||
end
|
||||
|
||||
-- check for strict arguments
|
||||
if (!feedback and command.arguments) then
|
||||
arguments = ArgumentCheckStub(command, client, argumentsTable)
|
||||
|
||||
if (isstring(arguments)) then
|
||||
feedback = arguments
|
||||
end
|
||||
end
|
||||
|
||||
-- run the command if all the checks passed
|
||||
if (!feedback) then
|
||||
local results = {command:OnRun(client, unpack(arguments))}
|
||||
local phrase = results[1]
|
||||
|
||||
-- check to see if the command has returned a phrase string and display it
|
||||
if (isstring(phrase)) then
|
||||
if (IsValid(client)) then
|
||||
if (phrase:sub(1, 1) == "@") then
|
||||
client:NotifyLocalized(phrase:sub(2), unpack(results, 2))
|
||||
else
|
||||
client:Notify(phrase)
|
||||
end
|
||||
else
|
||||
-- print message since we're running from the server console
|
||||
print(phrase)
|
||||
end
|
||||
end
|
||||
|
||||
client.ixCommandCooldown = RealTime() + 0.5
|
||||
|
||||
if (IsValid(client)) then
|
||||
ix.log.Add(client, "command", COMMAND_PREFIX .. command.name, argumentsTable and table.concat(argumentsTable, " "))
|
||||
end
|
||||
else
|
||||
client:Notify(feedback)
|
||||
end
|
||||
end
|
||||
|
||||
--- Parses a chat string and runs the command if one is found. Specifically, it checks for commands in a string with the
|
||||
-- format `/CommandName some arguments`
|
||||
-- @realm server
|
||||
-- @player client Player who is executing the command
|
||||
-- @string text Input string to search for the command format
|
||||
-- @string[opt] realCommand Specific command to check for. If this is specified, it will not try to run any command that's
|
||||
-- found at the beginning - only if it matches `realCommand`
|
||||
-- @tab[opt] arguments Array of arguments to pass to the command. If not specified, it will try to extract it from the
|
||||
-- string specified in `text` using `ix.command.ExtractArgs`
|
||||
-- @treturn bool Whether or not a command has been found
|
||||
-- @usage ix.command.Parse(player.GetByID(1), "/roll 10")
|
||||
function ix.command.Parse(client, text, realCommand, arguments)
|
||||
if (realCommand or text:utf8sub(1, 1) == COMMAND_PREFIX) then
|
||||
-- See if the string contains a command.
|
||||
local match = realCommand or text:utf8lower():match(COMMAND_PREFIX.."([_%w]+)")
|
||||
|
||||
-- is it unicode text?
|
||||
if (!match) then
|
||||
local post = string.Explode(" ", text)
|
||||
local len = string.len(post[1])
|
||||
|
||||
match = post[1]:utf8sub(2, len)
|
||||
end
|
||||
|
||||
match = match:utf8lower()
|
||||
|
||||
local command = ix.command.list[match]
|
||||
-- We have a valid, registered command.
|
||||
if (command) then
|
||||
-- Get the arguments like a console command.
|
||||
if (!arguments) then
|
||||
arguments = ix.command.ExtractArgs(text:utf8sub(match:utf8len() + 3))
|
||||
end
|
||||
|
||||
-- Runs the actual command.
|
||||
ix.command.Run(client, match, arguments)
|
||||
else
|
||||
if (IsValid(client)) then
|
||||
client:NotifyLocalized("cmdNoExist")
|
||||
else
|
||||
print("Sorry, that command does not exist.")
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
concommand.Add("ix", function(client, _, arguments)
|
||||
local command = arguments[1]
|
||||
table.remove(arguments, 1)
|
||||
|
||||
ix.command.Parse(client, nil, command or "", arguments)
|
||||
end)
|
||||
|
||||
net.Receive("ixCommand", function(length, client)
|
||||
if ((client.ixNextCmd or 0) < CurTime()) then
|
||||
local command = net.ReadString()
|
||||
local indices = net.ReadUInt(4)
|
||||
local arguments = {}
|
||||
|
||||
for _ = 1, indices do
|
||||
local value = net.ReadType()
|
||||
|
||||
if (isstring(value) or isnumber(value)) then
|
||||
arguments[#arguments + 1] = tostring(value)
|
||||
end
|
||||
end
|
||||
|
||||
ix.command.Parse(client, nil, command, arguments)
|
||||
client.ixNextCmd = CurTime() + 0.2
|
||||
end
|
||||
end)
|
||||
else
|
||||
--- Request the server to run a command. This mimics similar functionality to the client typing `/CommandName` in the chatbox.
|
||||
-- @realm client
|
||||
-- @string command Unique ID of the command
|
||||
-- @param ... Arguments to pass to the command
|
||||
-- @usage ix.command.Send("roll", 10)
|
||||
function ix.command.Send(command, ...)
|
||||
local arguments = {...}
|
||||
|
||||
net.Start("ixCommand")
|
||||
net.WriteString(command)
|
||||
net.WriteUInt(#arguments, 4)
|
||||
|
||||
for _, v in ipairs(arguments) do
|
||||
net.WriteType(v)
|
||||
end
|
||||
|
||||
net.SendToServer()
|
||||
end
|
||||
end
|
||||
166
gamemodes/helix/gamemode/core/libs/sh_compnettable.lua
Normal file
166
gamemodes/helix/gamemode/core/libs/sh_compnettable.lua
Normal file
@@ -0,0 +1,166 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
ix.compnettable = ix.compnettable or {}
|
||||
-- size of chunks
|
||||
ix.compnettable.chunkSize = 60000
|
||||
|
||||
do
|
||||
if (CLIENT) then
|
||||
ix.compnettable.doExpoBackOff = true
|
||||
-- expo backoff and jitter vars
|
||||
ix.compnettable.maxBackOffTime = 20 -- tenth of a second
|
||||
ix.compnettable.baseBackOffTime = 1 -- tenth of a second
|
||||
ix.compnettable.attemptsInWindow = 0
|
||||
ix.compnettable.windowSize = 1 -- seconds
|
||||
ix.compnettable.lastRequest = CurTime()
|
||||
else
|
||||
-- don't do backoffs on the server (that'd break a lot of shit)
|
||||
ix.compnettable.doExpobackOff = false
|
||||
end
|
||||
end
|
||||
|
||||
-- writes a compressed and chunked table with the following mechanism:
|
||||
--[[
|
||||
1) sends an int of the number of chunks to expect
|
||||
2) writes the chunks as compressed strings
|
||||
3) returns number of chunks to caller
|
||||
|
||||
IMPORTANT
|
||||
THIS MUST BE USED IN THE CORRECT CONTEXT. EX:
|
||||
|
||||
net.Send("YourNetHook", function()
|
||||
ix.compnettable:Write(myTable)
|
||||
end)
|
||||
net.Receive("YourNetHook", function()
|
||||
local yourTable = ix.compnettable:Read()
|
||||
end)
|
||||
|
||||
OTHERWISE IT WILL BREAK YOUR SHIT
|
||||
]]
|
||||
function ix.compnettable:Write(tbl)
|
||||
if (!istable(tbl)) then
|
||||
return nil
|
||||
end
|
||||
|
||||
local str = util.TableToJSON(tbl)
|
||||
local basestrlen = string.len(str)
|
||||
|
||||
if (!str or basestrlen < 1) then
|
||||
error("Provided table could not be converted to JSON!")
|
||||
end
|
||||
|
||||
local nchunks = math.ceil(basestrlen / self.chunkSize)
|
||||
net.WriteInt(nchunks, 17)
|
||||
|
||||
for i=0, nchunks + 1, 1 do
|
||||
local stringslice = string.sub(
|
||||
str,
|
||||
i * self.chunkSize,
|
||||
(i + 1) *self.chunkSize
|
||||
)
|
||||
local chunk = util.Compress(stringslice)
|
||||
|
||||
if (!chunk) then
|
||||
error(
|
||||
"Chunk "
|
||||
..tostring(i)
|
||||
.." cannot be compressed! Attempted to compress: "
|
||||
..stringslice
|
||||
)
|
||||
end
|
||||
|
||||
net.WriteInt(#chunk, 17)
|
||||
net.WriteData(chunk, #chunk)
|
||||
end
|
||||
|
||||
return nchunks
|
||||
end
|
||||
|
||||
|
||||
-- attempts to read a table compressed by ix.compnettable.Write()
|
||||
--[[ IMPORTANT
|
||||
THIS MUST BE USED IN THE CORRECT CONTEXT. EX:
|
||||
|
||||
net.Send("YourNetHook", function()
|
||||
ix.compnettable:Write(myTable)
|
||||
end)
|
||||
net.Receive("YourNetHook", function()
|
||||
local yourTable = ix.compnettable:Read()
|
||||
end)
|
||||
|
||||
OTHERWISE IT WILL BREAK YOUR SHIT
|
||||
]]
|
||||
function ix.compnettable:Read()
|
||||
local nchunks = net.ReadInt(17)
|
||||
if (nchunks < 1) then
|
||||
error("No chunks to read!")
|
||||
end
|
||||
|
||||
local fullstr = ""
|
||||
for i=0, nchunks + 1, 1 do
|
||||
local chunksize = net.ReadInt(17)
|
||||
local strcomp = net.ReadData(chunksize)
|
||||
if (#strcomp == 1 and strcomp[0] == 0) then
|
||||
error("Expected substring at idx ("..tostring(i)..") out of ("..tostring(nchunks)..") is empty or nil!")
|
||||
end
|
||||
|
||||
local strchunk = util.Decompress(strcomp)
|
||||
if (!strchunk) then
|
||||
error("Substring ("..tostring(i)..") out of ("..tostring(nchunks)..") failed to decompress!")
|
||||
end
|
||||
fullstr = fullstr..strchunk
|
||||
end
|
||||
|
||||
return util.JSONToTable(fullstr)
|
||||
end
|
||||
|
||||
-- reads with a brief backoff to help with overflow issues
|
||||
-- https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
|
||||
-- ^^ a cheap (and wildly simplified) version of this:
|
||||
function ix.compnettable:ReadWithBackoff()
|
||||
if (CLIENT) then
|
||||
local backOffTime = 0
|
||||
local reqStart = SysTime()
|
||||
|
||||
if (self.doExpoBackOff) then
|
||||
if ((reqStart - self.lastRequest) < self.windowSize) then
|
||||
self.attemptsInWindow = self.attemptsInWindow + 1
|
||||
local minBackOffTime = math.min(
|
||||
math.pow(
|
||||
self.baseBackOffTime,
|
||||
self.attemptsInWindow
|
||||
),
|
||||
self.maxBackOffTime
|
||||
)
|
||||
local maxBackOffTime = minBackOffTime + (self.baseBackOffTime * 10)
|
||||
backOffTime = math.Rand(minBackOffTime, maxBackOffTime) / 10
|
||||
else
|
||||
self.attemptsInWindow = 0
|
||||
end
|
||||
end
|
||||
|
||||
if (backOffTime > 0) then
|
||||
print("Backing off for "..tostring(backOffTime).." seconds...")
|
||||
-- ^^ leave this in for now just in case ;)
|
||||
end
|
||||
|
||||
local sec = tonumber(SysTime() + backOffTime);
|
||||
while (SysTime() < sec) do
|
||||
-- wait for backOffTime in seconds...
|
||||
-- NOTE: This might cause some freezing and things
|
||||
-- BUT A FREEZE IS BETTER THAN AN OVERFLOW!!!!!!!!
|
||||
end
|
||||
|
||||
self.lastRequest = reqStart
|
||||
end
|
||||
|
||||
return self:Read()
|
||||
end
|
||||
121
gamemodes/helix/gamemode/core/libs/sh_currency.lua
Normal file
121
gamemodes/helix/gamemode/core/libs/sh_currency.lua
Normal file
@@ -0,0 +1,121 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
--- A library representing the server's currency system.
|
||||
-- @module ix.currency
|
||||
|
||||
ix.currency = ix.currency or {}
|
||||
ix.currency.symbol = ix.currency.symbol or "$"
|
||||
ix.currency.singular = ix.currency.singular or "dollar"
|
||||
ix.currency.plural = ix.currency.plural or "dollars"
|
||||
ix.currency.model = ix.currency.model or "models/props_lab/box01a.mdl"
|
||||
|
||||
--- Sets the currency type.
|
||||
-- @realm shared
|
||||
-- @string symbol The symbol of the currency.
|
||||
-- @string singular The name of the currency in it's singular form.
|
||||
-- @string plural The name of the currency in it's plural form.
|
||||
-- @string model The model of the currency entity.
|
||||
function ix.currency.Set(symbol, singular, plural, model)
|
||||
ix.currency.symbol = symbol
|
||||
ix.currency.singular = singular
|
||||
ix.currency.plural = plural
|
||||
ix.currency.model = model
|
||||
end
|
||||
|
||||
--- Returns a formatted string according to the current currency.
|
||||
-- @realm shared
|
||||
-- @number amount The amount of cash being formatted.
|
||||
-- @treturn string The formatted string.
|
||||
function ix.currency.Get(amount)
|
||||
if (amount == 1) then
|
||||
return ix.currency.symbol.."1 "..ix.currency.singular
|
||||
else
|
||||
return ix.currency.symbol..amount.." "..ix.currency.plural
|
||||
end
|
||||
end
|
||||
|
||||
--- Spawns an amount of cash at a specific location on the map.
|
||||
-- @realm shared
|
||||
-- @vector pos The position of the money to be spawned.
|
||||
-- @number amount The amount of cash being spawned.
|
||||
-- @angle[opt=angle_zero] angle The angle of the entity being spawned.
|
||||
-- @treturn entity The spawned money entity.
|
||||
function ix.currency.Spawn(pos, amount, angle)
|
||||
if (!amount or amount < 0) then
|
||||
print("[Helix] Can't create currency entity: Invalid Amount of money")
|
||||
return
|
||||
end
|
||||
|
||||
local money = ents.Create("ix_money")
|
||||
money:Spawn()
|
||||
|
||||
if (IsValid(pos) and pos:IsPlayer()) then
|
||||
pos = pos:GetItemDropPos(money)
|
||||
elseif (!isvector(pos)) then
|
||||
print("[Helix] Can't create currency entity: Invalid Position")
|
||||
|
||||
money:Remove()
|
||||
return
|
||||
end
|
||||
|
||||
money:SetPos(pos)
|
||||
-- double check for negative.
|
||||
money:SetAmount(math.Round(math.abs(amount)))
|
||||
money:SetAngles(angle or angle_zero)
|
||||
money:Activate()
|
||||
|
||||
return money
|
||||
end
|
||||
|
||||
function GM:OnPickupMoney(client, moneyEntity)
|
||||
if (IsValid(moneyEntity)) then
|
||||
local amount = moneyEntity:GetAmount()
|
||||
|
||||
client:GetCharacter():GiveMoney(amount)
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local character = ix.meta.character
|
||||
|
||||
function character:HasMoney(amount)
|
||||
if (amount < 0) then
|
||||
print("Negative Money Check Received.")
|
||||
end
|
||||
|
||||
return self:GetMoney() >= amount
|
||||
end
|
||||
|
||||
function character:GiveMoney(amount, bNoLog)
|
||||
amount = math.abs(amount)
|
||||
|
||||
if (!bNoLog) then
|
||||
ix.log.Add(self:GetPlayer(), "money", amount)
|
||||
end
|
||||
|
||||
self:SetMoney(self:GetMoney() + amount)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function character:TakeMoney(amount, bNoLog)
|
||||
amount = math.abs(amount)
|
||||
|
||||
if (!bNoLog) then
|
||||
ix.log.Add(self:GetPlayer(), "money", -amount)
|
||||
end
|
||||
|
||||
self:SetMoney(self:GetMoney() - amount)
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
156
gamemodes/helix/gamemode/core/libs/sh_date.lua
Normal file
156
gamemodes/helix/gamemode/core/libs/sh_date.lua
Normal file
@@ -0,0 +1,156 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Persistent date and time handling.
|
||||
|
||||
All of Lua's time functions are dependent on the Unix epoch, which means we can't have dates that go further than 1970. This
|
||||
library remedies this problem. Time/date is represented by a `date` object that is queried, instead of relying on the seconds
|
||||
since the epoch.
|
||||
|
||||
## Futher documentation
|
||||
This library makes use of a third-party date library found at https://github.com/Tieske/date - you can find all documentation
|
||||
regarding the `date` object and its methods there.
|
||||
]]
|
||||
-- @module ix.date
|
||||
|
||||
ix.date = ix.date or {}
|
||||
ix.date.lib = ix.date.lib or include("thirdparty/sh_date.lua")
|
||||
ix.date.timeScale = ix.date.timeScale or ix.config.Get("secondsPerMinute", 60) -- seconds per minute
|
||||
ix.date.current = ix.date.current or ix.date.lib() -- current in-game date/time
|
||||
ix.date.start = ix.date.start or CurTime() -- arbitrary start time for calculating date/time offset
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixDateSync")
|
||||
|
||||
--- Loads the date from disk.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
function ix.date.Initialize()
|
||||
local currentDate = ix.data.Get("date", nil, false, true)
|
||||
|
||||
-- construct new starting date if we don't have it saved already
|
||||
if (!currentDate) then
|
||||
currentDate = {
|
||||
year = ix.config.Get("year"),
|
||||
month = ix.config.Get("month"),
|
||||
day = ix.config.Get("day"),
|
||||
hour = tonumber(os.date("%H")) or 0,
|
||||
min = tonumber(os.date("%M")) or 0,
|
||||
sec = tonumber(os.date("%S")) or 0
|
||||
}
|
||||
|
||||
currentDate = ix.date.lib.serialize(ix.date.lib(currentDate))
|
||||
ix.data.Set("date", currentDate, false, true)
|
||||
end
|
||||
|
||||
ix.date.timeScale = ix.config.Get("secondsPerMinute", 60)
|
||||
ix.date.current = ix.date.lib.construct(currentDate)
|
||||
end
|
||||
|
||||
--- Updates the internal in-game date/time representation and resets the offset.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
function ix.date.ResolveOffset()
|
||||
ix.date.current = ix.date.Get()
|
||||
ix.date.start = CurTime()
|
||||
end
|
||||
|
||||
--- Updates the time scale of the in-game date/time. The time scale is given in seconds per minute (i.e how many real life
|
||||
-- seconds it takes for an in-game minute to pass). You should avoid using this function and use the in-game config menu to
|
||||
-- change the time scale instead.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @number secondsPerMinute New time scale
|
||||
function ix.date.UpdateTimescale(secondsPerMinute)
|
||||
ix.date.ResolveOffset()
|
||||
ix.date.timeScale = secondsPerMinute
|
||||
end
|
||||
|
||||
--- Sends the current date to a player. This is done automatically when the player joins the server.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @player[opt=nil] client Player to send the date to, or `nil` to send to everyone
|
||||
function ix.date.Send(client)
|
||||
net.Start("ixDateSync")
|
||||
|
||||
net.WriteFloat(ix.date.timeScale)
|
||||
net.WriteTable(ix.date.current)
|
||||
net.WriteFloat(ix.date.start)
|
||||
|
||||
if (client) then
|
||||
net.Send(client)
|
||||
else
|
||||
net.Broadcast()
|
||||
end
|
||||
end
|
||||
|
||||
--- Saves the current in-game date to disk.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
function ix.date.Save()
|
||||
ix.date.bSaving = true
|
||||
|
||||
ix.date.ResolveOffset() -- resolve offset so we save the actual time to disk
|
||||
ix.data.Set("date", ix.date.lib.serialize(ix.date.current), false, true)
|
||||
|
||||
-- update config to reflect current saved date
|
||||
ix.config.Set("year", ix.date.current:getyear())
|
||||
ix.config.Set("month", ix.date.current:getmonth())
|
||||
ix.config.Set("day", ix.date.current:getday())
|
||||
|
||||
ix.date.bSaving = nil
|
||||
end
|
||||
else
|
||||
net.Receive("ixDateSync", function()
|
||||
local timeScale = net.ReadFloat()
|
||||
local currentDate = ix.date.lib.construct(net.ReadTable())
|
||||
local startTime = net.ReadFloat()
|
||||
|
||||
ix.date.timeScale = timeScale
|
||||
ix.date.current = currentDate
|
||||
ix.date.start = startTime
|
||||
end)
|
||||
end
|
||||
|
||||
--- Returns the currently set date.
|
||||
-- @realm shared
|
||||
-- @treturn date Current in-game date
|
||||
function ix.date.Get()
|
||||
local minutesSinceStart = (CurTime() - ix.date.start) / ix.date.timeScale
|
||||
|
||||
return ix.date.current:copy():addminutes(minutesSinceStart)
|
||||
end
|
||||
|
||||
--- Returns a string formatted version of a date.
|
||||
-- @realm shared
|
||||
-- @string format Format string
|
||||
-- @date[opt=nil] currentDate Date to format. If nil, it will use the currently set date
|
||||
-- @treturn string Formatted date
|
||||
function ix.date.GetFormatted(format, currentDate)
|
||||
return (currentDate or ix.date.Get()):fmt(format)
|
||||
end
|
||||
|
||||
--- Returns a serialized version of a date. This is useful when you need to network a date to clients, or save a date to disk.
|
||||
-- @realm shared
|
||||
-- @date[opt=nil] currentDate Date to serialize. If nil, it will use the currently set date
|
||||
-- @treturn table Serialized date
|
||||
function ix.date.GetSerialized(currentDate)
|
||||
return ix.date.lib.serialize(currentDate or ix.date.Get())
|
||||
end
|
||||
|
||||
--- Returns a date object from a table or serialized date.
|
||||
-- @realm shared
|
||||
-- @param currentDate Date to construct
|
||||
-- @treturn date Constructed date object
|
||||
function ix.date.Construct(currentDate)
|
||||
return ix.date.lib.construct(currentDate)
|
||||
end
|
||||
135
gamemodes/helix/gamemode/core/libs/sh_faction.lua
Normal file
135
gamemodes/helix/gamemode/core/libs/sh_faction.lua
Normal file
@@ -0,0 +1,135 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
--- Helper library for loading/getting faction information.
|
||||
-- @module ix.faction
|
||||
|
||||
ix.faction = ix.faction or {}
|
||||
ix.faction.teams = ix.faction.teams or {}
|
||||
ix.faction.indices = ix.faction.indices or {}
|
||||
|
||||
local CITIZEN_MODELS = {
|
||||
"models/humans/group01/male_01.mdl",
|
||||
"models/humans/group01/male_02.mdl",
|
||||
"models/humans/group01/male_04.mdl",
|
||||
"models/humans/group01/male_05.mdl",
|
||||
"models/humans/group01/male_06.mdl",
|
||||
"models/humans/group01/male_07.mdl",
|
||||
"models/humans/group01/male_08.mdl",
|
||||
"models/humans/group01/male_09.mdl",
|
||||
"models/humans/group02/male_01.mdl",
|
||||
"models/humans/group02/male_03.mdl",
|
||||
"models/humans/group02/male_05.mdl",
|
||||
"models/humans/group02/male_07.mdl",
|
||||
"models/humans/group02/male_09.mdl",
|
||||
"models/humans/group01/female_01.mdl",
|
||||
"models/humans/group01/female_02.mdl",
|
||||
"models/humans/group01/female_03.mdl",
|
||||
"models/humans/group01/female_06.mdl",
|
||||
"models/humans/group01/female_07.mdl",
|
||||
"models/humans/group02/female_01.mdl",
|
||||
"models/humans/group02/female_03.mdl",
|
||||
"models/humans/group02/female_06.mdl",
|
||||
"models/humans/group01/female_04.mdl"
|
||||
}
|
||||
|
||||
--- Loads factions from a directory.
|
||||
-- @realm shared
|
||||
-- @string directory The path to the factions files.
|
||||
function ix.faction.LoadFromDir(directory)
|
||||
for _, v in ipairs(file.Find(directory.."/*.lua", "LUA")) do
|
||||
local niceName = v:sub(4, -5)
|
||||
|
||||
FACTION = ix.faction.teams[niceName] or {index = table.Count(ix.faction.teams) + 1, isDefault = false}
|
||||
if (PLUGIN) then
|
||||
FACTION.plugin = PLUGIN.uniqueID
|
||||
end
|
||||
|
||||
ix.util.Include(directory.."/"..v, "shared")
|
||||
|
||||
if (!FACTION.name) then
|
||||
FACTION.name = "Unknown"
|
||||
ErrorNoHalt("Faction '"..niceName.."' is missing a name. You need to add a FACTION.name = \"Name\"\n")
|
||||
end
|
||||
|
||||
if (!FACTION.color) then
|
||||
FACTION.color = Color(150, 150, 150)
|
||||
ErrorNoHalt("Faction '"..niceName.."' is missing a color. You need to add FACTION.color = Color(1, 2, 3)\n")
|
||||
end
|
||||
|
||||
team.SetUp(FACTION.index, FACTION.name or "Unknown", FACTION.color or Color(125, 125, 125))
|
||||
|
||||
FACTION.models = FACTION.models or CITIZEN_MODELS
|
||||
FACTION.uniqueID = FACTION.uniqueID or niceName
|
||||
|
||||
for _, v2 in pairs(FACTION.models) do
|
||||
if (isstring(v2)) then
|
||||
util.PrecacheModel(v2)
|
||||
elseif (istable(v2)) then
|
||||
util.PrecacheModel(v2[1])
|
||||
end
|
||||
end
|
||||
|
||||
if (!FACTION.GetModels) then
|
||||
function FACTION:GetModels(client)
|
||||
return self.models
|
||||
end
|
||||
end
|
||||
|
||||
ix.faction.indices[FACTION.index] = FACTION
|
||||
ix.faction.teams[niceName] = FACTION
|
||||
FACTION = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Retrieves a faction table.
|
||||
-- @realm shared
|
||||
-- @param identifier Index or name of the faction
|
||||
-- @treturn table Faction table
|
||||
-- @usage print(ix.faction.Get(Entity(1):Team()).name)
|
||||
-- > "Citizen"
|
||||
function ix.faction.Get(identifier)
|
||||
return ix.faction.indices[identifier] or ix.faction.teams[identifier]
|
||||
end
|
||||
|
||||
--- Retrieves a faction index.
|
||||
-- @realm shared
|
||||
-- @string uniqueID Unique ID of the faction
|
||||
-- @treturn number Faction index
|
||||
function ix.faction.GetIndex(uniqueID)
|
||||
for k, v in ipairs(ix.faction.indices) do
|
||||
if (v.uniqueID == uniqueID) then
|
||||
return k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (CLIENT) then
|
||||
--- Returns true if a faction requires a whitelist.
|
||||
-- @realm client
|
||||
-- @number faction Index of the faction
|
||||
-- @treturn bool Whether or not the faction requires a whitelist
|
||||
function ix.faction.HasWhitelist(faction)
|
||||
local data = ix.faction.indices[faction]
|
||||
|
||||
if (data) then
|
||||
if (data.isDefault) then
|
||||
return true
|
||||
end
|
||||
|
||||
local ixData = ix.localData and ix.localData.whitelists or {}
|
||||
|
||||
return ixData[Schema.folder] and ixData[Schema.folder][data.uniqueID] == true or false
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
213
gamemodes/helix/gamemode/core/libs/sh_flag.lua
Normal file
213
gamemodes/helix/gamemode/core/libs/sh_flag.lua
Normal file
@@ -0,0 +1,213 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Grants abilities to characters.
|
||||
|
||||
Flags are a simple way of adding/removing certain abilities to players on a per-character basis. Helix comes with a few flags
|
||||
by default, for example to restrict spawning of props, usage of the physgun, etc. All flags will be listed in the
|
||||
`Flags` section of the `Help` menu. Flags are usually used when server validation is required to allow a player to do something
|
||||
on their character. However, it's usually preferable to use in-character methods over flags when possible (i.e restricting
|
||||
the business menu to characters that have a permit item, rather than using flags to determine availability).
|
||||
|
||||
Flags are a single alphanumeric character that can be checked on the server. Serverside callbacks can be used to provide
|
||||
functionality whenever the flag is added or removed. For example:
|
||||
ix.flag.Add("z", "Access to some cool stuff.", function(client, bGiven)
|
||||
print("z flag given:", bGiven)
|
||||
end)
|
||||
|
||||
Entity(1):GetCharacter():GiveFlags("z")
|
||||
> z flag given: true
|
||||
|
||||
Entity(1):GetCharacter():TakeFlags("z")
|
||||
> z flag given: false
|
||||
|
||||
print(Entity(1):GetCharacter():HasFlags("z"))
|
||||
> false
|
||||
|
||||
Check out `Character:GiveFlags` and `Character:TakeFlags` for additional info.
|
||||
]]
|
||||
-- @module ix.flag
|
||||
|
||||
ix.flag = ix.flag or {}
|
||||
ix.flag.list = ix.flag.list or {}
|
||||
|
||||
--- Creates a flag. This should be called shared in order for the client to be aware of the flag's existence.
|
||||
-- @realm shared
|
||||
-- @string flag Alphanumeric character to use for the flag
|
||||
-- @string description Description of the flag
|
||||
-- @func callback Function to call when the flag is given or taken from a player
|
||||
function ix.flag.Add(flag, description, callback)
|
||||
ix.flag.list[flag] = {
|
||||
description = description,
|
||||
callback = callback
|
||||
}
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
-- Called to apply flags when a player has spawned.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @player client Player to setup flags for
|
||||
function ix.flag.OnSpawn(client)
|
||||
-- Check if they have a valid character.
|
||||
if (client:GetCharacter()) then
|
||||
-- Get all of the character's flags.
|
||||
local flags = client:GetCharacter():GetFlags()
|
||||
|
||||
for i = 1, #flags do
|
||||
-- Get each individual flag.
|
||||
local flag = flags[i]
|
||||
local info = ix.flag.list[flag]
|
||||
|
||||
-- Check if the flag has a callback.
|
||||
if (info and info.callback) then
|
||||
-- Run the callback, passing the player and true so they get whatever benefits.
|
||||
info.callback(client, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local character = ix.meta.character
|
||||
|
||||
if (SERVER) then
|
||||
--- Flag util functions for character
|
||||
-- @classmod Character
|
||||
|
||||
--- Sets this character's accessible flags. Note that this method overwrites **all** flags instead of adding them.
|
||||
-- @realm server
|
||||
-- @string flags Flag(s) this charater is allowed to have
|
||||
-- @see GiveFlags
|
||||
function character:SetFlags(flags)
|
||||
self:SetData("f", flags)
|
||||
end
|
||||
|
||||
--- Adds a flag to the list of this character's accessible flags. This does not overwrite existing flags.
|
||||
-- @realm server
|
||||
-- @string flags Flag(s) this character should be given
|
||||
-- @usage character:GiveFlags("pet")
|
||||
-- -- gives p, e, and t flags to the character
|
||||
-- @see HasFlags
|
||||
function character:GiveFlags(flags)
|
||||
local addedFlags = ""
|
||||
|
||||
-- Get the individual flags within the flag string.
|
||||
for i = 1, #flags do
|
||||
local flag = flags[i]
|
||||
local info = ix.flag.list[flag]
|
||||
|
||||
if (info) then
|
||||
if (!self:HasFlags(flag)) then
|
||||
addedFlags = addedFlags..flag
|
||||
end
|
||||
|
||||
if (info.callback) then
|
||||
-- Pass the player and true (true for the flag being given.)
|
||||
info.callback(self:GetPlayer(), true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Only change the flag string if it is different.
|
||||
if (addedFlags != "") then
|
||||
self:SetFlags(self:GetFlags()..addedFlags)
|
||||
end
|
||||
end
|
||||
|
||||
--- Removes this character's access to the given flags.
|
||||
-- @realm server
|
||||
-- @string flags Flag(s) to remove from this character
|
||||
-- @usage -- for a character with "pet" flags
|
||||
-- character:TakeFlags("p")
|
||||
-- -- character now has e, and t flags
|
||||
function character:TakeFlags(flags)
|
||||
local oldFlags = self:GetFlags()
|
||||
local newFlags = oldFlags
|
||||
|
||||
-- Get the individual flags within the flag string.
|
||||
for i = 1, #flags do
|
||||
local flag = flags[i]
|
||||
local info = ix.flag.list[flag]
|
||||
|
||||
-- Call the callback if the flag has been registered.
|
||||
if (info and info.callback) then
|
||||
-- Pass the player and false (false since the flag is being taken)
|
||||
info.callback(self:GetPlayer(), false)
|
||||
end
|
||||
|
||||
newFlags = newFlags:gsub(flag, "")
|
||||
end
|
||||
|
||||
if (newFlags != oldFlags) then
|
||||
self:SetFlags(newFlags)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns all of the flags this character has.
|
||||
-- @realm shared
|
||||
-- @treturn string Flags this character has represented as one string. You can access individual flags by iterating through
|
||||
-- the string letter by letter
|
||||
function character:GetFlags()
|
||||
return self:GetData("f", "")
|
||||
end
|
||||
|
||||
--- Returns `true` if the character has the given flag(s).
|
||||
-- @realm shared
|
||||
-- @string flags Flag(s) to check access for
|
||||
-- @treturn bool Whether or not this character has access to the given flag(s)
|
||||
function character:HasFlags(flags)
|
||||
local bHasFlag = hook.Run("CharacterHasFlags", self, flags)
|
||||
|
||||
if (bHasFlag == true) then
|
||||
return true
|
||||
end
|
||||
|
||||
local flagList = self:GetFlags()
|
||||
|
||||
for i = 1, #flags do
|
||||
if (flagList:find(flags[i], 1, true)) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
ix.flag.Add("p", "Access to the physgun.", function(client, isGiven)
|
||||
if (isGiven) then
|
||||
client:Give("weapon_physgun")
|
||||
--client:SelectWeapon("weapon_physgun")
|
||||
else
|
||||
client:StripWeapon("weapon_physgun")
|
||||
end
|
||||
end)
|
||||
|
||||
ix.flag.Add("t", "Access to the toolgun", function(client, isGiven)
|
||||
if (isGiven) then
|
||||
client:Give("gmod_tool")
|
||||
--client:SelectWeapon("gmod_tool")
|
||||
else
|
||||
client:StripWeapon("gmod_tool")
|
||||
end
|
||||
end)
|
||||
|
||||
ix.flag.Add("c", "Access to spawn chairs.")
|
||||
ix.flag.Add("C", "Access to spawn vehicles.")
|
||||
ix.flag.Add("r", "Access to spawn ragdolls.")
|
||||
ix.flag.Add("e", "Access to spawn props.")
|
||||
ix.flag.Add("n", "Access to spawn NPCs.")
|
||||
end
|
||||
186
gamemodes/helix/gamemode/core/libs/sh_inventory.lua
Normal file
186
gamemodes/helix/gamemode/core/libs/sh_inventory.lua
Normal file
@@ -0,0 +1,186 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Inventory manipulation and helper functions.
|
||||
]]
|
||||
-- @module ix.inventory
|
||||
|
||||
ix.inventory = ix.inventory or {}
|
||||
|
||||
ix.util.Include("helix/gamemode/core/meta/sh_inventory.lua")
|
||||
|
||||
--- Retrieves an inventory table.
|
||||
-- @realm shared
|
||||
-- @number invID Index of the inventory
|
||||
-- @treturn Inventory Inventory table
|
||||
function ix.inventory.Get(invID)
|
||||
return ix.item.inventories[invID]
|
||||
end
|
||||
|
||||
function ix.inventory.Create(width, height, id)
|
||||
local inventory = ix.meta.inventory:New(id, width, height)
|
||||
ix.item.inventories[id] = inventory
|
||||
return inventory
|
||||
end
|
||||
|
||||
--- Loads an inventory and associated items from the database into memory. If you are passing a table into `invID`, it
|
||||
-- requires a table where the key is the inventory ID, and the value is a table of the width and height values. See below
|
||||
-- for an example.
|
||||
-- @realm server
|
||||
-- @param invID Inventory ID or table of inventory IDs
|
||||
-- @number width Width of inventory (this is not used when passing a table to `invID`)
|
||||
-- @number height Height of inventory (this is not used when passing a table to `invID`)
|
||||
-- @func callback Function to call when inventory is restored
|
||||
-- @usage ix.inventory.Restore({
|
||||
-- [10] = {5, 5},
|
||||
-- [11] = {7, 4}
|
||||
-- })
|
||||
-- -- inventories 10 and 11 with sizes (5, 5) and (7, 4) will be loaded
|
||||
function ix.inventory.Restore(invID, width, height, callback)
|
||||
local inventories = {}
|
||||
|
||||
if (!istable(invID)) then
|
||||
if (!isnumber(invID) or invID < 0) then
|
||||
error("Attempt to restore inventory with an invalid ID!")
|
||||
end
|
||||
|
||||
inventories[invID] = {width, height}
|
||||
ix.inventory.Create(width, height, invID)
|
||||
else
|
||||
for k, v in pairs(invID) do
|
||||
inventories[k] = {v[1], v[2]}
|
||||
ix.inventory.Create(v[1], v[2], k)
|
||||
end
|
||||
end
|
||||
|
||||
local query = mysql:Select("ix_items")
|
||||
query:Select("item_id")
|
||||
query:Select("inventory_id")
|
||||
query:Select("unique_id")
|
||||
query:Select("data")
|
||||
query:Select("character_id")
|
||||
query:Select("player_id")
|
||||
query:Select("x")
|
||||
query:Select("y")
|
||||
query:WhereIn("inventory_id", table.GetKeys(inventories))
|
||||
query:Callback(function(result)
|
||||
if (istable(result) and #result > 0) then
|
||||
local invSlots = {}
|
||||
|
||||
for _, item in ipairs(result) do
|
||||
local itemInvID = tonumber(item.inventory_id)
|
||||
local invInfo = inventories[itemInvID]
|
||||
|
||||
if (!itemInvID or !invInfo) then
|
||||
-- don't restore items with an invalid inventory id or type
|
||||
continue
|
||||
end
|
||||
|
||||
local inventory = ix.item.inventories[itemInvID]
|
||||
local x, y = tonumber(item.x), tonumber(item.y)
|
||||
local itemID = tonumber(item.item_id)
|
||||
local data = util.JSONToTable(item.data or "[]")
|
||||
local characterID, playerID = tonumber(item.character_id), tostring(item.player_id)
|
||||
|
||||
if (x and y and itemID) then
|
||||
if (x <= inventory.w and x > 0 and y <= inventory.h and y > 0) then
|
||||
local item2 = ix.item.New(item.unique_id, itemID)
|
||||
|
||||
if (item2) then
|
||||
invSlots[itemInvID] = invSlots[itemInvID] or {}
|
||||
local slots = invSlots[itemInvID]
|
||||
|
||||
item2.data = {}
|
||||
|
||||
if (data) then
|
||||
item2.data = data
|
||||
end
|
||||
|
||||
item2.gridX = x
|
||||
item2.gridY = y
|
||||
item2.invID = itemInvID
|
||||
item2.characterID = characterID
|
||||
item2.playerID = (playerID == "" or playerID == "NULL") and nil or playerID
|
||||
|
||||
for x2 = 0, item2.width - 1 do
|
||||
for y2 = 0, item2.height - 1 do
|
||||
slots[x + x2] = slots[x + x2] or {}
|
||||
slots[x + x2][y + y2] = item2
|
||||
end
|
||||
end
|
||||
|
||||
if (item2.OnRestored) then
|
||||
item2:OnRestored(item2, itemInvID)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for k, v in pairs(invSlots) do
|
||||
ix.item.inventories[k].slots = v
|
||||
end
|
||||
end
|
||||
|
||||
if (callback) then
|
||||
for k, _ in pairs(inventories) do
|
||||
callback(ix.item.inventories[k])
|
||||
end
|
||||
end
|
||||
end)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
function ix.inventory.New(owner, invType, callback)
|
||||
local invData = ix.item.inventoryTypes[invType] or {w = 1, h = 1}
|
||||
|
||||
local query = mysql:Insert("ix_inventories")
|
||||
query:Insert("inventory_type", invType)
|
||||
query:Insert("character_id", owner)
|
||||
query:Callback(function(result, status, lastID)
|
||||
local inventory = ix.inventory.Create(invData.w, invData.h, lastID)
|
||||
|
||||
if (invType) then
|
||||
if invType == "equipInventory" then
|
||||
inventory.vars.isBag = false
|
||||
else
|
||||
inventory.vars.isBag = invType
|
||||
end
|
||||
end
|
||||
|
||||
if (isnumber(owner) and owner > 0) then
|
||||
local character = ix.char.loaded[owner]
|
||||
local client = character:GetPlayer()
|
||||
|
||||
inventory:SetOwner(owner)
|
||||
|
||||
if (IsValid(client)) then
|
||||
inventory:Sync(client)
|
||||
end
|
||||
end
|
||||
|
||||
if (callback) then
|
||||
callback(inventory)
|
||||
end
|
||||
end)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
function ix.inventory.Register(invType, w, h, isBag)
|
||||
ix.item.inventoryTypes[invType] = {w = w, h = h}
|
||||
|
||||
if (isBag) then
|
||||
ix.item.inventoryTypes[invType].isBag = invType
|
||||
end
|
||||
|
||||
return ix.item.inventoryTypes[invType]
|
||||
end
|
||||
879
gamemodes/helix/gamemode/core/libs/sh_item.lua
Normal file
879
gamemodes/helix/gamemode/core/libs/sh_item.lua
Normal file
@@ -0,0 +1,879 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
--[[--
|
||||
Item manipulation and helper functions.
|
||||
]]
|
||||
-- @module ix.item
|
||||
|
||||
ix.item = ix.item or {}
|
||||
ix.item.list = ix.item.list or {}
|
||||
ix.item.base = ix.item.base or {}
|
||||
ix.item.instances = ix.item.instances or {}
|
||||
ix.item.inventories = ix.item.inventories or {
|
||||
[0] = {}
|
||||
}
|
||||
ix.item.inventoryTypes = ix.item.inventoryTypes or {}
|
||||
|
||||
ix.util.Include("helix/gamemode/core/meta/sh_item.lua")
|
||||
|
||||
-- Declare some supports for logic inventory
|
||||
local zeroInv = ix.item.inventories[0]
|
||||
|
||||
function zeroInv:GetID()
|
||||
return 0
|
||||
end
|
||||
|
||||
function zeroInv:OnCheckAccess(client)
|
||||
return true
|
||||
end
|
||||
|
||||
-- WARNING: You have to manually sync the data to client if you're trying to use item in the logical inventory in the vgui.
|
||||
function zeroInv:Add(uniqueID, quantity, data, x, y)
|
||||
quantity = quantity or 1
|
||||
|
||||
if (quantity > 0) then
|
||||
if (!isnumber(uniqueID)) then
|
||||
if (quantity > 1) then
|
||||
for _ = 1, quantity do
|
||||
self:Add(uniqueID, 1, data)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local itemTable = ix.item.list[uniqueID]
|
||||
|
||||
if (!itemTable) then
|
||||
return false, "invalidItem"
|
||||
end
|
||||
|
||||
ix.item.Instance(0, uniqueID, data, x, y, function(item)
|
||||
self[item:GetID()] = item
|
||||
end)
|
||||
|
||||
return nil, nil, 0
|
||||
end
|
||||
else
|
||||
return false, "notValid"
|
||||
end
|
||||
end
|
||||
|
||||
function ix.item.Instance(index, uniqueID, itemData, x, y, callback, characterID, playerID)
|
||||
if (!uniqueID or ix.item.list[uniqueID]) then
|
||||
itemData = istable(itemData) and itemData or {}
|
||||
|
||||
local query = mysql:Insert("ix_items")
|
||||
query:Insert("inventory_id", index)
|
||||
query:Insert("unique_id", uniqueID)
|
||||
query:Insert("data", util.TableToJSON(itemData))
|
||||
query:Insert("x", x)
|
||||
query:Insert("y", y)
|
||||
|
||||
if (characterID) then
|
||||
query:Insert("character_id", characterID)
|
||||
end
|
||||
|
||||
if (playerID) then
|
||||
query:Insert("player_id", playerID)
|
||||
end
|
||||
|
||||
query:Callback(function(result, status, lastID)
|
||||
local item = ix.item.New(uniqueID, lastID)
|
||||
|
||||
if (item) then
|
||||
item.data = table.Copy(itemData)
|
||||
item.invID = index
|
||||
item.characterID = characterID
|
||||
item.playerID = playerID
|
||||
|
||||
if (callback) then
|
||||
callback(item)
|
||||
end
|
||||
|
||||
if (item.OnInstanced) then
|
||||
item:OnInstanced(index, x, y, item)
|
||||
end
|
||||
end
|
||||
end)
|
||||
query:Execute()
|
||||
else
|
||||
ErrorNoHalt("[Helix] Attempt to give an invalid item! (" .. (uniqueID or "nil") .. ")\n")
|
||||
end
|
||||
end
|
||||
|
||||
--- Retrieves an item table.
|
||||
-- @realm shared
|
||||
-- @string identifier Unique ID of the item
|
||||
-- @treturn item Item table
|
||||
-- @usage print(ix.item.Get("example"))
|
||||
-- > "item[example][0]"
|
||||
function ix.item.Get(identifier)
|
||||
return ix.item.base[identifier] or ix.item.list[identifier]
|
||||
end
|
||||
|
||||
function ix.item.Load(path, baseID, isBaseItem)
|
||||
local uniqueID = path:match("sh_([_%w]+)%.lua")
|
||||
|
||||
if (uniqueID) then
|
||||
uniqueID = (isBaseItem and "base_" or "")..uniqueID
|
||||
ix.item.Register(uniqueID, baseID, isBaseItem, path)
|
||||
else
|
||||
if (!path:find(".txt")) then
|
||||
ErrorNoHalt("[Helix] Item at '"..path.."' follows invalid naming convention!\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ix.item.Register(uniqueID, baseID, isBaseItem, path, luaGenerated)
|
||||
local meta = ix.meta.item
|
||||
|
||||
if (uniqueID) then
|
||||
ITEM = (isBaseItem and ix.item.base or ix.item.list)[uniqueID] or setmetatable({}, meta)
|
||||
ITEM.uniqueID = uniqueID
|
||||
ITEM.base = baseID or ITEM.base
|
||||
ITEM.isBase = isBaseItem
|
||||
ITEM.hooks = ITEM.hooks or {}
|
||||
ITEM.postHooks = ITEM.postHooks or {}
|
||||
ITEM.functions = ITEM.functions or {}
|
||||
ITEM.functions.drop = ITEM.functions.drop or {
|
||||
tip = "dropTip",
|
||||
icon = "icon16/world.png",
|
||||
OnRun = function(item)
|
||||
local bSuccess, error = item:Transfer(nil, nil, nil, item.player)
|
||||
|
||||
if (!bSuccess and isstring(error)) then
|
||||
item.player:NotifyLocalized(error)
|
||||
else
|
||||
item.player:EmitSound("npc/zombie/foot_slide" .. math.random(1, 3) .. ".wav", 75, math.random(90, 120), 1)
|
||||
end
|
||||
|
||||
return false
|
||||
end,
|
||||
OnCanRun = function(item)
|
||||
return !IsValid(item.entity) and !item.noDrop
|
||||
end
|
||||
}
|
||||
ITEM.functions.take = ITEM.functions.take or {
|
||||
tip = "takeTip",
|
||||
icon = "icon16/box.png",
|
||||
OnRun = function(item)
|
||||
local client = item.player
|
||||
local bSuccess, error = item:Transfer(client:GetCharacter():GetInventory():GetID(), nil, nil, client)
|
||||
|
||||
if (!bSuccess) then
|
||||
client:NotifyLocalized(error or "unknownError")
|
||||
return false
|
||||
else
|
||||
client:EmitSound("npc/zombie/foot_slide" .. math.random(1, 3) .. ".wav", 75, math.random(90, 120), 1)
|
||||
|
||||
if (item.data) then -- I don't like it but, meh...
|
||||
for k, v in pairs(item.data) do
|
||||
item:SetData(k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
OnCanRun = function(item)
|
||||
return IsValid(item.entity)
|
||||
end
|
||||
}
|
||||
ITEM.functions.removeLabel = ITEM.functions.removeLabel or {
|
||||
name = "Remove Label",
|
||||
icon = "icon16/tag_blue_delete.png",
|
||||
OnRun = function(item)
|
||||
local client = item.player
|
||||
local inventory = client:GetCharacter():GetInventory()
|
||||
inventory:Add("itemlabel", 1, {
|
||||
labelInfoName = item:GetData("labelName"),
|
||||
labelInfoDesc = item:GetData("labelDescription")
|
||||
})
|
||||
|
||||
item:SetData("labelName", false)
|
||||
item:SetData("labelDescription", false)
|
||||
|
||||
client:Notify("Etiketi şundan kaldırdınız: " .. item.name .. ".")
|
||||
|
||||
return false
|
||||
end,
|
||||
OnCanRun = function(item)
|
||||
return item:GetData("labelName", false) or item:GetData("labelDescription", false)
|
||||
end
|
||||
}
|
||||
|
||||
local oldBase = ITEM.base
|
||||
|
||||
if (ITEM.base) then
|
||||
local baseTable = ix.item.base[ITEM.base]
|
||||
|
||||
if (baseTable) then
|
||||
for k, v in pairs(baseTable) do
|
||||
if (ITEM[k] == nil) then
|
||||
ITEM[k] = v
|
||||
end
|
||||
|
||||
ITEM.baseTable = baseTable
|
||||
end
|
||||
|
||||
local mergeTable = table.Copy(baseTable)
|
||||
ITEM = table.Merge(mergeTable, ITEM)
|
||||
else
|
||||
ErrorNoHalt("[Helix] Item '"..ITEM.uniqueID.."' has a non-existent base! ("..ITEM.base..")\n")
|
||||
end
|
||||
end
|
||||
|
||||
if (PLUGIN) then
|
||||
ITEM.plugin = PLUGIN.uniqueID
|
||||
end
|
||||
|
||||
if (!luaGenerated and path) then
|
||||
ix.util.Include(path)
|
||||
end
|
||||
|
||||
if (ITEM.base and oldBase != ITEM.base) then
|
||||
local baseTable = ix.item.base[ITEM.base]
|
||||
|
||||
if (baseTable) then
|
||||
for k, v in pairs(baseTable) do
|
||||
if (ITEM[k] == nil) then
|
||||
ITEM[k] = v
|
||||
end
|
||||
|
||||
ITEM.baseTable = baseTable
|
||||
end
|
||||
|
||||
local mergeTable = table.Copy(baseTable)
|
||||
ITEM = table.Merge(mergeTable, ITEM)
|
||||
else
|
||||
ErrorNoHalt("[Helix] Item '"..ITEM.uniqueID.."' has a non-existent base! ("..ITEM.base..")\n")
|
||||
end
|
||||
end
|
||||
|
||||
ITEM.description = ITEM.description or "noDesc"
|
||||
ITEM.width = ITEM.width or 1
|
||||
ITEM.height = ITEM.height or 1
|
||||
ITEM.category = ITEM.category or "misc"
|
||||
|
||||
if (ITEM.OnRegistered) then
|
||||
ITEM:OnRegistered()
|
||||
end
|
||||
|
||||
(isBaseItem and ix.item.base or ix.item.list)[ITEM.uniqueID] = ITEM
|
||||
|
||||
if (IX_RELOADED) then
|
||||
-- we don't know which item was actually edited, so we'll refresh all of them
|
||||
for _, v in pairs(ix.item.instances) do
|
||||
if (v.uniqueID == uniqueID) then
|
||||
table.Merge(v, ITEM)
|
||||
end
|
||||
end
|
||||
end
|
||||
if (luaGenerated) then
|
||||
return ITEM
|
||||
else
|
||||
ITEM = nil
|
||||
end
|
||||
else
|
||||
ErrorNoHalt("[Helix] You tried to register an item without uniqueID!\n")
|
||||
end
|
||||
end
|
||||
|
||||
function ix.item.LoadFromDir(directory)
|
||||
local files, folders
|
||||
|
||||
files = file.Find(directory.."/base/*.lua", "LUA")
|
||||
|
||||
for _, v in ipairs(files) do
|
||||
ix.item.Load(directory.."/base/"..v, nil, true)
|
||||
end
|
||||
|
||||
files, folders = file.Find(directory.."/*", "LUA")
|
||||
|
||||
for _, v in ipairs(folders) do
|
||||
if (v == "base") then
|
||||
continue
|
||||
end
|
||||
|
||||
for _, v2 in ipairs(file.Find(directory.."/"..v.."/*.lua", "LUA")) do
|
||||
ix.item.Load(directory.."/"..v .. "/".. v2, "base_"..v)
|
||||
end
|
||||
end
|
||||
|
||||
for _, v in ipairs(files) do
|
||||
ix.item.Load(directory.."/"..v)
|
||||
end
|
||||
end
|
||||
|
||||
function ix.item.New(uniqueID, id)
|
||||
if (ix.item.instances[id] and ix.item.instances[id].uniqueID == uniqueID) then
|
||||
return ix.item.instances[id]
|
||||
end
|
||||
|
||||
local stockItem = ix.item.list[uniqueID]
|
||||
|
||||
if (stockItem) then
|
||||
local item = setmetatable({id = id, data = {}}, {
|
||||
__index = stockItem,
|
||||
__eq = stockItem.__eq,
|
||||
__tostring = stockItem.__tostring
|
||||
})
|
||||
|
||||
ix.item.instances[id] = item
|
||||
|
||||
return item
|
||||
else
|
||||
ErrorNoHalt("[Helix] Attempt to index unknown item '"..uniqueID.."'\n")
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
function ix.item.GetInv(invID)
|
||||
ErrorNoHalt("ix.item.GetInv is deprecated. Use ix.inventory.Get instead!\n")
|
||||
return ix.inventory.Get(invID)
|
||||
end
|
||||
|
||||
function ix.item.RegisterInv(invType, w, h, isBag)
|
||||
ErrorNoHalt("ix.item.RegisterInv is deprecated. Use ix.inventory.Register instead!\n")
|
||||
return ix.inventory.Register(invType, w, h, isBag)
|
||||
end
|
||||
|
||||
function ix.item.NewInv(owner, invType, callback)
|
||||
ErrorNoHalt("ix.item.NewInv is deprecated. Use ix.inventory.New instead!\n")
|
||||
return ix.inventory.New(owner, invType, callback)
|
||||
end
|
||||
|
||||
function ix.item.CreateInv(width, height, id)
|
||||
ErrorNoHalt("ix.item.CreateInv is deprecated. Use ix.inventory.Create instead!\n")
|
||||
return ix.inventory.Create(width, height, id)
|
||||
end
|
||||
|
||||
function ix.item.RestoreInv(invID, width, height, callback)
|
||||
ErrorNoHalt("ix.item.RestoreInv is deprecated. Use ix.inventory.Restore instead!\n")
|
||||
return ix.inventory.Restore(invID, width, height, callback)
|
||||
end
|
||||
|
||||
if (CLIENT) then
|
||||
net.Receive("ixInventorySync", function()
|
||||
local slots = net.ReadTable()
|
||||
local id = net.ReadUInt(32)
|
||||
local w, h = net.ReadUInt(6), net.ReadUInt(6)
|
||||
local owner = net.ReadType()
|
||||
local vars = net.ReadTable()
|
||||
|
||||
if (!LocalPlayer():GetCharacter()) then
|
||||
return
|
||||
end
|
||||
|
||||
local character = owner and ix.char.loaded[owner]
|
||||
local inventory = ix.inventory.Create(w, h, id)
|
||||
inventory.slots = {}
|
||||
inventory.vars = vars
|
||||
|
||||
local x, y
|
||||
|
||||
for _, v in ipairs(slots) do
|
||||
x, y = v[1], v[2]
|
||||
|
||||
inventory.slots[x] = inventory.slots[x] or {}
|
||||
|
||||
local item = ix.item.New(v[3], v[4])
|
||||
|
||||
item.data = {}
|
||||
if (v[5]) then
|
||||
item.data = v[5]
|
||||
end
|
||||
|
||||
item.invID = item.invID or id
|
||||
inventory.slots[x][y] = item
|
||||
end
|
||||
|
||||
if (character) then
|
||||
inventory:SetOwner(character:GetID())
|
||||
character.vars.inv = character.vars.inv or {}
|
||||
|
||||
for k, v in ipairs(character:GetInventory(true)) do
|
||||
if (v:GetID() == id) then
|
||||
character:GetInventory(true)[k] = inventory
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(character.vars.inv, inventory)
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixInventoryData", function()
|
||||
local id = net.ReadUInt(32)
|
||||
local item = ix.item.instances[id]
|
||||
|
||||
if (item) then
|
||||
local key = net.ReadString()
|
||||
local value = net.ReadType()
|
||||
|
||||
item.data = item.data or {}
|
||||
item.data[key] = value
|
||||
|
||||
local invID = item.invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or item.invID
|
||||
local panel = ix.gui["inv" .. invID]
|
||||
|
||||
if (panel and panel.panels) then
|
||||
local icon = panel.panels[id]
|
||||
|
||||
if (icon) then
|
||||
icon:SetHelixTooltip(function(tooltip)
|
||||
ix.hud.PopulateItemTooltip(tooltip, item)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixInventorySet", function()
|
||||
local invID = net.ReadUInt(32)
|
||||
local x, y = net.ReadUInt(6), net.ReadUInt(6)
|
||||
local uniqueID = net.ReadString()
|
||||
local id = net.ReadUInt(32)
|
||||
local owner = net.ReadUInt(32)
|
||||
local data = net.ReadTable()
|
||||
|
||||
local character = owner != 0 and ix.char.loaded[owner] or LocalPlayer():GetCharacter()
|
||||
|
||||
if (character) then
|
||||
local inventory = ix.item.inventories[invID]
|
||||
|
||||
if (inventory) then
|
||||
local item = (uniqueID != "" and id != 0) and ix.item.New(uniqueID, id) or nil
|
||||
item.invID = invID
|
||||
item.data = {}
|
||||
|
||||
if (data) then
|
||||
item.data = data
|
||||
end
|
||||
|
||||
inventory.slots[x] = inventory.slots[x] or {}
|
||||
inventory.slots[x][y] = item
|
||||
|
||||
invID = invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or invID
|
||||
|
||||
local panel = ix.gui["inv" .. invID]
|
||||
|
||||
if (IsValid(panel)) then
|
||||
local icon = panel:AddIcon(item, item:GetModel() or "models/props_junk/popcan01a.mdl",
|
||||
x, y, item.width, item.height, item:GetSkin(), item:GetModelBodygroups(), item.color, item.material, item.rotate, item.OnInventoryDraw)
|
||||
|
||||
if (IsValid(icon)) then
|
||||
icon:SetHelixTooltip(function(tooltip)
|
||||
ix.hud.PopulateItemTooltip(tooltip, item)
|
||||
end)
|
||||
|
||||
icon.itemID = item.id
|
||||
panel.panels[item.id] = icon
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixInventoryMove", function()
|
||||
local invID = net.ReadUInt(32)
|
||||
local inventory = ix.item.inventories[invID]
|
||||
|
||||
if (!inventory) then
|
||||
return
|
||||
end
|
||||
|
||||
local itemID = net.ReadUInt(32)
|
||||
local oldX = net.ReadUInt(6)
|
||||
local oldY = net.ReadUInt(6)
|
||||
local x = net.ReadUInt(6)
|
||||
local y = net.ReadUInt(6)
|
||||
|
||||
invID = invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or invID
|
||||
|
||||
local item = ix.item.instances[itemID]
|
||||
local panel = ix.gui["inv" .. invID]
|
||||
|
||||
-- update inventory UI if it's open
|
||||
if (IsValid(panel)) then
|
||||
local icon = panel.panels[itemID]
|
||||
|
||||
if (IsValid(icon)) then
|
||||
icon:Move(x, y, panel, true)
|
||||
end
|
||||
end
|
||||
|
||||
-- update inventory slots
|
||||
if (item) then
|
||||
inventory.slots[oldX][oldY] = nil
|
||||
|
||||
inventory.slots[x] = inventory.slots[x] or {}
|
||||
inventory.slots[x][y] = item
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixInventoryRemove", function()
|
||||
local id = net.ReadUInt(32)
|
||||
local invID = net.ReadUInt(32)
|
||||
|
||||
local inventory = ix.item.inventories[invID]
|
||||
|
||||
if (!inventory) then
|
||||
return
|
||||
end
|
||||
|
||||
inventory:Remove(id)
|
||||
|
||||
invID = invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or invID
|
||||
local panel = ix.gui["inv" .. invID]
|
||||
|
||||
if (IsValid(panel)) then
|
||||
local icon = panel.panels[id]
|
||||
|
||||
if (IsValid(icon)) then
|
||||
for _, v in ipairs(icon.slots or {}) do
|
||||
if (v.item == icon) then
|
||||
v.item = nil
|
||||
end
|
||||
end
|
||||
|
||||
icon:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
local item = ix.item.instances[id]
|
||||
|
||||
if (!item) then
|
||||
return
|
||||
end
|
||||
|
||||
-- we need to close any bag windows that are open because of this item
|
||||
if (item.isBag) then
|
||||
local itemInv = item:GetInventory()
|
||||
|
||||
if (itemInv) then
|
||||
local frame = ix.gui["inv" .. itemInv:GetID()]
|
||||
|
||||
if (IsValid(frame)) then
|
||||
frame:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
else
|
||||
util.AddNetworkString("ixInventorySync")
|
||||
util.AddNetworkString("ixInventorySet")
|
||||
util.AddNetworkString("ixInventoryMove")
|
||||
util.AddNetworkString("ixInventoryRemove")
|
||||
util.AddNetworkString("ixInventoryData")
|
||||
util.AddNetworkString("ixInventoryAction")
|
||||
|
||||
function ix.item.LoadItemByID(itemIndex, recipientFilter, callback)
|
||||
local query = mysql:Select("ix_items")
|
||||
query:Select("item_id")
|
||||
query:Select("unique_id")
|
||||
query:Select("data")
|
||||
query:Select("character_id")
|
||||
query:Select("player_id")
|
||||
query:WhereIn("item_id", itemIndex)
|
||||
query:Callback(function(result)
|
||||
if (istable(result)) then
|
||||
for _, v in ipairs(result) do
|
||||
local itemID = tonumber(v.item_id)
|
||||
local data = util.JSONToTable(v.data or "[]")
|
||||
local uniqueID = v.unique_id
|
||||
local itemTable = ix.item.list[uniqueID]
|
||||
local characterID = tonumber(v.character_id)
|
||||
local playerID = tostring(v.player_id)
|
||||
|
||||
if (itemTable and itemID) then
|
||||
local item = ix.item.New(uniqueID, itemID)
|
||||
|
||||
item.data = data or {}
|
||||
item.invID = 0
|
||||
item.characterID = characterID
|
||||
item.playerID = (playerID == "" or playerID == "NULL") and nil or playerID
|
||||
|
||||
if (callback) then
|
||||
callback(item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
function ix.item.PerformInventoryAction(client, action, item, invID, data)
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if (!character) then
|
||||
return
|
||||
end
|
||||
|
||||
local inventory = ix.item.inventories[invID or 0]
|
||||
|
||||
if (hook.Run("CanPlayerInteractItem", client, action, item, data) == false) then
|
||||
return
|
||||
end
|
||||
|
||||
if (!inventory:OnCheckAccess(client)) then
|
||||
return
|
||||
end
|
||||
|
||||
if (isentity(item)) then
|
||||
if (IsValid(item)) then
|
||||
local entity = item
|
||||
local itemID = item.ixItemID
|
||||
item = ix.item.instances[itemID]
|
||||
|
||||
if (!item) then
|
||||
return
|
||||
end
|
||||
|
||||
item.entity = entity
|
||||
item.player = client
|
||||
else
|
||||
return
|
||||
end
|
||||
elseif (isnumber(item)) then
|
||||
item = ix.item.instances[item]
|
||||
|
||||
if (!item) then
|
||||
return
|
||||
end
|
||||
|
||||
item.player = client
|
||||
end
|
||||
|
||||
if (item.entity) then
|
||||
if (item.entity:GetPos():Distance(client:GetPos()) > 96) then
|
||||
return
|
||||
end
|
||||
elseif (!inventory:GetItemByID(item.id)) then
|
||||
return
|
||||
end
|
||||
|
||||
if (!item.bAllowMultiCharacterInteraction and IsValid(client) and client:GetCharacter()) then
|
||||
local itemPlayerID = item:GetPlayerID()
|
||||
local itemCharacterID = item:GetCharacterID()
|
||||
local playerID = client:SteamID64()
|
||||
local characterID = client:GetCharacter():GetID()
|
||||
|
||||
if (itemPlayerID and itemCharacterID and itemPlayerID == playerID and itemCharacterID != characterID) then
|
||||
client:NotifyLocalized("itemOwned")
|
||||
|
||||
item.player = nil
|
||||
item.entity = nil
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local callback = item.functions[action]
|
||||
|
||||
if (callback) then
|
||||
if (callback.OnCanRun and callback.OnCanRun(item, data) == false) then
|
||||
item.entity = nil
|
||||
item.player = nil
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
hook.Run("PlayerInteractItem", client, action, item)
|
||||
|
||||
local entity = item.entity
|
||||
local result
|
||||
|
||||
if (item.hooks[action]) then
|
||||
result = item.hooks[action](item, data)
|
||||
end
|
||||
|
||||
if (result == nil) then
|
||||
result = callback.OnRun(item, data)
|
||||
end
|
||||
|
||||
if (item.postHooks[action]) then
|
||||
-- Posthooks shouldn't override the result from OnRun
|
||||
item.postHooks[action](item, result, data)
|
||||
end
|
||||
|
||||
if (result != false) then
|
||||
if (IsValid(entity)) then
|
||||
entity.ixIsSafe = true
|
||||
entity:Remove()
|
||||
else
|
||||
item:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
item.entity = nil
|
||||
item.player = nil
|
||||
|
||||
return result != false
|
||||
end
|
||||
end
|
||||
|
||||
local function NetworkInventoryMove(receiver, invID, itemID, oldX, oldY, x, y)
|
||||
net.Start("ixInventoryMove")
|
||||
net.WriteUInt(invID, 32)
|
||||
net.WriteUInt(itemID, 32)
|
||||
net.WriteUInt(oldX, 6)
|
||||
net.WriteUInt(oldY, 6)
|
||||
net.WriteUInt(x, 6)
|
||||
net.WriteUInt(y, 6)
|
||||
net.Send(receiver)
|
||||
end
|
||||
|
||||
net.Receive("ixInventoryMove", function(length, client)
|
||||
local oldX, oldY, x, y = net.ReadUInt(6), net.ReadUInt(6), net.ReadUInt(6), net.ReadUInt(6)
|
||||
local invID, newInvID = net.ReadUInt(32), net.ReadUInt(32)
|
||||
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if (character) then
|
||||
local inventory = ix.item.inventories[invID]
|
||||
|
||||
if (!inventory or inventory == nil) then
|
||||
inventory:Sync(client)
|
||||
end
|
||||
|
||||
if ((!inventory.owner or (inventory.owner and inventory.owner == character:GetID())) or
|
||||
inventory:OnCheckAccess(client)) then
|
||||
local item = inventory:GetItemAt(oldX, oldY)
|
||||
|
||||
if (item) then
|
||||
if (newInvID and invID != newInvID) then
|
||||
local inventory2 = ix.item.inventories[newInvID]
|
||||
|
||||
if (inventory2) then
|
||||
local bStatus, error = item:Transfer(newInvID, x, y, client)
|
||||
|
||||
if (!bStatus) then
|
||||
NetworkInventoryMove(
|
||||
client, item.invID, item:GetID(), item.gridX, item.gridY, item.gridX, item.gridY
|
||||
)
|
||||
|
||||
client:NotifyLocalized(error or "unknownError")
|
||||
end
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if hook.Run("CanMoveItemSameInv", item, inventory, x, y) == false then return end
|
||||
|
||||
if (inventory:CanItemFit(x, y, item.width, item.height, item)) then
|
||||
item.gridX = x
|
||||
item.gridY = y
|
||||
|
||||
for x2 = 0, item.width - 1 do
|
||||
for y2 = 0, item.height - 1 do
|
||||
local previousX = inventory.slots[oldX + x2]
|
||||
|
||||
if (previousX) then
|
||||
previousX[oldY + y2] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for x2 = 0, item.width - 1 do
|
||||
for y2 = 0, item.height - 1 do
|
||||
inventory.slots[x + x2] = inventory.slots[x + x2] or {}
|
||||
inventory.slots[x + x2][y + y2] = item
|
||||
end
|
||||
end
|
||||
|
||||
local receivers = inventory:GetReceivers()
|
||||
|
||||
if (istable(receivers)) then
|
||||
local filtered = {}
|
||||
|
||||
for _, v in ipairs(receivers) do
|
||||
if (v != client) then
|
||||
filtered[#filtered + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
if (#filtered > 0) then
|
||||
NetworkInventoryMove(
|
||||
filtered, invID, item:GetID(), oldX, oldY, x, y
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if (!inventory.noSave) then
|
||||
local query = mysql:Update("ix_items")
|
||||
query:Update("x", x)
|
||||
query:Update("y", y)
|
||||
query:Where("item_id", item.id)
|
||||
query:Execute()
|
||||
end
|
||||
else
|
||||
NetworkInventoryMove(
|
||||
client, item.invID, item:GetID(), item.gridX, item.gridY, item.gridX, item.gridY
|
||||
)
|
||||
end
|
||||
end
|
||||
else
|
||||
local item = inventory:GetItemAt(oldX, oldY)
|
||||
|
||||
if (item) then
|
||||
NetworkInventoryMove(
|
||||
client, item.invID, item.invID, item:GetID(), item.gridX, item.gridY, item.gridX, item.gridY
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixInventoryAction", function(length, client)
|
||||
ix.item.PerformInventoryAction(client, net.ReadString(), net.ReadUInt(32), net.ReadUInt(32), net.ReadTable())
|
||||
end)
|
||||
end
|
||||
|
||||
--- Instances and spawns a given item type.
|
||||
-- @realm server
|
||||
-- @string uniqueID Unique ID of the item
|
||||
-- @vector position The position in which the item's entity will be spawned
|
||||
-- @func[opt=nil] callback Function to call when the item entity is created
|
||||
-- @angle[opt=angle_zero] angles The angles at which the item's entity will spawn
|
||||
-- @tab[opt=nil] data Additional data for this item instance
|
||||
function ix.item.Spawn(uniqueID, position, callback, angles, data)
|
||||
ix.item.Instance(0, uniqueID, data or {}, 1, 1, function(item)
|
||||
local entity = item:Spawn(position, angles)
|
||||
|
||||
if (callback) then
|
||||
callback(item, entity)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
--- Inventory util functions for character
|
||||
-- @classmod Character
|
||||
|
||||
--- Returns this character's associated `Inventory` object.
|
||||
-- @function GetInventory
|
||||
-- @realm shared
|
||||
-- @treturn Inventory This character's inventory
|
||||
ix.char.RegisterVar("Inventory", {
|
||||
bNoNetworking = true,
|
||||
bNoDisplay = true,
|
||||
OnGet = function(character, index)
|
||||
if (index and !isnumber(index)) then
|
||||
return character.vars.inv or {}
|
||||
end
|
||||
|
||||
return character.vars.inv and character.vars.inv[index or 1]
|
||||
end,
|
||||
alias = "Inv"
|
||||
})
|
||||
138
gamemodes/helix/gamemode/core/libs/sh_language.lua
Normal file
138
gamemodes/helix/gamemode/core/libs/sh_language.lua
Normal file
@@ -0,0 +1,138 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Multi-language phrase support.
|
||||
|
||||
Helix has support for multiple languages, and you can easily leverage this system for use in your own schema, plugins, etc.
|
||||
Languages will be loaded from the schema and any plugins in `languages/sh_languagename.lua`, where `languagename` is the id of a
|
||||
language (`english` for English, `french` for French, etc). The structure of a language file is a table of phrases with the key
|
||||
as its phrase ID and the value as its translation for that language. For example, in `plugins/area/sh_english.lua`:
|
||||
LANGUAGE = {
|
||||
area = "Area",
|
||||
areas = "Areas",
|
||||
areaEditMode = "Area Edit Mode",
|
||||
-- etc.
|
||||
}
|
||||
|
||||
The phrases defined in these language files can be used with the `L` global function:
|
||||
print(L("areaEditMode"))
|
||||
> Area Edit Mode
|
||||
|
||||
All phrases are formatted with `string.format`, so if you wish to add some info in a phrase you can use standard Lua string
|
||||
formatting arguments:
|
||||
print(L("areaDeleteConfirm", "Test"))
|
||||
> Are you sure you want to delete the area "Test"?
|
||||
|
||||
Phrases are also usable on the server, but only when trying to localize a phrase based on a client's preferences. The server
|
||||
does not have a set language. An example:
|
||||
Entity(1):ChatPrint(L("areaEditMode"))
|
||||
> -- "Area Edit Mode" will print in the player's chatbox
|
||||
]]
|
||||
-- @module ix.lang
|
||||
|
||||
ix.lang = ix.lang or {}
|
||||
ix.lang.stored = ix.lang.stored or {}
|
||||
ix.lang.names = ix.lang.names or {}
|
||||
|
||||
--- Loads language files from a directory.
|
||||
-- @realm shared
|
||||
-- @internal
|
||||
-- @string directory Directory to load language files from
|
||||
function ix.lang.LoadFromDir(directory)
|
||||
for _, v in ipairs(file.Find(directory.."/sh_*.lua", "LUA")) do
|
||||
local niceName = v:sub(4, -5):lower()
|
||||
|
||||
ix.util.Include(directory.."/"..v, "shared")
|
||||
|
||||
if (LANGUAGE) then
|
||||
if (NAME) then
|
||||
ix.lang.names[niceName] = NAME
|
||||
NAME = nil
|
||||
end
|
||||
|
||||
ix.lang.AddTable(niceName, LANGUAGE)
|
||||
LANGUAGE = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Adds phrases to a language. This is used when you aren't adding entries through the files in the `languages/` folder. A
|
||||
-- common use case is adding language phrases in a single-file plugin.
|
||||
-- @realm shared
|
||||
-- @string language The ID of the language
|
||||
-- @tab data Language data to add to the given language
|
||||
-- @usage ix.lang.AddTable("english", {
|
||||
-- myPhrase = "My Phrase"
|
||||
-- })
|
||||
function ix.lang.AddTable(language, data)
|
||||
language = tostring(language):lower()
|
||||
ix.lang.stored[language] = table.Merge(ix.lang.stored[language] or {}, data)
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
-- luacheck: globals L
|
||||
function L(key, arg, ...)
|
||||
local defaultLang = ix.config.language or "english"
|
||||
local languages = ix.lang.stored
|
||||
|
||||
if (IsEntity(arg) and arg:IsPlayer()) then
|
||||
local langKey = ix.option.Get(arg, "language", defaultLang)
|
||||
local info = languages[langKey] or languages[defaultLang]
|
||||
|
||||
return string.format(info and info[key] or languages[defaultLang][key] or languages.english[key] or key, ...)
|
||||
else
|
||||
local info = languages[defaultLang]
|
||||
|
||||
return string.format(info and info[key] or languages[defaultLang][key] or languages.english[key] or key, arg, ...)
|
||||
end
|
||||
end
|
||||
|
||||
-- luacheck: globals L2
|
||||
function L2(key, arg, ...)
|
||||
local defaultLang = ix.config.language or "english"
|
||||
local languages = ix.lang.stored
|
||||
|
||||
if (IsEntity(arg) and arg:IsPlayer()) then
|
||||
local langKey = ix.option.Get(arg, "language", defaultLang)
|
||||
local info = languages[langKey] or languages[defaultLang]
|
||||
|
||||
if (info and info[key]) then
|
||||
return string.format(info[key], ...)
|
||||
end
|
||||
else
|
||||
local info = ix.lang.stored[defaultLang]
|
||||
|
||||
if (info and info[key]) then
|
||||
return string.format(info[key], arg, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
function L(key, ...)
|
||||
local defaultLang = ix.config.language or "english"
|
||||
local languages = ix.lang.stored
|
||||
local langKey = ix.option.Get("language", defaultLang)
|
||||
local info = languages[langKey] or languages[defaultLang]
|
||||
|
||||
return string.format(info and info[key] or languages[defaultLang][key] or languages.english[key] or key, ...)
|
||||
end
|
||||
|
||||
function L2(key, ...)
|
||||
local defaultLang = ix.config.language or "english"
|
||||
local langKey = ix.option.Get("language", defaultLang)
|
||||
local info = ix.lang.stored[langKey]
|
||||
|
||||
if (info and info[key]) then
|
||||
return string.format(info[key], ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
169
gamemodes/helix/gamemode/core/libs/sh_log.lua
Normal file
169
gamemodes/helix/gamemode/core/libs/sh_log.lua
Normal file
@@ -0,0 +1,169 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Logging helper functions.
|
||||
|
||||
Predefined flags:
|
||||
FLAG_NORMAL
|
||||
FLAG_SUCCESS
|
||||
FLAG_WARNING
|
||||
FLAG_DANGER
|
||||
FLAG_SERVER
|
||||
FLAG_DEV
|
||||
]]
|
||||
-- @module ix.log
|
||||
|
||||
-- luacheck: globals FLAG_NORMAL FLAG_SUCCESS FLAG_WARNING FLAG_DANGER FLAG_SERVER FLAG_DEV
|
||||
FLAG_NORMAL = 0
|
||||
FLAG_SUCCESS = 1
|
||||
FLAG_WARNING = 2
|
||||
FLAG_DANGER = 3
|
||||
FLAG_SERVER = 4
|
||||
FLAG_DEV = 5
|
||||
|
||||
ix.log = ix.log or {}
|
||||
ix.log.color = {
|
||||
[FLAG_NORMAL] = Color(200, 200, 200),
|
||||
[FLAG_SUCCESS] = Color(50, 200, 50),
|
||||
[FLAG_WARNING] = Color(255, 255, 0),
|
||||
[FLAG_DANGER] = Color(255, 50, 50),
|
||||
[FLAG_SERVER] = Color(200, 200, 220),
|
||||
[FLAG_DEV] = Color(200, 200, 220),
|
||||
}
|
||||
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = "Helix - Logs",
|
||||
MinAccess = "admin"
|
||||
})
|
||||
|
||||
local consoleColor = Color(50, 200, 50)
|
||||
|
||||
if (SERVER) then
|
||||
if (!ix.db) then
|
||||
include("sv_database.lua")
|
||||
end
|
||||
|
||||
util.AddNetworkString("ixLogStream")
|
||||
|
||||
function ix.log.LoadTables()
|
||||
ix.log.CallHandler("Load")
|
||||
end
|
||||
|
||||
ix.log.types = ix.log.types or {}
|
||||
|
||||
--- Adds a log type
|
||||
-- @realm server
|
||||
-- @string logType Log category
|
||||
-- @string format The string format that log messages should use
|
||||
-- @number flag Log level
|
||||
function ix.log.AddType(logType, format, flag)
|
||||
ix.log.types[logType] = {format = format, flag = flag}
|
||||
end
|
||||
|
||||
function ix.log.Parse(client, logType, ...)
|
||||
local info = ix.log.types[logType]
|
||||
|
||||
if (!info) then
|
||||
ErrorNoHalt("attempted to add entry to non-existent log type \"" .. tostring(logType) .. "\"")
|
||||
return
|
||||
end
|
||||
|
||||
local text = info and info.format
|
||||
|
||||
if (text) then
|
||||
if (isfunction(text)) then
|
||||
text = text(client, ...)
|
||||
end
|
||||
else
|
||||
text = -1
|
||||
end
|
||||
|
||||
return text, info.flag
|
||||
end
|
||||
|
||||
function ix.log.AddRaw(logString, bNoSave)
|
||||
CAMI.GetPlayersWithAccess("Helix - Logs", function(receivers)
|
||||
ix.log.Send(receivers, logString)
|
||||
end)
|
||||
|
||||
Msg("[LOG] ", logString .. "\n")
|
||||
|
||||
if (!bNoSave) then
|
||||
ix.log.CallHandler("Write", nil, logString)
|
||||
end
|
||||
end
|
||||
|
||||
--- Add a log message
|
||||
-- @realm server
|
||||
-- @player client Player who instigated the log
|
||||
-- @string logType Log category
|
||||
-- @param ... Arguments to pass to the log
|
||||
function ix.log.Add(client, logType, ...)
|
||||
local logString, logFlag = ix.log.Parse(client, logType, ...)
|
||||
if (logString == -1) then return end
|
||||
|
||||
CAMI.GetPlayersWithAccess("Helix - Logs", function(receivers)
|
||||
ix.log.Send(receivers, logString, logFlag)
|
||||
end)
|
||||
|
||||
Msg("[LOG] ", logString .. "\n")
|
||||
|
||||
ix.log.CallHandler("Write", client, logString, logFlag, logType, {...})
|
||||
end
|
||||
|
||||
function ix.log.Send(client, logString, flag)
|
||||
net.Start("ixLogStream")
|
||||
net.WriteString(logString)
|
||||
net.WriteUInt(flag or 0, 4)
|
||||
net.Send(client)
|
||||
end
|
||||
|
||||
ix.log.handlers = ix.log.handlers or {}
|
||||
function ix.log.CallHandler(event, ...)
|
||||
for _, v in pairs(ix.log.handlers) do
|
||||
if (isfunction(v[event])) then
|
||||
v[event](...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ix.log.RegisterHandler(name, data)
|
||||
data.name = string.gsub(name, "%s", "")
|
||||
name = name:lower()
|
||||
data.uniqueID = name
|
||||
|
||||
ix.log.handlers[name] = data
|
||||
end
|
||||
|
||||
do
|
||||
local HANDLER = {}
|
||||
|
||||
function HANDLER.Load()
|
||||
file.CreateDir("helix/logs")
|
||||
end
|
||||
|
||||
function HANDLER.Write(client, message)
|
||||
file.Append("helix/logs/" .. os.date("%x"):gsub("/", "-") .. ".txt", "[" .. os.date("%X") .. "]\t" .. message .. "\r\n")
|
||||
end
|
||||
|
||||
ix.log.RegisterHandler("File", HANDLER)
|
||||
end
|
||||
else
|
||||
net.Receive("ixLogStream", function(length)
|
||||
local logString = net.ReadString()
|
||||
local flag = net.ReadUInt(4)
|
||||
|
||||
if (isstring(logString) and isnumber(flag)) then
|
||||
MsgC(consoleColor, "[SERVER] ", ix.log.color[flag], logString .. "\n")
|
||||
end
|
||||
end)
|
||||
end
|
||||
119
gamemodes/helix/gamemode/core/libs/sh_menu.lua
Normal file
119
gamemodes/helix/gamemode/core/libs/sh_menu.lua
Normal file
@@ -0,0 +1,119 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Entity menu manipulation.
|
||||
|
||||
The `menu` library allows you to open up a context menu of arbitrary options whose callbacks will be ran when they are selected
|
||||
from the panel that shows up for the player.
|
||||
]]
|
||||
-- @module ix.menu
|
||||
|
||||
--- You'll need to pass a table of options to `ix.menu.Open` to populate the menu. This table consists of strings as its keys
|
||||
-- and functions as its values. These correspond to the text displayed in the menu and the callback to run, respectively.
|
||||
--
|
||||
-- Example usage:
|
||||
-- ix.menu.Open({
|
||||
-- Drink = function()
|
||||
-- print("Drink option selected!")
|
||||
-- end,
|
||||
-- Take = function()
|
||||
-- print("Take option selected!")
|
||||
-- end
|
||||
-- }, ents.GetByIndex(1))
|
||||
-- This opens a menu with the options `"Drink"` and `"Take"` which will print a message when you click on either of the options.
|
||||
-- @realm client
|
||||
-- @table MenuOptionsStructure
|
||||
|
||||
ix.menu = ix.menu or {}
|
||||
|
||||
if (CLIENT) then
|
||||
--- Opens up a context menu for the given entity.
|
||||
-- @realm client
|
||||
-- @tparam MenuOptionsStructure options Data describing what options to display
|
||||
-- @entity[opt] entity Entity to send commands to
|
||||
-- @treturn boolean Whether or not the menu opened successfully. It will fail when there is already a menu open.
|
||||
function ix.menu.Open(options, entity)
|
||||
if (IsValid(ix.menu.panel)) then
|
||||
return false
|
||||
end
|
||||
|
||||
local panel = vgui.Create("ixEntityMenu")
|
||||
panel:SetEntity(entity)
|
||||
panel:SetOptions(options)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Checks whether or not an entity menu is currently open.
|
||||
-- @realm client
|
||||
-- @treturn boolean Whether or not an entity menu is open
|
||||
function ix.menu.IsOpen()
|
||||
return IsValid(ix.menu.panel)
|
||||
end
|
||||
|
||||
--- Notifies the server of an option that was chosen for the given entity.
|
||||
-- @realm client
|
||||
-- @entity entity Entity to call option on
|
||||
-- @string choice Option that was chosen
|
||||
-- @param data Extra data to send to the entity
|
||||
function ix.menu.NetworkChoice(entity, choice, data)
|
||||
if (IsValid(entity)) then
|
||||
net.Start("ixEntityMenuSelect")
|
||||
net.WriteEntity(entity)
|
||||
net.WriteString(choice)
|
||||
net.WriteType(data)
|
||||
net.SendToServer()
|
||||
end
|
||||
end
|
||||
else
|
||||
util.AddNetworkString("ixEntityMenuSelect")
|
||||
|
||||
net.Receive("ixEntityMenuSelect", function(length, client)
|
||||
local entity = net.ReadEntity()
|
||||
local option = net.ReadString()
|
||||
local data = net.ReadType()
|
||||
|
||||
if (!IsValid(entity) or !isstring(option) or
|
||||
hook.Run("CanPlayerInteractEntity", client, entity, option, data) == false or
|
||||
entity:GetPos():Distance(client:GetPos()) > 96) then
|
||||
return
|
||||
end
|
||||
|
||||
hook.Run("PlayerInteractEntity", client, entity, option, data)
|
||||
|
||||
local callbackName = "OnSelect" .. option:gsub("%s", "")
|
||||
|
||||
if (entity[callbackName]) then
|
||||
entity[callbackName](entity, client, data)
|
||||
else
|
||||
entity:OnOptionSelected(client, option, data)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
do
|
||||
local PLAYER = FindMetaTable("Player")
|
||||
|
||||
if (CLIENT) then
|
||||
function PLAYER:GetEntityMenu()
|
||||
local options = {}
|
||||
|
||||
hook.Run("GetPlayerEntityMenu", self, options)
|
||||
return options
|
||||
end
|
||||
else
|
||||
function PLAYER:OnOptionSelected(client, option)
|
||||
hook.Run("OnPlayerOptionSelected", self, client, option)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
154
gamemodes/helix/gamemode/core/libs/sh_notice.lua
Normal file
154
gamemodes/helix/gamemode/core/libs/sh_notice.lua
Normal file
@@ -0,0 +1,154 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
--- Notification helper functions
|
||||
-- @module ix.notice
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixNotify")
|
||||
util.AddNetworkString("ixNotifyLocalized")
|
||||
|
||||
--- Sends a notification to a specified recipient.
|
||||
-- @realm server
|
||||
-- @string message Message to notify
|
||||
-- @player[opt=nil] recipient Player to be notified
|
||||
function ix.util.Notify(message, recipient)
|
||||
net.Start("ixNotify")
|
||||
net.WriteString(message)
|
||||
|
||||
if (recipient == nil) then
|
||||
net.Broadcast()
|
||||
else
|
||||
net.Send(recipient)
|
||||
end
|
||||
end
|
||||
|
||||
--- Sends a translated notification to a specified recipient.
|
||||
-- @realm server
|
||||
-- @string message Message to notify
|
||||
-- @player[opt=nil] recipient Player to be notified
|
||||
-- @param ... Arguments to pass to the translated message
|
||||
function ix.util.NotifyLocalized(message, recipient, ...)
|
||||
net.Start("ixNotifyLocalized")
|
||||
net.WriteString(message)
|
||||
net.WriteTable({...})
|
||||
|
||||
if (recipient == nil) then
|
||||
net.Broadcast()
|
||||
else
|
||||
net.Send(recipient)
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
--- Notification util functions for players
|
||||
-- @classmod Player
|
||||
|
||||
local playerMeta = FindMetaTable("Player")
|
||||
|
||||
--- Displays a prominent notification in the top-right of this player's screen.
|
||||
-- @realm shared
|
||||
-- @string message Text to display in the notification
|
||||
function playerMeta:Notify(message)
|
||||
ix.util.Notify(message, self)
|
||||
end
|
||||
|
||||
--- Displays a notification for this player with the given language phrase.
|
||||
-- @realm shared
|
||||
-- @string message ID of the phrase to display to the player
|
||||
-- @param ... Arguments to pass to the phrase
|
||||
-- @usage client:NotifyLocalized("mapRestarting", 10)
|
||||
-- -- displays "The map will restart in 10 seconds!" if the player's language is set to English
|
||||
-- @see ix.lang
|
||||
function playerMeta:NotifyLocalized(message, ...)
|
||||
ix.util.NotifyLocalized(message, self, ...)
|
||||
end
|
||||
|
||||
--- Displays a notification for this player in the chatbox.
|
||||
-- @realm shared
|
||||
-- @string message Text to display in the notification
|
||||
function playerMeta:ChatNotify(message)
|
||||
ix.chat.Send(nil, "notice", message, false, {self})
|
||||
end
|
||||
|
||||
--- Displays a notification for this player in the chatbox with the given language phrase.
|
||||
-- @realm shared
|
||||
-- @string message ID of the phrase to display to the player
|
||||
-- @param ... Arguments to pass to the phrase
|
||||
-- @see NotifyLocalized
|
||||
function playerMeta:ChatNotifyLocalized(message, ...)
|
||||
ix.chat.Send(nil, "notice", L(message, self, ...), false, {self})
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Create a notification panel.
|
||||
function ix.util.Notify(message)
|
||||
if (ix.option.Get("chatNotices", false)) then
|
||||
local messageLength = message:utf8len()
|
||||
|
||||
ix.chat.Send(LocalPlayer(), "notice", message, false, {
|
||||
bError = message:utf8sub(messageLength, messageLength) == "!"
|
||||
})
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if (IsValid(ix.gui.notices)) then
|
||||
ix.gui.notices:AddNotice(message)
|
||||
end
|
||||
|
||||
MsgC(Color(0, 255, 255), message .. "\n")
|
||||
end
|
||||
|
||||
-- Creates a translated notification.
|
||||
function ix.util.NotifyLocalized(message, ...)
|
||||
ix.util.Notify(L(message, ...))
|
||||
end
|
||||
|
||||
-- shortcut notify functions
|
||||
do
|
||||
local playerMeta = FindMetaTable("Player")
|
||||
|
||||
function playerMeta:Notify(message)
|
||||
if (self == LocalPlayer()) then
|
||||
ix.util.Notify(message)
|
||||
end
|
||||
end
|
||||
|
||||
function playerMeta:NotifyLocalized(message, ...)
|
||||
if (self == LocalPlayer()) then
|
||||
ix.util.NotifyLocalized(message, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function playerMeta:ChatNotify(message)
|
||||
if (self == LocalPlayer()) then
|
||||
ix.chat.Send(LocalPlayer(), "notice", message)
|
||||
end
|
||||
end
|
||||
|
||||
function playerMeta:ChatNotifyLocalized(message, ...)
|
||||
if (self == LocalPlayer()) then
|
||||
ix.chat.Send(LocalPlayer(), "notice", L(message, ...))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Receives a notification from the server.
|
||||
net.Receive("ixNotify", function()
|
||||
ix.util.Notify(net.ReadString())
|
||||
end)
|
||||
|
||||
-- Receives a notification from the server.
|
||||
net.Receive("ixNotifyLocalized", function()
|
||||
ix.util.NotifyLocalized(net.ReadString(), unpack(net.ReadTable()))
|
||||
end)
|
||||
end
|
||||
381
gamemodes/helix/gamemode/core/libs/sh_option.lua
Normal file
381
gamemodes/helix/gamemode/core/libs/sh_option.lua
Normal file
@@ -0,0 +1,381 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Client-side configuration management.
|
||||
|
||||
The `option` library provides a cleaner way to manage any arbitrary data on the client without the hassle of managing CVars. It
|
||||
is analagous to the `ix.config` library, but it only deals with data that needs to be stored on the client.
|
||||
|
||||
To get started, you'll need to define an option in a client realm so the framework can be aware of its existence. This can be
|
||||
done in the `cl_init.lua` file of your schema, or in an `if (CLIENT) then` statement in the `sh_plugin.lua` file of your plugin:
|
||||
ix.option.Add("headbob", ix.type.bool, true)
|
||||
|
||||
If you need to get the value of an option on the server, you'll need to specify `true` for the `bNetworked` argument in
|
||||
`ix.option.Add`. *NOTE:* You also need to define your option in a *shared* realm, since the server now also needs to be aware
|
||||
of its existence. This makes it so that the client will send that option's value to the server whenever it changes, which then
|
||||
means that the server can now retrieve the value that the client has the option set to. For example, if you need to get what
|
||||
language a client is using, you can simply do the following:
|
||||
ix.option.Get(player.GetByID(1), "language", "english")
|
||||
|
||||
This will return the language of the player, or `"english"` if one isn't found. Note that `"language"` is a networked option
|
||||
that is already defined in the framework, so it will always be available. All options will show up in the options menu on the
|
||||
client, unless `hidden` returns `true` when using `ix.option.Add`.
|
||||
|
||||
Note that the labels for each option in the menu will use a language phrase to show the name. For example, if your option is
|
||||
named `headbob`, then you'll need to define a language phrase called `optHeadbob` that will be used as the option title.
|
||||
]]
|
||||
-- @module ix.option
|
||||
|
||||
ix.option = ix.option or {}
|
||||
ix.option.stored = ix.option.stored or {}
|
||||
ix.option.categories = ix.option.categories or {}
|
||||
|
||||
--- Creates a client-side configuration option with the given information.
|
||||
-- @realm shared
|
||||
-- @string key Unique ID for this option
|
||||
-- @ixtype optionType Type of this option
|
||||
-- @param default Default value that this option will have - this can be nil if needed
|
||||
-- @tparam OptionStructure data Additional settings for this option
|
||||
-- @usage ix.option.Add("animationScale", ix.type.number, 1, {
|
||||
-- category = "appearance",
|
||||
-- min = 0.3,
|
||||
-- max = 2,
|
||||
-- decimals = 1
|
||||
-- })
|
||||
function ix.option.Add(key, optionType, default, data)
|
||||
assert(isstring(key) and key:find("%S"), "expected a non-empty string for the key")
|
||||
|
||||
data = data or {}
|
||||
|
||||
local categories = ix.option.categories
|
||||
local category = data.category or "misc"
|
||||
local upperName = key:sub(1, 1):upper() .. key:sub(2)
|
||||
|
||||
categories[category] = categories[category] or {}
|
||||
categories[category][key] = true
|
||||
|
||||
--- You can specify additional optional arguments for `ix.option.Add` by passing in a table of specific fields as the fourth
|
||||
-- argument.
|
||||
-- @table OptionStructure
|
||||
-- @realm shared
|
||||
-- @field[type=string,opt="opt" .. key] phrase The phrase to use when displaying in the UI. The default value is your option
|
||||
-- key in UpperCamelCase, prefixed with `"opt"`. For example, if your key is `"exampleOption"`, the default phrase will be
|
||||
-- `"optExampleOption"`.
|
||||
-- @field[type=string,opt="optd" .. key] description The phrase to use in the tooltip when hovered in the UI. The default
|
||||
-- value is your option key in UpperCamelCase, prefixed with `"optd"`. For example, if your key is `"exampleOption"`, the
|
||||
-- default phrase will be `"optdExampleOption"`.
|
||||
-- @field[type=string,opt="misc"] category The category that this option should reside in. This is purely for
|
||||
-- aesthetic reasons when displaying the options in the options menu. When displayed in the UI, it will take the form of
|
||||
-- `L("category name")`. This means that you must create a language phrase for the category name - otherwise it will only
|
||||
-- show as the exact string you've specified. If no category is set, it will default to `"misc"`.
|
||||
-- @field[type=number,opt=0] min The minimum allowed amount when setting this option. This field is not
|
||||
-- applicable to any type other than `ix.type.number`.
|
||||
-- @field[type=number,opt=10] max The maximum allowed amount when setting this option. This field is not
|
||||
-- applicable to any type other than `ix.type.number`.
|
||||
-- @field[type=number,opt=0] decimals How many decimals to constrain to when using a number type. This field is not
|
||||
-- applicable to any type other than `ix.type.number`.
|
||||
-- @field[type=boolean,opt=false] bNetworked Whether or not the server should be aware of this option for each client.
|
||||
-- @field[type=function,opt] OnChanged The function to run when this option is changed - this includes whether it was set
|
||||
-- by the player, or through code using `ix.option.Set`.
|
||||
-- OnChanged = function(oldValue, value)
|
||||
-- print("new value is", value)
|
||||
-- end
|
||||
-- @field[type=function,opt] hidden The function to check whether the option should be hidden from the options menu.
|
||||
-- @field[type=function,opt] populate The function to run when the option needs to be added to the menu. This is a required
|
||||
-- field for any array options. It should return a table of entries where the key is the value to set in `ix.option.Set`,
|
||||
-- and the value is the display name for the entry. An example:
|
||||
--
|
||||
-- populate = function()
|
||||
-- return {
|
||||
-- ["english"] = "English",
|
||||
-- ["french"] = "French",
|
||||
-- ["spanish"] = "Spanish"
|
||||
-- }
|
||||
-- end
|
||||
ix.option.stored[key] = {
|
||||
key = key,
|
||||
phrase = "opt" .. upperName,
|
||||
description = "optd" .. upperName,
|
||||
type = optionType,
|
||||
default = default,
|
||||
min = data.min or 0,
|
||||
max = data.max or 10,
|
||||
decimals = data.decimals or 0,
|
||||
category = data.category or "misc",
|
||||
bNetworked = data.bNetworked and true or false,
|
||||
hidden = data.hidden or nil,
|
||||
populate = data.populate or nil,
|
||||
OnChanged = data.OnChanged or nil
|
||||
}
|
||||
end
|
||||
|
||||
--- Loads all saved options from disk.
|
||||
-- @realm shared
|
||||
-- @internal
|
||||
function ix.option.Load()
|
||||
ix.util.Include("helix/gamemode/config/sh_options.lua")
|
||||
|
||||
if (CLIENT) then
|
||||
local options = ix.data.Get("options", nil, true, true)
|
||||
|
||||
if (options) then
|
||||
for k, v in pairs(options) do
|
||||
ix.option.client[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
ix.option.Sync()
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns all of the available options. Note that this does contain the actual values of the options, just their properties.
|
||||
-- @realm shared
|
||||
-- @treturn table Table of all options
|
||||
-- @usage PrintTable(ix.option.GetAll())
|
||||
-- > language:
|
||||
-- > bNetworked = true
|
||||
-- > default = english
|
||||
-- > type = 512
|
||||
-- -- etc.
|
||||
function ix.option.GetAll()
|
||||
return ix.option.stored
|
||||
end
|
||||
|
||||
|
||||
--- Returns all of the available options grouped by their categories. The returned table contains category tables, that contain
|
||||
-- all the options in that category as an array (this is so you can sort them if you'd like).
|
||||
-- @realm shared
|
||||
-- @bool[opt=false] bRemoveHidden Remove entries that are marked as hidden
|
||||
-- @treturn table Table of all options
|
||||
-- @usage PrintTable(ix.option.GetAllByCategories())
|
||||
-- > general:
|
||||
-- > 1:
|
||||
-- > key = language
|
||||
-- > bNetworked = true
|
||||
-- > default = english
|
||||
-- > type = 512
|
||||
-- -- etc.
|
||||
function ix.option.GetAllByCategories(bRemoveHidden)
|
||||
local result = {}
|
||||
|
||||
for k, v in pairs(ix.option.categories) do
|
||||
for k2, _ in pairs(v) do
|
||||
local option = ix.option.stored[k2]
|
||||
|
||||
if (bRemoveHidden and isfunction(option.hidden) and option.hidden()) then
|
||||
continue
|
||||
end
|
||||
|
||||
-- we create the category table here because it could contain all hidden options which makes the table empty
|
||||
result[k] = result[k] or {}
|
||||
result[k][#result[k] + 1] = option
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
if (CLIENT) then
|
||||
ix.option.client = ix.option.client or {}
|
||||
|
||||
--- Sets an option value for the local player.
|
||||
-- This function will error when an invalid key is passed.
|
||||
-- @realm client
|
||||
-- @string key Unique ID of the option
|
||||
-- @param value New value to assign to the option
|
||||
-- @bool[opt=false] bNoSave Whether or not to avoid saving
|
||||
-- @bool[opt=false] bNoNetwork Whether or not to avoid networking regardless of whether the option is networked or not
|
||||
function ix.option.Set(key, value, bNoSave, bNoNetwork)
|
||||
local option = assert(ix.option.stored[key], "invalid option key \"" .. tostring(key) .. "\"")
|
||||
|
||||
if (option.type == ix.type.number) then
|
||||
value = math.Clamp(math.Round(value, option.decimals), option.min, option.max)
|
||||
end
|
||||
|
||||
local oldValue = ix.option.client[key]
|
||||
ix.option.client[key] = value
|
||||
|
||||
if (option.bNetworked and !bNoNetwork) then
|
||||
net.Start("ixOptionSet")
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
if (!bNoSave) then
|
||||
ix.option.Save()
|
||||
end
|
||||
|
||||
if (isfunction(option.OnChanged)) then
|
||||
option.OnChanged(oldValue, value)
|
||||
end
|
||||
end
|
||||
|
||||
net.Receive("ixOptionSet", function()
|
||||
local key = net.ReadString()
|
||||
local value = net.ReadType()
|
||||
local bNoSave = net.ReadBool()
|
||||
|
||||
ix.option.Set(key, value, bNoSave, true)
|
||||
end)
|
||||
|
||||
--- Retrieves an option value for the local player. If it is not set, it'll return the default that you've specified.
|
||||
-- @realm client
|
||||
-- @string key Unique ID of the option
|
||||
-- @param default Default value to return if the option is not set
|
||||
-- @return[1] Value associated with the key
|
||||
-- @return[2] The given default if the option is not set
|
||||
function ix.option.Get(key, default)
|
||||
local option = ix.option.stored[key]
|
||||
|
||||
if (option) then
|
||||
local localValue = ix.option.client[key]
|
||||
|
||||
if (localValue != nil) then
|
||||
return localValue
|
||||
end
|
||||
|
||||
return option.default
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
--- Saves all options to disk.
|
||||
-- @realm client
|
||||
-- @internal
|
||||
function ix.option.Save()
|
||||
ix.data.Set("options", ix.option.client, true, true)
|
||||
end
|
||||
|
||||
--- Syncs all networked options to the server.
|
||||
-- @realm client
|
||||
function ix.option.Sync()
|
||||
local options = {}
|
||||
|
||||
for k, v in pairs(ix.option.stored) do
|
||||
if (v.bNetworked) then
|
||||
options[#options + 1] = {k, ix.option.client[k]}
|
||||
end
|
||||
end
|
||||
|
||||
if (#options > 0) then
|
||||
net.Start("ixOptionSync")
|
||||
net.WriteUInt(#options, 8)
|
||||
|
||||
for _, v in ipairs(options) do
|
||||
net.WriteString(v[1])
|
||||
net.WriteType(v[2])
|
||||
end
|
||||
|
||||
net.SendToServer()
|
||||
end
|
||||
end
|
||||
else
|
||||
util.AddNetworkString("ixOptionSet")
|
||||
util.AddNetworkString("ixOptionSync")
|
||||
|
||||
ix.option.clients = ix.option.clients or {}
|
||||
|
||||
--- Retrieves an option value from the specified player. If it is not set, it'll return the default that you've specified.
|
||||
-- This function will error when an invalid player is passed.
|
||||
-- @realm server
|
||||
-- @player client Player to retrieve option value from
|
||||
-- @string key Unique ID of the option
|
||||
-- @param default Default value to return if the option is not set
|
||||
-- @return[1] Value associated with the key
|
||||
-- @return[2] The given default if the option is not set
|
||||
function ix.option.Get(client, key, default)
|
||||
assert(IsValid(client) and client:IsPlayer(), "expected valid player for argument #1")
|
||||
|
||||
local option = ix.option.stored[key]
|
||||
|
||||
if (option) then
|
||||
local clientOptions = ix.option.clients[client:SteamID64()]
|
||||
|
||||
if (clientOptions) then
|
||||
local clientOption = clientOptions[key]
|
||||
|
||||
if (clientOption != nil) then
|
||||
return clientOption
|
||||
end
|
||||
end
|
||||
|
||||
return option.default
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
function ix.option.Set(client, key, value, bNoSave)
|
||||
local steamID = client:SteamID64()
|
||||
local option = ix.option.stored[key]
|
||||
|
||||
if (option) then
|
||||
if (option.bNetworked) then
|
||||
ix.option.clients[steamID] = ix.option.clients[steamID] or {}
|
||||
ix.option.clients[steamID][key] = value
|
||||
end
|
||||
|
||||
net.Start("ixOptionSet")
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
net.WriteBool(bNoSave)
|
||||
net.Send(client)
|
||||
else
|
||||
ErrorNoHalt(string.format("Attempted to set option with invalid key '%s' for '%s'\n", key, tostring(client) .. client:SteamID()))
|
||||
end
|
||||
end
|
||||
|
||||
-- sent whenever a client's networked option has changed
|
||||
net.Receive("ixOptionSet", function(length, client)
|
||||
local key = net.ReadString()
|
||||
local value = net.ReadType()
|
||||
|
||||
local steamID = client:SteamID64()
|
||||
local option = ix.option.stored[key]
|
||||
|
||||
if (option) then
|
||||
ix.option.clients[steamID] = ix.option.clients[steamID] or {}
|
||||
ix.option.clients[steamID][key] = value
|
||||
else
|
||||
ErrorNoHalt(string.format(
|
||||
"'%s' attempted to set option with invalid key '%s'\n", tostring(client) .. client:SteamID(), key
|
||||
))
|
||||
end
|
||||
end)
|
||||
|
||||
-- sent on first load to sync all networked option values
|
||||
net.Receive("ixOptionSync", function(length, client)
|
||||
local indices = net.ReadUInt(8)
|
||||
local data = {}
|
||||
|
||||
for _ = 1, indices do
|
||||
data[net.ReadString()] = net.ReadType()
|
||||
end
|
||||
|
||||
local steamID = client:SteamID64()
|
||||
ix.option.clients[steamID] = ix.option.clients[steamID] or {}
|
||||
|
||||
for k, v in pairs(data) do
|
||||
local option = ix.option.stored[k]
|
||||
|
||||
if (option) then
|
||||
ix.option.clients[steamID][k] = v
|
||||
else
|
||||
return ErrorNoHalt(string.format(
|
||||
"'%s' attempted to sync option with invalid key '%s'\n", tostring(client) .. client:SteamID(), k
|
||||
))
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
153
gamemodes/helix/gamemode/core/libs/sh_player.lua
Normal file
153
gamemodes/helix/gamemode/core/libs/sh_player.lua
Normal file
@@ -0,0 +1,153 @@
|
||||
--[[
|
||||
| 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 playerMeta = FindMetaTable("Player")
|
||||
|
||||
-- ixData information for the player.
|
||||
do
|
||||
if (SERVER) then
|
||||
function playerMeta:GetData(key, default)
|
||||
if (key == true) then
|
||||
return self.ixData
|
||||
end
|
||||
|
||||
local data = self.ixData and self.ixData[key]
|
||||
|
||||
if (data == nil) then
|
||||
return default
|
||||
else
|
||||
return data
|
||||
end
|
||||
end
|
||||
else
|
||||
function playerMeta:GetData(key, default)
|
||||
local data = ix.localData and ix.localData[key]
|
||||
|
||||
if (data == nil) then
|
||||
return default
|
||||
else
|
||||
return data
|
||||
end
|
||||
end
|
||||
|
||||
net.Receive("ixDataSync", function()
|
||||
ix.localData = net.ReadTable()
|
||||
ix.playTime = net.ReadUInt(32)
|
||||
end)
|
||||
|
||||
net.Receive("ixData", function()
|
||||
ix.localData = ix.localData or {}
|
||||
ix.localData[net.ReadString()] = net.ReadType()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
-- Whitelist networking information here.
|
||||
do
|
||||
function playerMeta:HasWhitelist(faction)
|
||||
local data = ix.faction.indices[faction]
|
||||
|
||||
if (data) then
|
||||
if (data.isDefault) then
|
||||
return true
|
||||
end
|
||||
|
||||
local ixData = self:GetData("whitelists", {})
|
||||
|
||||
return ixData[Schema.folder] and ixData[Schema.folder][data.uniqueID] == true or false
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function playerMeta:GetItems()
|
||||
local char = self:GetCharacter()
|
||||
|
||||
if (char) then
|
||||
local inv = char:GetInventory()
|
||||
|
||||
if (inv) then
|
||||
return inv:GetItems()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function playerMeta:GetClassData()
|
||||
local char = self:GetCharacter()
|
||||
|
||||
if (char) then
|
||||
local class = char:GetClass()
|
||||
|
||||
if (class) then
|
||||
local classData = ix.class.list[class]
|
||||
|
||||
return classData
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("PlayerModelChanged")
|
||||
util.AddNetworkString("PlayerSelectWeapon")
|
||||
|
||||
local entityMeta = FindMetaTable("Entity")
|
||||
|
||||
entityMeta.ixSetModel = entityMeta.ixSetModel or entityMeta.SetModel
|
||||
playerMeta.ixSelectWeapon = playerMeta.ixSelectWeapon or playerMeta.SelectWeapon
|
||||
|
||||
function entityMeta:SetModel(model)
|
||||
local oldModel = self:GetModel()
|
||||
|
||||
if (self:IsPlayer()) then
|
||||
hook.Run("PlayerModelChanged", self, model, oldModel)
|
||||
|
||||
net.Start("PlayerModelChanged")
|
||||
net.WriteEntity(self)
|
||||
net.WriteString(model)
|
||||
net.WriteString(oldModel)
|
||||
net.Broadcast()
|
||||
end
|
||||
|
||||
return self:ixSetModel(model)
|
||||
end
|
||||
|
||||
function playerMeta:SelectWeapon(className)
|
||||
net.Start("PlayerSelectWeapon")
|
||||
net.WriteEntity(self)
|
||||
net.WriteString(className)
|
||||
net.Broadcast()
|
||||
|
||||
return self:ixSelectWeapon(className)
|
||||
end
|
||||
else
|
||||
net.Receive("PlayerModelChanged", function(length)
|
||||
hook.Run("PlayerModelChanged", net.ReadEntity(), net.ReadString(), net.ReadString())
|
||||
end)
|
||||
|
||||
net.Receive("PlayerSelectWeapon", function(length)
|
||||
local client = net.ReadEntity()
|
||||
local className = net.ReadString()
|
||||
|
||||
if (!IsValid(client)) then
|
||||
hook.Run("PlayerWeaponChanged", client, NULL)
|
||||
return
|
||||
end
|
||||
|
||||
for _, v in ipairs(client:GetWeapons()) do
|
||||
if (v:GetClass() == className) then
|
||||
hook.Run("PlayerWeaponChanged", client, v)
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
526
gamemodes/helix/gamemode/core/libs/sh_plugin.lua
Normal file
526
gamemodes/helix/gamemode/core/libs/sh_plugin.lua
Normal file
@@ -0,0 +1,526 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
ix.plugin = ix.plugin or {}
|
||||
ix.plugin.list = ix.plugin.list or {}
|
||||
ix.plugin.unloaded = ix.plugin.unloaded or {}
|
||||
|
||||
ix.util.Include("helix/gamemode/core/meta/sh_tool.lua")
|
||||
|
||||
-- luacheck: globals HOOKS_CACHE
|
||||
HOOKS_CACHE = {}
|
||||
|
||||
local function insertSorted(tbl, plugin, func, priority)
|
||||
if (IX_RELOADED) then
|
||||
-- Clean out the old function from the table
|
||||
for i = 1, #tbl do
|
||||
if (tbl[i][1] == plugin) then
|
||||
table.remove(tbl, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Attempt to insert into an empty table or at the end first
|
||||
if (#tbl == 0 or tbl[#tbl][3] >= priority) then
|
||||
tbl[#tbl + 1] = {plugin, func, priority}
|
||||
return
|
||||
end
|
||||
|
||||
-- Find where to insert
|
||||
for i = #tbl - 1, 1, -1 do
|
||||
if (tbl[i][3] >= priority) then
|
||||
table.insert(tbl, i + 1, {plugin, func, priority})
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Insert at the front
|
||||
table.insert(tbl, 1, {plugin, func, priority})
|
||||
end
|
||||
|
||||
function ix.plugin.Load(uniqueID, path, isSingleFile, variable)
|
||||
if (hook.Run("PluginShouldLoad", uniqueID) == false) then return end
|
||||
|
||||
variable = variable or "PLUGIN"
|
||||
|
||||
-- Plugins within plugins situation?
|
||||
local oldPlugin = PLUGIN
|
||||
local PLUGIN = {
|
||||
folder = path,
|
||||
plugin = oldPlugin,
|
||||
uniqueID = uniqueID,
|
||||
name = "Unknown",
|
||||
description = "Description not available",
|
||||
author = "Anonymous"
|
||||
}
|
||||
|
||||
if (uniqueID == "schema") then
|
||||
if (Schema) then
|
||||
PLUGIN = Schema
|
||||
end
|
||||
|
||||
variable = "Schema"
|
||||
PLUGIN.folder = engine.ActiveGamemode()
|
||||
elseif (ix.plugin.list[uniqueID]) then
|
||||
PLUGIN = ix.plugin.list[uniqueID]
|
||||
end
|
||||
|
||||
_G[variable] = PLUGIN
|
||||
PLUGIN.loading = true
|
||||
|
||||
if (!isSingleFile) then
|
||||
ix.lang.LoadFromDir(path.."/languages")
|
||||
ix.util.IncludeDir(path.."/libs", true)
|
||||
ix.attributes.LoadFromDir(path.."/attributes")
|
||||
ix.faction.LoadFromDir(path.."/factions")
|
||||
ix.class.LoadFromDir(path.."/classes")
|
||||
ix.item.LoadFromDir(path.."/items")
|
||||
ix.plugin.LoadFromDir(path.."/plugins")
|
||||
ix.util.IncludeDir(path.."/derma", true)
|
||||
ix.plugin.LoadEntities(path.."/entities")
|
||||
|
||||
hook.Run("DoPluginIncludes", path, PLUGIN)
|
||||
end
|
||||
|
||||
ix.util.Include(isSingleFile and path or path.."/sh_"..variable:lower()..".lua", "shared")
|
||||
PLUGIN.loading = false
|
||||
|
||||
local uniqueID2 = uniqueID
|
||||
|
||||
if (uniqueID2 == "schema") then
|
||||
uniqueID2 = PLUGIN.name
|
||||
end
|
||||
|
||||
function PLUGIN:SetData(value, global, ignoreMap)
|
||||
ix.data.Set(uniqueID2, value, global, ignoreMap)
|
||||
end
|
||||
|
||||
function PLUGIN:GetData(default, global, ignoreMap, refresh)
|
||||
return ix.data.Get(uniqueID2, default, global, ignoreMap, refresh) or {}
|
||||
end
|
||||
|
||||
hook.Run("PluginLoaded", uniqueID, PLUGIN)
|
||||
|
||||
if (uniqueID != "schema") then
|
||||
PLUGIN.name = PLUGIN.name or "Unknown"
|
||||
PLUGIN.description = PLUGIN.description or "No description available."
|
||||
|
||||
PLUGIN.hookCallPriority = PLUGIN.hookCallPriority or 1000
|
||||
for k, v in pairs(PLUGIN) do
|
||||
if (isfunction(v)) then
|
||||
HOOKS_CACHE[k] = HOOKS_CACHE[k] or {}
|
||||
insertSorted(HOOKS_CACHE[k], PLUGIN, v,
|
||||
(PLUGIN.GetHookCallPriority and PLUGIN:GetHookCallPriority(k))
|
||||
or PLUGIN.hookCallPriority)
|
||||
end
|
||||
end
|
||||
|
||||
ix.plugin.list[uniqueID] = PLUGIN
|
||||
_G[variable] = nil
|
||||
end
|
||||
|
||||
if (PLUGIN.OnLoaded) then
|
||||
PLUGIN:OnLoaded()
|
||||
end
|
||||
end
|
||||
|
||||
function ix.plugin.GetHook(pluginName, hookName)
|
||||
local h = HOOKS_CACHE[hookName]
|
||||
|
||||
if (h) then
|
||||
local p = ix.plugin.list[pluginName]
|
||||
|
||||
if (p) then
|
||||
for _, v in ipairs(h) do
|
||||
if (v[1] == p) then
|
||||
return v[2]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
function ix.plugin.LoadEntities(path)
|
||||
local bLoadedTools
|
||||
local files, folders
|
||||
|
||||
local function IncludeFiles(path2, bClientOnly)
|
||||
if (SERVER and !bClientOnly) then
|
||||
if (file.Exists(path2.."init.lua", "LUA")) then
|
||||
ix.util.Include(path2.."init.lua", "server")
|
||||
elseif (file.Exists(path2.."shared.lua", "LUA")) then
|
||||
ix.util.Include(path2.."shared.lua")
|
||||
end
|
||||
|
||||
if (file.Exists(path2.."cl_init.lua", "LUA")) then
|
||||
ix.util.Include(path2.."cl_init.lua", "client")
|
||||
end
|
||||
elseif (file.Exists(path2.."cl_init.lua", "LUA")) then
|
||||
ix.util.Include(path2.."cl_init.lua", "client")
|
||||
elseif (file.Exists(path2.."shared.lua", "LUA")) then
|
||||
ix.util.Include(path2.."shared.lua")
|
||||
end
|
||||
end
|
||||
|
||||
local function HandleEntityInclusion(folder, variable, register, default, clientOnly, create, complete)
|
||||
files, folders = file.Find(path.."/"..folder.."/*", "LUA")
|
||||
default = default or {}
|
||||
|
||||
for _, v in ipairs(folders) do
|
||||
local path2 = path.."/"..folder.."/"..v.."/"
|
||||
v = ix.util.StripRealmPrefix(v)
|
||||
|
||||
_G[variable] = table.Copy(default)
|
||||
|
||||
if (!isfunction(create)) then
|
||||
_G[variable].ClassName = v
|
||||
else
|
||||
create(v)
|
||||
end
|
||||
|
||||
IncludeFiles(path2, clientOnly)
|
||||
|
||||
if (clientOnly) then
|
||||
if (CLIENT) then
|
||||
register(_G[variable], v)
|
||||
end
|
||||
else
|
||||
register(_G[variable], v)
|
||||
end
|
||||
|
||||
if (isfunction(complete)) then
|
||||
complete(_G[variable])
|
||||
end
|
||||
|
||||
_G[variable] = nil
|
||||
end
|
||||
|
||||
for _, v in ipairs(files) do
|
||||
local niceName = ix.util.StripRealmPrefix(string.StripExtension(v))
|
||||
|
||||
_G[variable] = table.Copy(default)
|
||||
|
||||
if (!isfunction(create)) then
|
||||
_G[variable].ClassName = niceName
|
||||
else
|
||||
create(niceName)
|
||||
end
|
||||
|
||||
ix.util.Include(path.."/"..folder.."/"..v, clientOnly and "client" or "shared")
|
||||
|
||||
if (clientOnly) then
|
||||
if (CLIENT) then
|
||||
register(_G[variable], niceName)
|
||||
end
|
||||
else
|
||||
register(_G[variable], niceName)
|
||||
end
|
||||
|
||||
if (isfunction(complete)) then
|
||||
complete(_G[variable])
|
||||
end
|
||||
|
||||
_G[variable] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function RegisterTool(tool, className)
|
||||
local gmodTool = weapons.GetStored("gmod_tool")
|
||||
|
||||
if (className:sub(1, 3) == "sh_") then
|
||||
className = className:sub(4)
|
||||
end
|
||||
|
||||
if (gmodTool) then
|
||||
gmodTool.Tool[className] = tool
|
||||
else
|
||||
-- this should never happen
|
||||
ErrorNoHalt(string.format("attempted to register tool '%s' with invalid gmod_tool weapon", className))
|
||||
end
|
||||
|
||||
bLoadedTools = true
|
||||
end
|
||||
|
||||
-- Include entities.
|
||||
HandleEntityInclusion("entities", "ENT", scripted_ents.Register, {
|
||||
Type = "anim",
|
||||
Base = "base_gmodentity",
|
||||
Spawnable = true
|
||||
}, false, nil, function(ent)
|
||||
if (SERVER and ent.Holdable == true) then
|
||||
ix.allowedHoldableClasses[ent.ClassName] = true
|
||||
end
|
||||
end)
|
||||
|
||||
-- Include weapons.
|
||||
HandleEntityInclusion("weapons", "SWEP", weapons.Register, {
|
||||
Primary = {},
|
||||
Secondary = {},
|
||||
Base = "weapon_base"
|
||||
})
|
||||
|
||||
HandleEntityInclusion("tools", "TOOL", RegisterTool, {}, false, function(className)
|
||||
if (className:sub(1, 3) == "sh_") then
|
||||
className = className:sub(4)
|
||||
end
|
||||
|
||||
TOOL = ix.meta.tool:Create()
|
||||
TOOL.Mode = className
|
||||
TOOL:CreateConVars()
|
||||
end)
|
||||
|
||||
-- Include effects.
|
||||
HandleEntityInclusion("effects", "EFFECT", effects and effects.Register, nil, true)
|
||||
|
||||
-- only reload spawn menu if any new tools were registered
|
||||
if (CLIENT and bLoadedTools) then
|
||||
RunConsoleCommand("spawnmenu_reload")
|
||||
end
|
||||
end
|
||||
|
||||
function ix.plugin.Initialize()
|
||||
ix.plugin.unloaded = ix.data.Get("unloaded", {}, true, true)
|
||||
|
||||
ix.plugin.LoadFromDir("helix/plugins")
|
||||
|
||||
ix.plugin.Load("schema", engine.ActiveGamemode().."/schema")
|
||||
hook.Run("InitializedSchema")
|
||||
|
||||
ix.plugin.LoadFromDir(engine.ActiveGamemode().."/plugins")
|
||||
hook.Run("InitializedPlugins")
|
||||
end
|
||||
|
||||
function ix.plugin.Get(identifier)
|
||||
return ix.plugin.list[identifier]
|
||||
end
|
||||
|
||||
function ix.plugin.LoadFromDir(directory)
|
||||
local files, folders = file.Find(directory.."/*", "LUA")
|
||||
|
||||
for _, v in ipairs(folders) do
|
||||
ix.plugin.Load(v, directory.."/"..v)
|
||||
end
|
||||
|
||||
for _, v in ipairs(files) do
|
||||
ix.plugin.Load(string.StripExtension(v), directory.."/"..v, true)
|
||||
end
|
||||
end
|
||||
|
||||
function ix.plugin.SetUnloaded(uniqueID, state, bNoSave)
|
||||
local plugin = ix.plugin.list[uniqueID]
|
||||
|
||||
if (state) then
|
||||
if (plugin and plugin.OnUnload) then
|
||||
plugin:OnUnload()
|
||||
end
|
||||
|
||||
ix.plugin.unloaded[uniqueID] = true
|
||||
elseif (ix.plugin.unloaded[uniqueID]) then
|
||||
ix.plugin.unloaded[uniqueID] = nil
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
if (SERVER and !bNoSave) then
|
||||
local status
|
||||
|
||||
if (state) then
|
||||
status = true
|
||||
end
|
||||
|
||||
local unloaded = ix.data.Get("unloaded", {}, true, true)
|
||||
unloaded[uniqueID] = status
|
||||
ix.data.Set("unloaded", unloaded, true, true)
|
||||
end
|
||||
|
||||
if (state) then
|
||||
hook.Run("PluginUnloaded", uniqueID)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
--- Runs the `LoadData` and `PostLoadData` hooks for the gamemode, schema, and plugins. Any plugins that error during the
|
||||
-- hook will have their `SaveData` and `PostLoadData` hooks removed to prevent them from saving junk data.
|
||||
-- @internal
|
||||
-- @realm server
|
||||
function ix.plugin.RunLoadData()
|
||||
local errors = hook.SafeRun("LoadData")
|
||||
|
||||
-- remove the SaveData and PostLoadData hooks for any plugins that error during LoadData since they would probably be
|
||||
-- saving bad data. this doesn't prevent plugins from saving data via other means, but there's only so much we can do
|
||||
for _, v in pairs(errors or {}) do
|
||||
if (v.plugin) then
|
||||
local plugin = ix.plugin.Get(v.plugin)
|
||||
|
||||
if (plugin) then
|
||||
local saveDataHooks = HOOKS_CACHE["SaveData"] or {}
|
||||
saveDataHooks[plugin] = nil
|
||||
|
||||
local postLoadDataHooks = HOOKS_CACHE["PostLoadData"] or {}
|
||||
postLoadDataHooks[plugin] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
hook.Run("PostLoadData")
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
|
||||
-- luacheck: globals hook
|
||||
hook.ixCall = hook.ixCall or hook.Call
|
||||
local hookCall
|
||||
if (CLIENT) then
|
||||
hookCall = function(name, gm, ...)
|
||||
local cache = HOOKS_CACHE[name]
|
||||
|
||||
if (cache) then
|
||||
for i = 1, #cache do
|
||||
local a, b, c, d, e, f = cache[i][2](cache[i][1], ...)
|
||||
if (a != nil) then
|
||||
return a, b, c, d, e, f
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (Schema and Schema[name]) then
|
||||
local a, b, c, d, e, f = Schema[name](Schema, ...)
|
||||
|
||||
if (a != nil) then
|
||||
return a, b, c, d, e, f
|
||||
end
|
||||
end
|
||||
|
||||
--luacheck: ignore global SwiftAC__N
|
||||
if (SwiftAC__N) then
|
||||
SwiftAC__N.hook.Call(name, gm, ...)
|
||||
else
|
||||
return hook.ixCall(name, gm, ...)
|
||||
end
|
||||
end
|
||||
|
||||
--SwiftAC compatibility
|
||||
hook.Run = function(name, ...)
|
||||
return hookCall(name, gmod and gmod.GetGamemode(), ...)
|
||||
end
|
||||
else
|
||||
hookCall = function(name, gm, ...)
|
||||
local cache = HOOKS_CACHE[name]
|
||||
|
||||
if (cache) then
|
||||
for i = 1, #cache do
|
||||
local a, b, c, d, e, f = cache[i][2](cache[i][1], ...)
|
||||
if (a != nil) then
|
||||
return a, b, c, d, e, f
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (Schema and Schema[name]) then
|
||||
local a, b, c, d, e, f = Schema[name](Schema, ...)
|
||||
|
||||
if (a != nil) then
|
||||
return a, b, c, d, e, f
|
||||
end
|
||||
end
|
||||
|
||||
return hook.ixCall(name, gm, ...)
|
||||
end
|
||||
end
|
||||
hook.Call = hookCall
|
||||
|
||||
|
||||
|
||||
--- Runs the given hook in a protected call so that the calling function will continue executing even if any errors occur
|
||||
-- while running the hook. This function is much more expensive to call than `hook.Run`, so you should avoid using it unless
|
||||
-- you absolutely need to avoid errors from stopping the execution of your function.
|
||||
-- @internal
|
||||
-- @realm shared
|
||||
-- @string name Name of the hook to run
|
||||
-- @param ... Arguments to pass to the hook functions
|
||||
-- @treturn[1] table Table of error data if an error occurred while running
|
||||
-- @treturn[1] ... Any arguments returned by the hook functions
|
||||
-- @usage local errors, bCanSpray = hook.SafeRun("PlayerSpray", Entity(1))
|
||||
-- if (!errors) then
|
||||
-- -- do stuff with bCanSpray
|
||||
-- else
|
||||
-- PrintTable(errors)
|
||||
-- end
|
||||
function hook.SafeRun(name, ...)
|
||||
local errors = {}
|
||||
local gm = gmod and gmod.GetGamemode() or nil
|
||||
local cache = HOOKS_CACHE[name]
|
||||
|
||||
if (cache) then
|
||||
for i = 1, #cache do
|
||||
local bSuccess, a, b, c, d, e, f = pcall(cache[i][2], cache[i][1], ...)
|
||||
|
||||
if (bSuccess) then
|
||||
if (a != nil) then
|
||||
return errors, a, b, c, d, e, f
|
||||
end
|
||||
else
|
||||
local plugin = cache[i][1]
|
||||
ErrorNoHalt(string.format("[Helix] hook.SafeRun error for plugin hook \"%s:%s\":\n\t%s\n%s\n",
|
||||
tostring(plugin and plugin.uniqueID or nil), tostring(name), tostring(a), debug.traceback()))
|
||||
|
||||
errors[#errors + 1] = {
|
||||
name = name,
|
||||
plugin = plugin and plugin.uniqueID or nil,
|
||||
errorMessage = tostring(a)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (Schema and Schema[name]) then
|
||||
local bSuccess, a, b, c, d, e, f = pcall(Schema[name], Schema, ...)
|
||||
|
||||
if (bSuccess) then
|
||||
if (a != nil) then
|
||||
return errors, a, b, c, d, e, f
|
||||
end
|
||||
else
|
||||
ErrorNoHalt(string.format("[Helix] hook.SafeRun error for schema hook \"%s\":\n\t%s\n%s\n",
|
||||
tostring(name), tostring(a), debug.traceback()))
|
||||
|
||||
errors[#errors + 1] = {
|
||||
name = name,
|
||||
schema = Schema.name,
|
||||
errorMessage = tostring(a)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local bSuccess, a, b, c, d, e, f = pcall(hook.ixCall, name, gm, ...)
|
||||
|
||||
if (bSuccess) then
|
||||
return errors, a, b, c, d, e, f
|
||||
else
|
||||
ErrorNoHalt(string.format("[Helix] hook.SafeRun error for gamemode hook \"%s\":\n\t%s\n%s\n",
|
||||
tostring(name), tostring(a), debug.traceback()))
|
||||
|
||||
errors[#errors + 1] = {
|
||||
name = name,
|
||||
gamemode = "gamemode",
|
||||
errorMessage = tostring(a)
|
||||
}
|
||||
|
||||
return errors
|
||||
end
|
||||
end
|
||||
end
|
||||
491
gamemodes/helix/gamemode/core/libs/sh_storage.lua
Normal file
491
gamemodes/helix/gamemode/core/libs/sh_storage.lua
Normal file
@@ -0,0 +1,491 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Player manipulation of inventories.
|
||||
|
||||
This library provides an easy way for players to manipulate other inventories. The only functions that you should need are
|
||||
`ix.storage.Open` and `ix.storage.Close`. When opening an inventory as a storage item, it will display both the given inventory
|
||||
and the player's inventory in the player's UI, which allows them to drag items to and from the given inventory.
|
||||
|
||||
Example usage:
|
||||
ix.storage.Open(client, inventory, {
|
||||
name = "Filing Cabinet",
|
||||
entity = ents.GetByIndex(3),
|
||||
bMultipleUsers = true,
|
||||
searchText = "Rummaging...",
|
||||
searchTime = 4
|
||||
})
|
||||
]]
|
||||
-- @module ix.storage
|
||||
|
||||
--- There are some parameters you can customize when opening an inventory as a storage object with `ix.storage.Open`.
|
||||
-- @realm server
|
||||
-- @table StorageInfoStructure
|
||||
-- @field[type=entity] entity Entity to "attach" the inventory to. This is used to provide a location for the inventory for
|
||||
-- things like making sure the player doesn't move too far away from the inventory, etc. This can also be a `player` object.
|
||||
-- @field[type=number,opt=inventory id] id The ID of the nventory. This defaults to the inventory passed into `ix.Storage.Open`.
|
||||
-- @field[type=string,opt="Storage"] name Title to display in the UI when the inventory is open.
|
||||
-- @field[type=boolean,opt=false] bMultipleUsers Whether or not multiple players are allowed to view this inventory at the
|
||||
-- same time.
|
||||
-- @field[type=number,opt=0] searchTime How long the player has to wait before the inventory is opened.
|
||||
-- @field[type=string,opt="@storageSearching"] text Text to display to the user while opening the inventory. If prefixed with
|
||||
-- `"@"`, it will display a language phrase.
|
||||
-- @field[type=function,opt] OnPlayerClose Called when a player who was accessing the inventory has closed it. The
|
||||
-- argument passed to the callback is the player who closed it.
|
||||
-- @field[type=table,opt={}] data Table of arbitrary data to send to the client when the inventory has been opened.
|
||||
|
||||
ix.storage = ix.storage or {}
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixStorageOpen")
|
||||
util.AddNetworkString("ixStorageClose")
|
||||
util.AddNetworkString("ixStorageExpired")
|
||||
util.AddNetworkString("ixStorageMoneyTake")
|
||||
util.AddNetworkString("ixStorageMoneyGive")
|
||||
util.AddNetworkString("ixStorageMoneyUpdate")
|
||||
|
||||
--- Returns whether or not the given inventory has a storage context and is being looked at by other players.
|
||||
-- @realm server
|
||||
-- @inventory inventory Inventory to check
|
||||
-- @treturn bool Whether or not `inventory` is in use
|
||||
function ix.storage.InUse(inventory)
|
||||
if (inventory.storageInfo) then
|
||||
for _, v in pairs(inventory:GetReceivers()) do
|
||||
if (IsValid(v) and v:IsPlayer() and v != inventory.storageInfo.entity) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Returns whether or not an inventory is in use by a specific player.
|
||||
-- @realm server
|
||||
-- @inventory inventory Inventory to check
|
||||
-- @player client Player to check
|
||||
-- @treturn bool Whether or not the player is using the given `inventory`
|
||||
function ix.storage.InUseBy(inventory, client)
|
||||
if (inventory.storageInfo) then
|
||||
for _, v in pairs(inventory:GetReceivers()) do
|
||||
if (IsValid(v) and v:IsPlayer() and v == client) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Creates a storage context on the given inventory.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @inventory inventory Inventory to create a storage context for
|
||||
-- @tab info Information to store on the context
|
||||
function ix.storage.CreateContext(inventory, info)
|
||||
info = info or {}
|
||||
|
||||
info.id = inventory:GetID()
|
||||
info.name = info.name or "Storage"
|
||||
info.entity = assert(IsValid(info.entity), "expected valid entity in info table") and info.entity
|
||||
info.bMultipleUsers = info.bMultipleUsers == nil and false or info.bMultipleUsers
|
||||
info.searchTime = tonumber(info.searchTime) or 0
|
||||
info.searchText = info.searchText or "@storageSearching"
|
||||
info.data = info.data or {}
|
||||
|
||||
inventory.storageInfo = info
|
||||
|
||||
-- remove context from any bags this inventory might have
|
||||
for _, v in pairs(inventory:GetItems()) do
|
||||
if (v.isBag and v:GetInventory()) then
|
||||
ix.storage.CreateContext(v:GetInventory(), table.Copy(info))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Removes a storage context from an inventory if it exists.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @inventory inventory Inventory to remove a storage context from
|
||||
function ix.storage.RemoveContext(inventory)
|
||||
inventory.storageInfo = nil
|
||||
|
||||
-- remove context from any bags this inventory might have
|
||||
for _, v in pairs(inventory:GetItems()) do
|
||||
if (v.isBag and v:GetInventory()) then
|
||||
ix.storage.RemoveContext(v:GetInventory())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Synchronizes an inventory with a storage context to the given client.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @player client Player to sync storage for
|
||||
-- @inventory inventory Inventory to sync storage for
|
||||
function ix.storage.Sync(client, inventory)
|
||||
local info = inventory.storageInfo
|
||||
|
||||
-- we'll retrieve the money value as we're syncing because it may have changed while
|
||||
-- we were waiting for the timer to finish
|
||||
if (info.entity.GetMoney) then
|
||||
info.data.money = info.entity:GetMoney()
|
||||
elseif (info.entity:IsPlayer() and info.entity:GetCharacter()) then
|
||||
info.data.money = info.entity:GetCharacter():GetMoney()
|
||||
end
|
||||
|
||||
-- bags are automatically sync'd when the owning inventory is sync'd
|
||||
inventory:Sync(client)
|
||||
|
||||
net.Start("ixStorageOpen")
|
||||
net.WriteUInt(info.id, 32)
|
||||
net.WriteEntity(info.entity)
|
||||
net.WriteString(info.name)
|
||||
net.WriteTable(info.data)
|
||||
net.Send(client)
|
||||
end
|
||||
|
||||
--- Adds a receiver to a given inventory with a storage context.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @player client Player to sync storage for
|
||||
-- @inventory inventory Inventory to sync storage for
|
||||
-- @bool bDontSync Whether or not to skip syncing the storage to the client. If this is `true`, the storage panel will not
|
||||
-- show up for the player
|
||||
function ix.storage.AddReceiver(client, inventory, bDontSync)
|
||||
local info = inventory.storageInfo
|
||||
|
||||
if (info) then
|
||||
inventory:AddReceiver(client)
|
||||
client.ixOpenStorage = inventory
|
||||
|
||||
-- update receivers for any bags this inventory might have
|
||||
for _, v in pairs(inventory:GetItems()) do
|
||||
if (v.isBag and v:GetInventory()) then
|
||||
v:GetInventory():AddReceiver(client)
|
||||
end
|
||||
end
|
||||
|
||||
if (isfunction(info.OnPlayerOpen)) then
|
||||
info.OnPlayerOpen(client)
|
||||
end
|
||||
|
||||
if (!bDontSync) then
|
||||
ix.storage.Sync(client, inventory)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Removes a storage receiver and removes the context if there are no more receivers.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @player client Player to remove from receivers
|
||||
-- @inventory inventory Inventory with storage context to remove receiver from
|
||||
-- @bool bDontRemove Whether or not to skip removing the storage context if there are no more receivers
|
||||
function ix.storage.RemoveReceiver(client, inventory, bDontRemove)
|
||||
local info = inventory.storageInfo
|
||||
|
||||
if (info) then
|
||||
inventory:RemoveReceiver(client)
|
||||
|
||||
-- update receivers for any bags this inventory might have
|
||||
for _, v in pairs(inventory:GetItems()) do
|
||||
if (v.isBag and v:GetInventory()) then
|
||||
v:GetInventory():RemoveReceiver(client)
|
||||
end
|
||||
end
|
||||
|
||||
if (isfunction(info.OnPlayerClose)) then
|
||||
info.OnPlayerClose(client)
|
||||
end
|
||||
|
||||
if (!bDontRemove and !ix.storage.InUse(inventory)) then
|
||||
ix.storage.RemoveContext(inventory)
|
||||
end
|
||||
|
||||
client.ixOpenStorage = nil
|
||||
|
||||
hook.Run("OnStorageReceiverRemoved", client, inventory)
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Makes a player open an inventory that they can interact with. This can be called multiple times on the same inventory,
|
||||
-- if the info passed allows for multiple users.
|
||||
-- @realm server
|
||||
-- @player client Player to open the inventory for
|
||||
-- @inventory inventory Inventory to open
|
||||
-- @tab info `StorageInfoStructure` describing the storage properties
|
||||
function ix.storage.Open(client, inventory, info)
|
||||
assert(IsValid(client) and client:IsPlayer(), "expected valid player")
|
||||
assert(type(inventory) == "table" and inventory:IsInstanceOf(ix.meta.inventory), "expected valid inventory")
|
||||
|
||||
-- create storage context if one isn't already created
|
||||
if (!inventory.storageInfo) then
|
||||
info = info or {}
|
||||
ix.storage.CreateContext(inventory, info)
|
||||
end
|
||||
|
||||
local storageInfo = inventory.storageInfo
|
||||
|
||||
-- add the client to the list of receivers if we're allowed to have multiple users
|
||||
-- or if nobody else is occupying this inventory, otherwise nag the player
|
||||
if (storageInfo.bMultipleUsers or !ix.storage.InUse(inventory)) then
|
||||
ix.storage.AddReceiver(client, inventory, true)
|
||||
else
|
||||
client:NotifyLocalized("storageInUse")
|
||||
return
|
||||
end
|
||||
|
||||
if (storageInfo.searchTime > 0) then
|
||||
client:SetAction(storageInfo.searchText, storageInfo.searchTime)
|
||||
client:DoStaredAction(storageInfo.entity, function()
|
||||
if (IsValid(client) and IsValid(storageInfo.entity) and inventory.storageInfo) then
|
||||
ix.storage.Sync(client, inventory)
|
||||
end
|
||||
end, storageInfo.searchTime, function()
|
||||
if (IsValid(client)) then
|
||||
ix.storage.RemoveReceiver(client, inventory)
|
||||
client:SetAction()
|
||||
end
|
||||
end)
|
||||
else
|
||||
ix.storage.Sync(client, inventory)
|
||||
end
|
||||
end
|
||||
|
||||
--- Forcefully makes clients close this inventory if they have it open.
|
||||
-- @realm server
|
||||
-- @inventory inventory Inventory to close
|
||||
function ix.storage.Close(inventory)
|
||||
local receivers = inventory:GetReceivers()
|
||||
|
||||
if (#receivers > 0) then
|
||||
net.Start("ixStorageExpired")
|
||||
net.WriteUInt(inventory.storageInfo.id, 32)
|
||||
net.Send(receivers)
|
||||
end
|
||||
|
||||
ix.storage.RemoveContext(inventory)
|
||||
end
|
||||
|
||||
net.Receive("ixStorageClose", function(length, client)
|
||||
local inventory = client.ixOpenStorage
|
||||
|
||||
if (inventory) then
|
||||
ix.storage.RemoveReceiver(client, inventory)
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixStorageMoneyTake", function(length, client)
|
||||
if (CurTime() < (client.ixStorageMoneyTimer or 0)) then
|
||||
return
|
||||
end
|
||||
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if (!character) then
|
||||
return
|
||||
end
|
||||
|
||||
local storageID = net.ReadUInt(32)
|
||||
local amount = net.ReadUInt(32)
|
||||
|
||||
local inventory = client.ixOpenStorage
|
||||
|
||||
if (!inventory or !inventory.storageInfo or storageID != inventory:GetID()) then
|
||||
return
|
||||
end
|
||||
|
||||
local entity = inventory.storageInfo.entity
|
||||
if (hook.Run("CanTakeMoney", client, entity, amount) == false) then
|
||||
client:NotifyLocalized("notAllowed")
|
||||
|
||||
return
|
||||
end
|
||||
local total
|
||||
if (inventory.storageInfo.OnMoneyTake) then
|
||||
amount, total = inventory.storageInfo.OnMoneyTake(client, inventory, amount)
|
||||
|
||||
if (!amount) then
|
||||
return
|
||||
end
|
||||
else
|
||||
if (!IsValid(entity) or
|
||||
(!entity:IsPlayer() and (!isfunction(entity.GetMoney) or !isfunction(entity.SetMoney))) or
|
||||
(entity:IsPlayer() and !entity:GetCharacter())) then
|
||||
return
|
||||
end
|
||||
|
||||
entity = entity:IsPlayer() and entity:GetCharacter() or entity
|
||||
amount = math.Clamp(math.Round(tonumber(amount) or 0), 0, entity:GetMoney())
|
||||
|
||||
if (amount == 0) then
|
||||
return
|
||||
end
|
||||
|
||||
character:SetMoney(character:GetMoney() + amount)
|
||||
|
||||
total = entity:GetMoney() - amount
|
||||
entity:SetMoney(total)
|
||||
|
||||
ix.log.Add(client, "storageMoneyTake", entity, amount, total)
|
||||
end
|
||||
|
||||
net.Start("ixStorageMoneyUpdate")
|
||||
net.WriteUInt(storageID, 32)
|
||||
net.WriteUInt(total, 32)
|
||||
net.Send(inventory:GetReceivers())
|
||||
|
||||
client.ixStorageMoneyTimer = CurTime() + 0.5
|
||||
end)
|
||||
|
||||
net.Receive("ixStorageMoneyGive", function(length, client)
|
||||
if (CurTime() < (client.ixStorageMoneyTimer or 0)) then
|
||||
return
|
||||
end
|
||||
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if (!character) then
|
||||
return
|
||||
end
|
||||
|
||||
local storageID = net.ReadUInt(32)
|
||||
local amount = net.ReadUInt(32)
|
||||
|
||||
local inventory = client.ixOpenStorage
|
||||
|
||||
if (!inventory or !inventory.storageInfo or storageID != inventory:GetID()) then
|
||||
return
|
||||
end
|
||||
|
||||
local entity = inventory.storageInfo.entity
|
||||
if (hook.Run("CanGiveMoney", client, entity, amount) == false) then
|
||||
client:NotifyLocalized("notAllowed")
|
||||
|
||||
return
|
||||
end
|
||||
local total
|
||||
if (inventory.storageInfo.OnMoneyGive) then
|
||||
amount, total = inventory.storageInfo.OnMoneyGive(client, inventory, amount)
|
||||
|
||||
if (!amount) then
|
||||
return
|
||||
end
|
||||
else
|
||||
if (!IsValid(entity) or
|
||||
(!entity:IsPlayer() and (!isfunction(entity.GetMoney) or !isfunction(entity.SetMoney))) or
|
||||
(entity:IsPlayer() and !entity:GetCharacter())) then
|
||||
return
|
||||
end
|
||||
|
||||
entity = entity:IsPlayer() and entity:GetCharacter() or entity
|
||||
amount = math.Clamp(math.Round(tonumber(amount) or 0), 0, character:GetMoney())
|
||||
|
||||
if (amount == 0) then
|
||||
return
|
||||
end
|
||||
|
||||
character:SetMoney(character:GetMoney() - amount)
|
||||
|
||||
total = entity:GetMoney() + amount
|
||||
entity:SetMoney(total)
|
||||
|
||||
ix.log.Add(client, "storageMoneyGive", entity, amount, total)
|
||||
end
|
||||
|
||||
net.Start("ixStorageMoneyUpdate")
|
||||
net.WriteUInt(storageID, 32)
|
||||
net.WriteUInt(total, 32)
|
||||
net.Send(inventory:GetReceivers())
|
||||
|
||||
client.ixStorageMoneyTimer = CurTime() + 0.5
|
||||
end)
|
||||
else
|
||||
net.Receive("ixStorageOpen", function()
|
||||
if (IsValid(ix.gui.menu)) then
|
||||
net.Start("ixStorageClose")
|
||||
net.SendToServer()
|
||||
return
|
||||
end
|
||||
|
||||
local id = net.ReadUInt(32)
|
||||
local entity = net.ReadEntity()
|
||||
local name = net.ReadString()
|
||||
local data = net.ReadTable()
|
||||
|
||||
local inventory = ix.item.inventories[id]
|
||||
|
||||
if (IsValid(entity) and inventory and inventory.slots) then
|
||||
local char = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter()
|
||||
local localInventory = char and char:GetInventory()
|
||||
local equipInvID = char and char.GetEquipInventory and char:GetEquipInventory()
|
||||
local equipInv = equipInvID and ix.item.inventories[equipInvID]
|
||||
|
||||
local panel = vgui.Create("ixStorageView")
|
||||
panel:CreateContents(inventory, entity)
|
||||
|
||||
if (localInventory) then
|
||||
panel:SetLocalInventory(localInventory)
|
||||
end
|
||||
|
||||
if (equipInv) then
|
||||
panel:SetEquipInv(equipInv)
|
||||
end
|
||||
|
||||
panel:SetStorageID(id)
|
||||
panel:SetStorageTitle(name)
|
||||
panel:SetStorageInventory(inventory)
|
||||
|
||||
if (data.money) then
|
||||
if (localInventory) then
|
||||
panel:SetLocalMoney(LocalPlayer():GetCharacter():GetMoney())
|
||||
end
|
||||
|
||||
panel:SetStorageMoney(data.money)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixStorageExpired", function()
|
||||
if (IsValid(ix.gui.openedStorage)) then
|
||||
ix.gui.openedStorage:Remove()
|
||||
end
|
||||
|
||||
local id = net.ReadUInt(32)
|
||||
|
||||
if (id != 0) then
|
||||
ix.item.inventories[id] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixStorageMoneyUpdate", function()
|
||||
local storageID = net.ReadUInt(32)
|
||||
local amount = net.ReadUInt(32)
|
||||
|
||||
local panel = ix.gui.openedStorage
|
||||
|
||||
if (!IsValid(panel) or panel:GetStorageID() != storageID) then
|
||||
return
|
||||
end
|
||||
|
||||
panel:SetStorageMoney(amount)
|
||||
panel:SetLocalMoney(LocalPlayer():GetCharacter():GetMoney())
|
||||
end)
|
||||
end
|
||||
204
gamemodes/helix/gamemode/core/libs/sv_database.lua
Normal file
204
gamemodes/helix/gamemode/core/libs/sv_database.lua
Normal file
@@ -0,0 +1,204 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
ix.db = ix.db or {
|
||||
schema = {},
|
||||
schemaQueue = {},
|
||||
type = {
|
||||
-- TODO: more specific types, lengths, and defaults
|
||||
-- i.e INT(11) UNSIGNED, SMALLINT(4), LONGTEXT, VARCHAR(350), NOT NULL, DEFAULT NULL, etc
|
||||
[ix.type.string] = "VARCHAR(255)",
|
||||
[ix.type.text] = "TEXT",
|
||||
[ix.type.number] = "INT(11)",
|
||||
[ix.type.steamid] = "VARCHAR(20)",
|
||||
[ix.type.bool] = "TINYINT(1)"
|
||||
}
|
||||
}
|
||||
|
||||
ix.db.config = ix.config.server.database or {}
|
||||
|
||||
function ix.db.Connect()
|
||||
ix.db.config.adapter = ix.db.config.adapter or "sqlite"
|
||||
|
||||
local dbmodule = ix.db.config.adapter
|
||||
local hostname = ix.db.config.hostname
|
||||
local username = ix.db.config.username
|
||||
local password = ix.db.config.password
|
||||
local database = ix.db.config.database
|
||||
local port = ix.db.config.port
|
||||
|
||||
mysql:SetModule(dbmodule)
|
||||
mysql:Connect(hostname, username, password, database, port)
|
||||
end
|
||||
|
||||
function ix.db.AddToSchema(schemaType, field, fieldType)
|
||||
if (!ix.db.type[fieldType]) then
|
||||
error(string.format("attempted to add field in schema with invalid type '%s'", fieldType))
|
||||
return
|
||||
end
|
||||
|
||||
if (!mysql:IsConnected() or !ix.db.schema[schemaType]) then
|
||||
ix.db.schemaQueue[#ix.db.schemaQueue + 1] = {schemaType, field, fieldType}
|
||||
return
|
||||
end
|
||||
|
||||
ix.db.InsertSchema(schemaType, field, fieldType)
|
||||
end
|
||||
|
||||
-- this is only ever used internally
|
||||
function ix.db.InsertSchema(schemaType, field, fieldType)
|
||||
local schema = ix.db.schema[schemaType]
|
||||
|
||||
if (!schema) then
|
||||
error(string.format("attempted to insert into schema with invalid schema type '%s'", schemaType))
|
||||
return
|
||||
end
|
||||
|
||||
if (!schema[field]) then
|
||||
schema[field] = true
|
||||
|
||||
local query = mysql:Update("ix_schema")
|
||||
query:Update("columns", util.TableToJSON(schema))
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Alter(schemaType)
|
||||
query:Add(field, ix.db.type[fieldType])
|
||||
query:Execute()
|
||||
end
|
||||
end
|
||||
|
||||
function ix.db.LoadTables()
|
||||
local query
|
||||
|
||||
query = mysql:Create("ix_schema")
|
||||
query:Create("table", "VARCHAR(64) NOT NULL")
|
||||
query:Create("columns", "TEXT NOT NULL")
|
||||
query:PrimaryKey("table")
|
||||
query:Execute()
|
||||
|
||||
-- table structure will be populated with more fields when vars
|
||||
-- are registered using ix.char.RegisterVar
|
||||
query = mysql:Create("ix_characters")
|
||||
query:Create("id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT")
|
||||
query:PrimaryKey("id")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Create("ix_characters_data")
|
||||
query:Create("id", "INT(11) UNSIGNED NOT NULL")
|
||||
query:Create("key", "VARCHAR(60) NOT NULL")
|
||||
query:Create("data", "TEXT DEFAULT NULL")
|
||||
query:PrimaryKey("id`,`key")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Create("ix_inventories")
|
||||
query:Create("inventory_id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT")
|
||||
query:Create("character_id", "INT(11) UNSIGNED NOT NULL")
|
||||
query:Create("inventory_type", "VARCHAR(150) DEFAULT NULL")
|
||||
query:PrimaryKey("inventory_id")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Create("ix_items")
|
||||
query:Create("item_id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT")
|
||||
query:Create("inventory_id", "INT(11) UNSIGNED NOT NULL")
|
||||
query:Create("unique_id", "VARCHAR(60) NOT NULL")
|
||||
query:Create("character_id", "INT(11) UNSIGNED DEFAULT NULL")
|
||||
query:Create("player_id", "VARCHAR(20) DEFAULT NULL")
|
||||
query:Create("data", "TEXT DEFAULT NULL")
|
||||
query:Create("x", "SMALLINT(4) NOT NULL")
|
||||
query:Create("y", "SMALLINT(4) NOT NULL")
|
||||
query:PrimaryKey("item_id")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Create("ix_players")
|
||||
query:Create("steamid", "VARCHAR(20) NOT NULL")
|
||||
query:Create("steam_name", "VARCHAR(32) NOT NULL")
|
||||
query:Create("play_time", "INT(11) UNSIGNED DEFAULT NULL")
|
||||
query:Create("address", "VARCHAR(15) DEFAULT NULL")
|
||||
query:Create("last_join_time", "INT(11) UNSIGNED DEFAULT NULL")
|
||||
query:Create("data", "TEXT")
|
||||
query:PrimaryKey("steamid")
|
||||
query:Execute()
|
||||
|
||||
-- populate schema table if rows don't exist
|
||||
query = mysql:InsertIgnore("ix_schema")
|
||||
query:Insert("table", "ix_characters")
|
||||
query:Insert("columns", util.TableToJSON({}))
|
||||
query:Execute()
|
||||
|
||||
-- load schema from database
|
||||
query = mysql:Select("ix_schema")
|
||||
query:Callback(function(result)
|
||||
if (!istable(result)) then
|
||||
return
|
||||
end
|
||||
|
||||
for _, v in pairs(result) do
|
||||
ix.db.schema[v.table] = util.JSONToTable(v.columns)
|
||||
end
|
||||
|
||||
-- update schema if needed
|
||||
for i = 1, #ix.db.schemaQueue do
|
||||
local entry = ix.db.schemaQueue[i]
|
||||
ix.db.InsertSchema(entry[1], entry[2], entry[3])
|
||||
end
|
||||
end)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
function ix.db.WipeTables(callback)
|
||||
local query
|
||||
|
||||
query = mysql:Drop("ix_schema")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Drop("ix_characters")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Drop("ix_inventories")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Drop("ix_items")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Drop("ix_players")
|
||||
query:Callback(callback)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
hook.Add("InitPostEntity", "ixDatabaseConnect", function()
|
||||
-- Connect to the database using SQLite, mysqoo, or tmysql4.
|
||||
ix.db.Connect()
|
||||
end)
|
||||
|
||||
local resetCalled = 0
|
||||
|
||||
concommand.Add("ix_wipedb", function(client, cmd, arguments)
|
||||
-- can only be ran through the server's console
|
||||
if (!IsValid(client)) then
|
||||
if (resetCalled < RealTime()) then
|
||||
resetCalled = RealTime() + 3
|
||||
|
||||
MsgC(Color(255, 0, 0),
|
||||
"[Helix] WIPING THE DATABASE WILL PERMENANTLY REMOVE ALL PLAYER, CHARACTER, ITEM, AND INVENTORY DATA.\n")
|
||||
MsgC(Color(255, 0, 0), "[Helix] THE SERVER WILL RESTART TO APPLY THESE CHANGES WHEN COMPLETED.\n")
|
||||
MsgC(Color(255, 0, 0), "[Helix] TO CONFIRM DATABASE RESET, RUN 'ix_wipedb' AGAIN WITHIN 3 SECONDS.\n")
|
||||
else
|
||||
resetCalled = 0
|
||||
MsgC(Color(255, 0, 0), "[Helix] DATABASE WIPE IN PROGRESS...\n")
|
||||
|
||||
hook.Run("OnWipeTables")
|
||||
ix.db.WipeTables(function()
|
||||
MsgC(Color(255, 255, 0), "[Helix] DATABASE WIPE COMPLETED!\n")
|
||||
RunConsoleCommand("changelevel", game.GetMap())
|
||||
end)
|
||||
end
|
||||
end
|
||||
end)
|
||||
245
gamemodes/helix/gamemode/core/libs/sv_networking.lua
Normal file
245
gamemodes/helix/gamemode/core/libs/sv_networking.lua
Normal file
@@ -0,0 +1,245 @@
|
||||
--[[
|
||||
| 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 ix.net
|
||||
|
||||
local entityMeta = FindMetaTable("Entity")
|
||||
local playerMeta = FindMetaTable("Player")
|
||||
|
||||
ix.net = ix.net or {}
|
||||
ix.net.list = ix.net.list or {}
|
||||
ix.net.locals = ix.net.locals or {}
|
||||
ix.net.globals = ix.net.globals or {}
|
||||
|
||||
util.AddNetworkString("ixGlobalVarSet")
|
||||
util.AddNetworkString("ixLocalVarSet")
|
||||
util.AddNetworkString("ixNetVarSet")
|
||||
util.AddNetworkString("ixNetStatics")
|
||||
util.AddNetworkString("ixNetVarDelete")
|
||||
|
||||
-- Check if there is an attempt to send a function. Can't send those.
|
||||
local function CheckBadType(name, object)
|
||||
if (isfunction(object)) then
|
||||
ErrorNoHalt("Net var '" .. name .. "' contains a bad object type!")
|
||||
|
||||
return true
|
||||
elseif (istable(object)) then
|
||||
for k, v in pairs(object) do
|
||||
-- Check both the key and the value for tables, and has recursion.
|
||||
if (CheckBadType(name, k) or CheckBadType(name, v)) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function GetNetVar(key, default) -- luacheck: globals GetNetVar
|
||||
local value = ix.net.globals[key]
|
||||
|
||||
return value != nil and value or default
|
||||
end
|
||||
|
||||
function SetNetVar(key, value, receiver) -- luacheck: globals SetNetVar
|
||||
if (CheckBadType(key, value)) then return end
|
||||
if (GetNetVar(key) == value) then return end
|
||||
|
||||
ix.net.globals[key] = value
|
||||
|
||||
net.Start("ixGlobalVarSet")
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
|
||||
if (receiver == nil) then
|
||||
net.Broadcast()
|
||||
else
|
||||
net.Send(receiver)
|
||||
end
|
||||
end
|
||||
|
||||
--- Player networked variable functions
|
||||
-- @classmod Player
|
||||
|
||||
--- Synchronizes networked variables to the client.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
local division = 100
|
||||
function playerMeta:SyncVars()
|
||||
for k, v in pairs(ix.net.globals) do
|
||||
net.Start("ixGlobalVarSet")
|
||||
net.WriteString(k)
|
||||
net.WriteType(v)
|
||||
net.Send(self)
|
||||
end
|
||||
|
||||
for k, v in pairs(ix.net.locals[self] or {}) do
|
||||
net.Start("ixLocalVarSet")
|
||||
net.WriteString(k)
|
||||
net.WriteType(v)
|
||||
net.Send(self)
|
||||
end
|
||||
|
||||
local statics = {}
|
||||
local toWrite = {}
|
||||
for entity, data in pairs(ix.net.list) do
|
||||
if (IsValid(entity)) then
|
||||
local index = entity:EntIndex()
|
||||
|
||||
if (table.Count(data) == 1 and data.Persistent == true) then
|
||||
statics[#statics + 1] = index
|
||||
continue
|
||||
end
|
||||
|
||||
for k, v in pairs(data) do
|
||||
toWrite[#toWrite + 1] = {index, k, v}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i = 0, math.floor(#toWrite / division) do
|
||||
timer.Simple(i * 0.5, function()
|
||||
if (!IsValid(self)) then return end
|
||||
|
||||
for j = 0, division - 1 do
|
||||
if (!toWrite[i * division + j]) then continue end
|
||||
net.Start("ixNetVarSet")
|
||||
net.WriteUInt(toWrite[i * division + j][1], 16)
|
||||
net.WriteString(toWrite[i * division + j][2])
|
||||
net.WriteType(toWrite[i * division + j][3])
|
||||
net.Send(self)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
timer.Simple(30, function()
|
||||
if (!IsValid(self)) then return end
|
||||
|
||||
net.Start("ixNetStatics")
|
||||
net.WriteUInt(#statics, 16)
|
||||
for _, v in ipairs(statics) do
|
||||
local entity = Entity(v)
|
||||
if (IsValid(entity) and ix.net.list[entity] and ix.net.list[entity].Persistent == true) then
|
||||
net.WriteUInt(v, 16)
|
||||
else
|
||||
net.WriteUInt(0, 16)
|
||||
end
|
||||
end
|
||||
net.Send(self)
|
||||
end)
|
||||
end
|
||||
|
||||
--- Retrieves a local networked variable. If it is not set, it'll return the default that you've specified.
|
||||
-- Locally networked variables can only be retrieved from the owning player when used from the client.
|
||||
-- @realm shared
|
||||
-- @string key Identifier of the local variable
|
||||
-- @param default Default value to return if the local variable is not set
|
||||
-- @return Value associated with the key, or the default that was given if it doesn't exist
|
||||
-- @usage print(client:GetLocalVar("secret"))
|
||||
-- > 12345678
|
||||
-- @see SetLocalVar
|
||||
function playerMeta:GetLocalVar(key, default)
|
||||
if (ix.net.locals[self] and ix.net.locals[self][key] != nil) then
|
||||
return ix.net.locals[self][key]
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
--- Sets the value of a local networked variable.
|
||||
-- @realm server
|
||||
-- @string key Identifier of the local variable
|
||||
-- @param value New value to assign to the local variable
|
||||
-- @usage client:SetLocalVar("secret", 12345678)
|
||||
-- @see GetLocalVar
|
||||
function playerMeta:SetLocalVar(key, value)
|
||||
if (CheckBadType(key, value)) then return end
|
||||
|
||||
ix.net.locals[self] = ix.net.locals[self] or {}
|
||||
ix.net.locals[self][key] = value
|
||||
|
||||
net.Start("ixLocalVarSet")
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
net.Send(self)
|
||||
end
|
||||
|
||||
--- Entity networked variable functions
|
||||
-- @classmod Entity
|
||||
|
||||
--- Retrieves a networked variable. If it is not set, it'll return the default that you've specified.
|
||||
-- @realm shared
|
||||
-- @string key Identifier of the networked variable
|
||||
-- @param default Default value to return if the networked variable is not set
|
||||
-- @return Value associated with the key, or the default that was given if it doesn't exist
|
||||
-- @usage print(client:GetNetVar("example"))
|
||||
-- > Hello World!
|
||||
-- @see SetNetVar
|
||||
function entityMeta:GetNetVar(key, default)
|
||||
if (ix.net.list[self] and ix.net.list[self][key] != nil) then
|
||||
return ix.net.list[self][key]
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
--- Sets the value of a networked variable.
|
||||
-- @realm server
|
||||
-- @string key Identifier of the networked variable
|
||||
-- @param value New value to assign to the networked variable
|
||||
-- @tab[opt=nil] receiver The players to send the networked variable to
|
||||
-- @usage client:SetNetVar("example", "Hello World!")
|
||||
-- @see GetNetVar
|
||||
function entityMeta:SetNetVar(key, value, receiver)
|
||||
if (CheckBadType(key, value)) then return end
|
||||
|
||||
ix.net.list[self] = ix.net.list[self] or {}
|
||||
|
||||
if (ix.net.list[self][key] != value) then
|
||||
ix.net.list[self][key] = value
|
||||
end
|
||||
|
||||
self:SendNetVar(key, receiver)
|
||||
end
|
||||
|
||||
--- Sends a networked variable.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @string key Identifier of the networked variable
|
||||
-- @tab[opt=nil] receiver The players to send the networked variable to
|
||||
function entityMeta:SendNetVar(key, receiver)
|
||||
net.Start("ixNetVarSet")
|
||||
net.WriteUInt(self:EntIndex(), 16)
|
||||
net.WriteString(key)
|
||||
net.WriteType(ix.net.list[self] and ix.net.list[self][key])
|
||||
|
||||
if (receiver == nil) then
|
||||
net.Broadcast()
|
||||
else
|
||||
net.Send(receiver)
|
||||
end
|
||||
end
|
||||
|
||||
--- Clears all of the networked variables.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @tab[opt=nil] receiver The players to clear the networked variable for
|
||||
function entityMeta:ClearNetVars(receiver)
|
||||
ix.net.list[self] = nil
|
||||
ix.net.locals[self] = nil
|
||||
|
||||
net.Start("ixNetVarDelete")
|
||||
net.WriteUInt(self:EntIndex(), 16)
|
||||
|
||||
if (receiver == nil) then
|
||||
net.Broadcast()
|
||||
else
|
||||
net.Send(receiver)
|
||||
end
|
||||
end
|
||||
125
gamemodes/helix/gamemode/core/libs/sv_player.lua
Normal file
125
gamemodes/helix/gamemode/core/libs/sv_player.lua
Normal file
@@ -0,0 +1,125 @@
|
||||
--[[
|
||||
| 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 playerMeta = FindMetaTable("Player")
|
||||
|
||||
-- Player data (outside of characters) handling.
|
||||
do
|
||||
util.AddNetworkString("ixData")
|
||||
util.AddNetworkString("ixDataSync")
|
||||
|
||||
function playerMeta:LoadData(callback)
|
||||
local name = self:SteamName()
|
||||
local steamID64 = self:SteamID64()
|
||||
local timestamp = math.floor(os.time())
|
||||
local ip = self:IPAddress():match("%d+%.%d+%.%d+%.%d+")
|
||||
|
||||
local query = mysql:Select("ix_players")
|
||||
query:Select("data")
|
||||
query:Select("play_time")
|
||||
query:Where("steamid", steamID64)
|
||||
query:Callback(function(result)
|
||||
if (IsValid(self) and istable(result) and #result > 0 and result[1].data) then
|
||||
local updateQuery = mysql:Update("ix_players")
|
||||
updateQuery:Update("last_join_time", timestamp)
|
||||
updateQuery:Update("address", ip)
|
||||
updateQuery:Where("steamid", steamID64)
|
||||
updateQuery:Execute()
|
||||
|
||||
self.ixPlayTime = tonumber(result[1].play_time) or 0
|
||||
self.ixData = util.JSONToTable(result[1].data)
|
||||
|
||||
hook.Run("PlayerDataRestored", self)
|
||||
|
||||
if (callback) then
|
||||
callback(self.ixData)
|
||||
end
|
||||
else
|
||||
local insertQuery = mysql:Insert("ix_players")
|
||||
insertQuery:Insert("steamid", steamID64)
|
||||
insertQuery:Insert("steam_name", name)
|
||||
insertQuery:Insert("play_time", 0)
|
||||
insertQuery:Insert("address", ip)
|
||||
insertQuery:Insert("last_join_time", timestamp)
|
||||
insertQuery:Insert("data", util.TableToJSON({}))
|
||||
insertQuery:Execute()
|
||||
|
||||
hook.Run("PlayerDataRestored", self)
|
||||
|
||||
if (callback) then
|
||||
callback({})
|
||||
end
|
||||
end
|
||||
end)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
function playerMeta:SaveData()
|
||||
local name = self:SteamName()
|
||||
local steamID64 = self:SteamID64()
|
||||
|
||||
local query = mysql:Update("ix_players")
|
||||
query:Update("steam_name", name)
|
||||
query:Update("play_time", math.floor((self.ixPlayTime or 0) + (RealTime() - (self.ixJoinTime or RealTime() - 1))))
|
||||
query:Update("data", util.TableToJSON(self.ixData))
|
||||
query:Where("steamid", steamID64)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
function playerMeta:SetData(key, value, bNoNetworking)
|
||||
self.ixData = self.ixData or {}
|
||||
self.ixData[key] = value
|
||||
|
||||
if (!bNoNetworking) then
|
||||
net.Start("ixData")
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
net.Send(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Whitelisting information for the player.
|
||||
do
|
||||
function playerMeta:SetWhitelisted(faction, whitelisted)
|
||||
if (!whitelisted) then
|
||||
whitelisted = nil
|
||||
end
|
||||
|
||||
local data = ix.faction.indices[faction]
|
||||
|
||||
if (data) then
|
||||
local whitelists = self:GetData("whitelists", {})
|
||||
whitelists[Schema.folder] = whitelists[Schema.folder] or {}
|
||||
whitelists[Schema.folder][data.uniqueID] = whitelisted and true or nil
|
||||
|
||||
self:SetData("whitelists", whitelists)
|
||||
self:SaveData()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
playerMeta.ixGive = playerMeta.ixGive or playerMeta.Give
|
||||
|
||||
function playerMeta:Give(className, bNoAmmo)
|
||||
local weapon
|
||||
|
||||
self.ixWeaponGive = true
|
||||
weapon = self:ixGive(className, bNoAmmo)
|
||||
self.ixWeaponGive = nil
|
||||
|
||||
return weapon
|
||||
end
|
||||
end
|
||||
362
gamemodes/helix/gamemode/core/libs/thirdparty/cl_ikon.lua
vendored
Normal file
362
gamemodes/helix/gamemode/core/libs/thirdparty/cl_ikon.lua
vendored
Normal 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
|
||||
1870
gamemodes/helix/gamemode/core/libs/thirdparty/data/sh_utf8_casemap.lua
vendored
Normal file
1870
gamemodes/helix/gamemode/core/libs/thirdparty/data/sh_utf8_casemap.lua
vendored
Normal file
File diff suppressed because it is too large
Load Diff
560
gamemodes/helix/gamemode/core/libs/thirdparty/sh_cami.lua
vendored
Normal file
560
gamemodes/helix/gamemode/core/libs/thirdparty/sh_cami.lua
vendored
Normal 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
|
||||
791
gamemodes/helix/gamemode/core/libs/thirdparty/sh_date.lua
vendored
Normal file
791
gamemodes/helix/gamemode/core/libs/thirdparty/sh_date.lua
vendored
Normal 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
|
||||
193
gamemodes/helix/gamemode/core/libs/thirdparty/sh_middleclass.lua
vendored
Normal file
193
gamemodes/helix/gamemode/core/libs/thirdparty/sh_middleclass.lua
vendored
Normal 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
|
||||
174
gamemodes/helix/gamemode/core/libs/thirdparty/sh_netstream2.lua
vendored
Normal file
174
gamemodes/helix/gamemode/core/libs/thirdparty/sh_netstream2.lua
vendored
Normal 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;
|
||||
411
gamemodes/helix/gamemode/core/libs/thirdparty/sh_pon.lua
vendored
Normal file
411
gamemodes/helix/gamemode/core/libs/thirdparty/sh_pon.lua
vendored
Normal 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
|
||||
385
gamemodes/helix/gamemode/core/libs/thirdparty/sh_tween.lua
vendored
Normal file
385
gamemodes/helix/gamemode/core/libs/thirdparty/sh_tween.lua
vendored
Normal 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
|
||||
338
gamemodes/helix/gamemode/core/libs/thirdparty/sh_utf8.lua
vendored
Normal file
338
gamemodes/helix/gamemode/core/libs/thirdparty/sh_utf8.lua
vendored
Normal 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
|
||||
615
gamemodes/helix/gamemode/core/libs/thirdparty/sh_yaml.lua
vendored
Normal file
615
gamemodes/helix/gamemode/core/libs/thirdparty/sh_yaml.lua
vendored
Normal 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
|
||||
663
gamemodes/helix/gamemode/core/libs/thirdparty/sv_mysql.lua
vendored
Normal file
663
gamemodes/helix/gamemode/core/libs/thirdparty/sv_mysql.lua
vendored
Normal 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
|
||||
Reference in New Issue
Block a user