This commit is contained in:
lifestorm
2024-08-04 22:55:00 +03:00
parent 0e770b2b49
commit 94063e4369
7342 changed files with 1718932 additions and 14 deletions

136
lua/sam/cl_adverts.lua Normal file
View File

@@ -0,0 +1,136 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local config = sam.config
local times = {}
local entry_OnValueChange = function(s)
s:SetTall(s:GetNumLines() * (sam.SUI.Scale(16) --[[font size]] + 1) + 1 + 2)
end
local entry_OnEnter = function(s)
local ads = config.get("Adverts")
local txt = s:GetText()
if txt == "" then
s:Remove()
if s.i then
table.remove(ads, s.i)
end
else
if txt == s.ad then return end
ads[s.i] = txt
s.ad = txt
end
config.set("Adverts", ads, true)
end
local entry_OnKeyCodeTyped = function(s, code)
if code == KEY_ENTER then
s:old_OnKeyCodeTyped(code)
return true
else
return s:old_OnKeyCodeTyped(code)
end
end
config.add_menu_setting("Adverts", function(body)
local adverts_body
local adverts = body:Add("SAM.LabelPanel")
adverts:Dock(TOP)
adverts:DockMargin(8, 6, 8, 0)
adverts:SetLabel("Adverts\n- Random adverts print every 60 seconds\n- Timed adverts can be done like this: {1m} This advert prints every 1 minute")
local add_advert = adverts:Add("SAM.Button")
add_advert:SetText("+")
add_advert:SetSize(25, 25)
local zpos = 0
local add_func = function(ad, ad_i)
zpos = zpos + 1
local entry = adverts_body:Add("SAM.TextEntry")
entry:SetPlaceholder("")
entry:SetMultiline(true)
entry:SetNoBar(true)
entry:Dock(TOP)
entry:DockMargin(8, 6, 8, 0)
entry:SetZPos(zpos)
entry.ad = ad
entry.no_scale = true
if not sam.ispanel(ad) then
entry.i = ad_i
entry:SetValue(ad)
else
entry.i = #config.get("Adverts") + 1
end
entry.OnValueChange = entry_OnValueChange
entry.OnEnter = entry_OnEnter
entry.old_OnKeyCodeTyped = entry.OnKeyCodeTyped
entry.OnKeyCodeTyped = entry_OnKeyCodeTyped
end
add_advert:On("DoClick", add_func)
adverts_body = body:Add("Panel")
adverts_body:Dock(TOP)
function adverts_body:PerformLayout(w, h)
for k, v in ipairs(self:GetChildren()) do
entry_OnValueChange(v)
end
self:SizeToChildren(false, true)
end
sam.config.hook({"Adverts"}, function()
if not IsValid(adverts_body) then return end
adverts_body:Clear()
for k, v in ipairs(config.get("Adverts")) do
add_func(v, k)
end
end)
end)
local random = {}
timer.Create("SAM.Advert.RandomAdverts", 60, 0, function()
local ad = random[math.random(1, #random)]
if not ad then return end
sam.player.send_message(nil, ad)
end)
sam.config.hook({"Adverts"}, function()
for i = #times, 1, -1 do
times[i] = nil
timer.Remove("SAM.Adverts." .. i)
end
random = {}
for k, v in ipairs(config.get("Adverts")) do
if v:sub(1, 1) == "{" then
local time
time, v = v:match("(%b{}) *(.*)")
time = sam.parse_length(time)
if time then
timer.Create("SAM.Adverts." .. table.insert(times, true), time * 60, 0, function()
sam.player.send_message(nil, v)
end)
end
else
table.insert(random, v)
end
end
end)

View File

@@ -0,0 +1,58 @@
--[[
| 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 SAM_LOADED then return end
local sam, command = sam, sam.command
command.new_argument("dropdown")
:OnExecute(function(arg, input, ply, _, result)
if not arg.options or table.Empty(arg.options) then
ply:sam_send_message("no data", {S = "dropdown", S_2 = input})
return
end
table.insert(result, input)
end)
:Menu(function(set_result, body, buttons, arg)
local default = arg.hint or "select"
local cbo = buttons:Add("SAM.ComboBox")
cbo:SetValue(default)
cbo:SetTall(25)
function cbo:OnSelect(_, value)
set_result(value)
default = value
end
function cbo:DoClick()
if self:IsMenuOpen() then
return self:CloseMenu()
end
self:Clear()
self:SetValue(default)
if not arg.options then
LocalPlayer():sam_send_message("dropdown has no options data")
return
end
for k, v in pairs(arg.options) do
self:AddChoice(v)
end
self:OpenMenu()
end
return cbo
end)
:End()

View File

@@ -0,0 +1,72 @@
--[[
| 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 SAM_LOADED then return end
local sam, command = sam, sam.command
local get_length = function(arg, input)
if (input == "" or input == nil) and arg.optional then
if arg.default ~= nil then
return arg.default
end
return ""
end
return sam.parse_length(input)
end
command.new_argument("length")
:OnExecute(function(arg, input, ply, _, result, i)
local length = get_length(arg, input)
if length == "" then
result[i] = nil
elseif not length then
ply:sam_send_message("invalid", {
S = "length", S_2 = input
})
return false
else
if arg.min and length ~= 0 then
length = math.max(length, arg.min)
end
if arg.max then
if length == 0 then
length = arg.max
else
length = math.min(length, arg.max)
end
end
result[i] = length
end
end)
:Menu(function(set_result, body, buttons, argument)
local length_input = buttons:Add("SAM.TextEntry")
length_input:SetTall(25)
length_input:SetCheck(function(new_limit)
new_limit = get_length(argument, new_limit) or nil
set_result(new_limit)
return new_limit or false
end)
local hint = argument.hint or "length"
if argument.default then
hint = hint .. " = " .. tostring(argument.default)
end
length_input:SetPlaceholder(hint)
return length_input
end)
:End()

View File

@@ -0,0 +1,60 @@
--[[
| 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 SAM_LOADED then return end
local sam, command = sam, sam.command
command.new_argument("map")
:OnExecute(function(argument, input, ply, _, result)
local map_name = sam.is_valid_map(input)
if not map_name and not (argument.optional and input == "None") then
ply:sam_send_message("invalid", {
S = "map", S_2 = input
})
return false
end
table.insert(result, map_name)
end)
:Menu(function(set_result, _, buttons, argument)
local maps = buttons:Add("SAM.ComboBox")
maps:SetTall(25)
if argument.optional then
maps:AddChoice("None", nil, true)
end
for _, map_name in ipairs(sam.get_global("Maps")) do
if not (argument.exclude_current and map_name == game.GetMap()) then
maps:AddChoice(map_name)
end
end
function maps:OnSelect(_, value)
set_result(value)
end
local value = argument.optional and "None" or maps:GetOptionText(1)
maps:SetValue(value)
maps:OnSelect(nil, value)
return maps
end)
:AutoComplete(function(_, result, name)
for _, map_name in ipairs(sam.get_global("Maps")) do
if map_name:lower():find(name, 1, true) then
table.insert(result, map_name)
end
end
end)
:End()

View File

@@ -0,0 +1,77 @@
--[[
| 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 SAM_LOADED then return end
local sam, command = sam, sam.command
local get_number = function(argument, input, gsub)
if (input == "" or input == nil) and argument.optional then
if argument.default ~= nil then
return argument.default
end
return ""
end
local number = tonumber(input)
if gsub ~= false and not isnumber(number) then
number = tonumber(input:gsub("%D", ""), 10 /*gsub returns two args*/)
end
return number
end
command.new_argument("number")
:OnExecute(function(argument, input, ply, _, result, i)
local number = get_number(argument, input)
if number == "" then
result[i] = nil
elseif not number then
ply:sam_send_message("invalid", {
S = argument.hint or "number", S_2 = input
})
return false
else
if argument.min then
number = math.max(number, argument.min)
end
if argument.max then
number = math.min(number, argument.max)
end
if argument.round then
number = math.Round(number)
end
result[i] = number
end
end)
:Menu(function(set_result, body, buttons, argument)
local number_entry = buttons:Add("SAM.TextEntry")
number_entry:SetUpdateOnType(true)
number_entry:SetNumeric(true)
number_entry:SetTall(25)
number_entry:SetCheck(function(number)
number = get_number(argument, number, false)
set_result(number)
return number or false
end)
local hint = argument.hint or "number"
if argument.default then
hint = hint .. " = " .. tostring(argument.default)
end
number_entry:SetPlaceholder(hint)
return number_entry
end)
:End()

View File

@@ -0,0 +1,456 @@
--[[
| 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 SAM_LOADED then return end
local sam, command = sam, sam.command
local can_target_player = function(arg, admin, target, cmd, input)
if not IsValid(target) or not target:IsPlayer() or not target:sam_get_nwvar("is_authed") then
if input then
admin:sam_send_message("cant_find_target", {
S = input
})
end
return false
end
if not arg.allow_higher_target and not admin:CanTarget(target) then
if cmd then
admin:sam_send_message("cant_target_player", {
S = target:Name()
})
end
return false
end
if arg.cant_target_self and admin == target then
if cmd then
admin:sam_send_message("cant_target_self", {
S = cmd.name
})
end
return false
end
return true
end
local check_text_match = function(text, ply)
if ply:Name():lower():find(text, 1, true) then return true end
if ply:sam_getrank():lower():find(text, 1, true) then return true end
if team.GetName(ply:Team()):lower():find(text, 1, true) then return true end
if not ply:IsBot() then
return ply:SteamID():lower():find(text, 1, true) or ply:SteamID64():lower():find(text, 1, true)
end
return false
end
command.new_argument("player")
:OnExecute(function(arg, input, ply, cmd, result, n)
if input == nil and arg.optional then
if sam.isconsole(ply) then
ply:sam_send_message("cant_target_self", {
S = cmd.name
})
return false
end
result[n] = {ply, admin = ply, input = input}
return
end
local single_target = arg.single_target
local targets = {admin = ply, input = input}
if input == "*" then
if single_target then
ply:sam_send_message("cant_target_multi_players")
return false
end
local players = player.GetAll()
for i = 1, #players do
local v = players[i]
if can_target_player(arg, ply, v) then
table.insert(targets, v)
end
end
elseif input:sub(1, 1) == "#" and not single_target then
local tmp = {}
for _, v in ipairs(input:sub(2):Trim():Split(",")) do
v = tonumber(v)
if not sam.isnumber(v) then continue end
local target = Entity(v)
if not tmp[target] and IsValid(target) and target:IsPlayer() then
tmp[target] = true
if can_target_player(arg, ply, target) then
table.insert(targets, target)
end
end
end
else
local target
if input == "^" then
target = ply
elseif input == "@" and not sam.isconsole(ply) then
target = ply:GetEyeTrace().Entity
elseif sam.is_steamid(input) then
target = player.GetBySteamID(input)
elseif sam.is_steamid64(input) then
target = player.GetBySteamID64(input)
elseif input:sub(1, 1) == "#" then
local index = input:sub(2):Trim()
index = tonumber(index)
if not isnumber(index) then
ply:sam_send_message("invalid_id", {
S = input
})
return false
end
target = Entity(index)
if not IsValid(target) or not target:IsPlayer() then
ply:sam_send_message("player_id_not_found", {
S = index
})
return false
end
else
if input:sub(1, 1) == "%" and #input > 1 then
input = input:sub(2)
end
target = sam.player.find_by_name(input)
if sam.istable(target) then
if single_target then
ply:sam_send_message("found_multi_players", {T = target})
return false
else
for k, v in ipairs(target) do
if can_target_player(arg, ply, v) then
table.insert(targets, v)
end
end
goto _end
end
end
end
if not can_target_player(arg, ply, target, cmd, input) then
return false
end
table.insert(targets, target)
end
::_end::
if #targets == 0 then
ply:sam_send_message("cant_find_target", {
S = input
})
return false
end
result[n] = targets
end)
-- Do NOT ask me about this code at all please because I feel shit about it but I'm not gonna make
-- a file specially for this one
:Menu(function(set_result, body, buttons, argument, childs)
if body.ply_list then
local ply_list = body.ply_list
ply_list.argument = argument
ply_list.set_result = set_result
ply_list.multi_select = argument.single_target ~= true
if argument.single_target == true and #ply_list:GetSelected() > 1 then
ply_list:ClearSelection()
end
ply_list:OnRowSelected()
ply_list:GetParent():Show()
return
end
local SUI = sam.SUI
local SetVisible = FindMetaTable("Panel").SetVisible
local left_body = body:Add("SAM.Panel")
left_body:Dock(LEFT)
left_body:DockMargin(0, 0, 5, 0)
left_body:SetWide(0)
left_body.no_remove = true
left_body.no_change = "player"
SetVisible(left_body, false)
left_body.SetVisible = function(s, visible)
if visible == s:IsVisible() or visible == s.visible_state then return end
if visible then
SetVisible(s, true)
s:InvalidateLayout(true)
end
s.visible_state = visible
s:Stop()
s:SizeTo(visible and SUI.Scale(320) or 0, -1, 0.2, 0, 0, function()
SetVisible(s, visible)
s:InvalidateParent(true)
end)
end
left_body:Show()
table.insert(childs, left_body)
local ply_list = left_body:Add("SAM.ScrollPanel")
ply_list:Dock(FILL)
ply_list:Background(Color(34, 34, 34), 3)
ply_list.argument = argument
ply_list.set_result = set_result
ply_list.multi_select = argument.single_target ~= true
ply_list.Paint = function(s, w, h)
s:RoundedBox("Background", 3, 0, 0, w, h, SUI.GetColor("text_entry_bg"))
end
local lines = {}
function ply_list:OnClickLine(line, clear)
local multi_select = ply_list.multi_select
if not multi_select and not clear then return end
if multi_select and input.IsKeyDown(KEY_LCONTROL) then
if line.Selected then
line.Selected = false
self.main_selected_line = nil
self:OnRowSelected()
return
end
clear = false
end
if multi_select and input.IsKeyDown(KEY_LSHIFT) then
local selected = self:GetSelectedLine()
if selected then
self.main_selected_line = self.main_selected_line or selected
if clear then
self:ClearSelection()
end
local first = math.min(self.main_selected_line.id, line.id)
local last = math.max(self.main_selected_line.id, line.id)
for id = first, last do
local line_2 = lines[id]
local was_selected = line_2.Selected
line_2.Selected = true
if not was_selected then
self:OnRowSelected(line_2.id, line_2)
end
end
return
end
end
if not multi_select or clear then
self:ClearSelection()
end
line.Selected = true
self.main_selected_line = line
self:OnRowSelected(line.id, line)
end
function ply_list:GetSelected()
local ret = {}
for _, v in ipairs(lines) do
if v.Selected then
table.insert(ret, v)
end
end
return ret
end
function ply_list:GetSelectedLine()
for _, line in ipairs(lines) do
if line.Selected then return line end
end
end
function ply_list:ClearSelection()
for _, line in ipairs(lines) do
line.Selected = false
end
self:OnRowSelected()
end
function ply_list:OnRowSelected()
local plys = {}
for k, v in ipairs(ply_list:GetSelected()) do
plys[k] = v.ply:EntIndex()
end
if #plys == 0 then
self.set_result(nil)
else
self.set_result("#" .. table.concat(plys, ","))
end
end
function ply_list:OnRowRightClick(_, line)
local dmenu = vgui.Create("SAM.Menu")
dmenu:SetInternal(line)
local name = line.ply:Name()
dmenu:AddOption("Copy Name", function()
SetClipboardText(name)
end)
dmenu:AddSpacer()
local steamid = line.ply:SteamID()
dmenu:AddOption("Copy SteamID", function()
SetClipboardText(steamid)
end)
dmenu:AddOption("Copy SteamID64", function()
SetClipboardText(util.SteamIDTo64(steamid))
end)
dmenu:Open()
dmenu:SetPos(input.GetCursorPos())
end
local item_click = function(s)
ply_list:OnClickLine(s, true)
end
local item_rightclick = function(s)
if not s.Selected then
ply_list:OnClickLine(s, true)
end
ply_list:OnRowRightClick(s.id, s)
end
local item_cursor = function(s)
if input.IsMouseDown(MOUSE_LEFT) then
ply_list:OnClickLine(s)
end
end
local added_players = {}
local add_player = function(ply, i)
if can_target_player(ply_list.argument, LocalPlayer(), ply) then
local player_button = ply_list:Add("SAM.Button")
player_button:Dock(TOP)
player_button:DockMargin(0, 0, 0, 2)
player_button:DockPadding(4, 4, 4, 4)
player_button:SetContained(false)
player_button:SetText("")
player_button:SetZPos(i)
player_button.DoClick = item_click
player_button.DoRightClick = item_rightclick
player_button.OnCursorMoved = item_cursor
local line = player_button:Add("SAM.PlayerLine")
line:SetMouseInputEnabled(false)
line:SetInfo({
steamid = ply:IsBot() and "BOT" or ply:SteamID(),
name = ply:Name(),
rank = ply:sam_getrank()
})
player_button:InvalidateLayout(true)
player_button:SizeToChildren(false, true)
player_button.ply = ply
player_button.line = line
player_button.id = table.insert(lines, player_button)
body.search_entry:OnValueChange()
added_players[ply] = true
end
end
ply_list:On("Think", function()
local players = player.GetAll()
for i = 1, #players do
local ply = players[i]
if not added_players[ply] then
add_player(ply, i)
end
end
local argument = ply_list.argument
for i = 1, #lines do
local line = lines[i]
local ply = line.ply
if not can_target_player(argument, LocalPlayer(), ply) then
line:Remove()
table.remove(lines, i)
added_players[ply] = nil
ply_list:OnRowSelected()
break
end
line = line.line
line:SetName(ply:Name())
line:SetRank(ply:sam_getrank())
end
end)
local search_entry = left_body:Add("SAM.TextEntry")
search_entry:Dock(TOP)
search_entry:DockMargin(0, 0, 0, 5)
search_entry:SetPlaceholder("Search... (name/steamid/rank/job)")
search_entry:SetBackground(Color(34, 34, 34))
search_entry:SetTall(25)
search_entry:SetNoBar(true)
function search_entry:OnValueChange(text)
if text == nil then
text = self:GetValue()
end
if text ~= "" then
ply_list:ClearSelection()
end
text = text:lower()
for i, line in ipairs(lines) do
local ply = line.ply
if IsValid(ply) then
line:SetVisible(check_text_match(text, ply))
end
end
ply_list:GetCanvas():InvalidateLayout(true)
end
body.ply_list = ply_list
body.search_entry = search_entry
end)
:AutoComplete(function(arg, result, name)
local ply = LocalPlayer()
for k, v in ipairs(player.GetAll()) do
if can_target_player(arg, ply, v) and v:Name():lower():find(name, 1, true) then
table.insert(result, "%" .. v:Name())
end
end
end)
:End()

View File

@@ -0,0 +1,78 @@
--[[
| 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 SAM_LOADED then return end
local sam, command = sam, sam.command
local is_good_rank = function(rank, arg, ply)
if arg.check and not arg.check(rank, ply) then
return false
end
return true
end
command.new_argument("rank")
:OnExecute(function(arg, input, ply, _, result, i)
if not input and arg.optional then
result[i] = nil
return
end
if not sam.ranks.is_rank(input) or not is_good_rank(input, arg, ply) then
ply:sam_send_message("invalid", {
S = arg.hint or "rank", S_2 = input
})
return false
end
result[i] = input
end)
:Menu(function(set_result, body, buttons, arg)
local current_rank = arg.hint or "select rank"
local ranks = buttons:Add("SAM.ComboBox")
ranks:SetValue(current_rank)
ranks:SetTall(25)
function ranks:OnSelect(_, value)
set_result(value)
current_rank = value
end
function ranks:DoClick()
if self:IsMenuOpen() then
return self:CloseMenu()
end
self:Clear()
self:SetValue(current_rank)
for rank_name in SortedPairsByMemberValue(sam.ranks.get_ranks(), "immunity", true) do
if is_good_rank(rank_name, arg, LocalPlayer()) then
self:AddChoice(rank_name)
end
end
self:OpenMenu()
end
return ranks
end)
:AutoComplete(function(arg, result, name)
for rank_name in SortedPairsByMemberValue(sam.ranks.get_ranks(), "immunity", true) do
if rank_name:lower():find(name, 1, true) and is_good_rank(rank_name, arg, LocalPlayer()) then
table.insert(result, rank_name)
end
end
end)
:End()

View 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/
--]]
if SAM_LOADED then return end
local sam, command = sam, sam.command
local cached_ranks = {}
local targeting_offline = {}
local check_steamid = function(steamid)
if not sam.is_steamid(steamid) then
if sam.is_steamid64(steamid) then
return util.SteamIDFrom64(steamid)
else
return nil
end
end
return steamid
end
local can_target_steamid_callback = function(data, promise)
local ply, steamid = promise.ply, promise.steamid
if not data or sam.ranks.can_target(promise.rank, data.rank) then
promise:resolve({steamid})
elseif IsValid(ply) then
ply:sam_send_message("cant_target_player", {
S = steamid
})
end
targeting_offline[ply] = nil
cached_ranks[steamid] = data ~= nil and data or false
end
command.new_argument("steamid")
:OnExecute(function(argument, input, ply, _, result, i)
local steamid = check_steamid(input)
if not steamid then
ply:sam_send_message("invalid", {
S = "steamid/steamid64", S_2 = input
})
return false
end
if argument.allow_higher_target then
result[i] = steamid
return
end
local promise = sam.Promise.new()
promise.ply = ply
promise.rank = ply:sam_getrank()
promise.steamid = steamid
local target = player.GetBySteamID(steamid)
if sam.isconsole(ply) then
promise:resolve({steamid})
elseif target then
if ply:CanTarget(target) then
promise:resolve({steamid, target})
else
ply:sam_send_message("cant_target_player", {
S = steamid
})
end
elseif cached_ranks[steamid] ~= nil then
can_target_steamid_callback(cached_ranks[steamid], promise)
else
targeting_offline[ply] = true
sam.SQL.FQuery([[
SELECT
`rank`
FROM
`sam_players`
WHERE
`steamid` = {1}
]], {steamid}, can_target_steamid_callback, true, promise)
end
result[i] = promise
end)
:Menu(function(set_result, body, buttons, argument)
local steamid_entry = buttons:Add("SAM.TextEntry")
steamid_entry:SetTall(25)
steamid_entry:SetUpdateOnType(true)
steamid_entry:SetPlaceholder("steamid/steamid64")
steamid_entry:SetCheck(function(steamid)
steamid = check_steamid(steamid)
set_result(steamid)
return steamid or false
end)
return steamid_entry
end)
:End()
timer.Create("SAM.ClearCachedRanks", 60 * 2.5, 0, function()
table.Empty(cached_ranks)
end)
hook.Add("SAM.ChangedSteamIDRank", "RemoveIfCached", function(steamid)
cached_ranks[steamid] = nil
end)
hook.Add("SAM.CanRunCommand", "StopIfTargetingOffline", function(ply)
if targeting_offline[ply] then
return false
end
end)

View File

@@ -0,0 +1,70 @@
--[[
| 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 SAM_LOADED then return end
local sam, command = sam, sam.command
command.new_argument("text")
:OnExecute(function(argument, input, ply, _, result, i)
if sam.isstring(input) then
input = input:sub(1, 255)
end
local invalid = false
if input == nil then
if not argument.optional then
invalid = true
end
elseif argument.check and not argument.check(input, ply) then
invalid = true
end
if invalid then
ply:sam_send_message("invalid", {
S = argument.hint or "text", S_2 = input
})
return false
end
result[i] = input
end)
:Menu(function(set_result, body, buttons, argument)
local text_entry = buttons:Add("SAM.TextEntry")
text_entry:SetTall(25)
local default = argument.default
text_entry:SetCheck(function(text)
local valid = true
if text == "" then
if default then
text = default
elseif not argument.optional then
valid = false
end
elseif argument.check and not argument.check(text, LocalPlayer()) then
valid = false
end
set_result(valid and text or nil)
return valid
end)
local hint = argument.hint or "text"
if default then
hint = hint .. " = " .. tostring(default)
end
text_entry:SetPlaceholder(hint)
return text_entry
end)
:End()

View File

@@ -0,0 +1,128 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local command = sam.command
local get_syntax = function(args, cmd_args, cmd_str)
for i = 1, #cmd_args do
cmd_str = cmd_str .. " "
local cmd_arg = cmd_args[i]
local arg = args[i]
if arg == "" then
arg = nil
end
local optional = cmd_arg.optional
local c_1, c_2 = "<", ">"
if optional then
c_1, c_2 = "[", "]"
end
cmd_str = cmd_str .. (arg and "\"" or c_1)
cmd_str = cmd_str .. (arg or cmd_arg.hint or cmd_arg.name)
if not arg then
local default = cmd_arg.default
if default then
cmd_str = cmd_str .. " = " .. tostring(default)
end
end
cmd_str = cmd_str .. (arg and "\"" or c_2)
end
return cmd_str
end
--
-- Auto Complete
--
concommand.Add("sam", function(_, _, _, text)
LocalPlayer():ConCommand("sam_run " .. text)
end, function(_, text)
local ply = LocalPlayer()
local result = {}
local new_arg = text:EndsWith(" ")
local args = sam.parse_args(text)
local cmd_name = (args[1] or ""):lower()
local cmd = command.get_command(cmd_name)
if not cmd or (#args == 1 and not new_arg) then
local commands = command.get_commands()
for _, v in ipairs(commands) do
local name = v.name
if name:find(cmd_name, nil, true) and ply:HasPermission(name) then
table.insert(result, "sam " .. name)
end
end
return result
end
if not ply:HasPermission(cmd_name) then return end
table.remove(args, 1)
if new_arg then
local syntax = get_syntax(args, cmd.args, "sam " .. cmd.name)
if #args == 0 then
print(syntax)
end
table.insert(result, syntax)
return result
end
local arg_index = new_arg and #args + 1 or #args
local cmd_args = cmd.args
local cmd_args_n = #cmd_args
if cmd_args_n == 0 then return end
if arg_index >= cmd_args_n then
arg_index = cmd_args_n
if cmd.get_rest_args then
local arg = table.concat(args, " ", cmd_args_n)
if arg ~= "" then
args[cmd_args_n] = arg
for i = cmd_args_n + 1, #args do
args[i] = nil
end
end
end
end
local arguments = command.get_arguments()
local cmd_arg = cmd_args[arg_index]
local func = arguments[cmd_arg.name].auto_complete
if func then
func(cmd_arg, result, args[arg_index] or "")
end
local cmd_str = "sam " .. cmd_name .. " "
if arg_index - 1 > 0 then
cmd_str = cmd_str .. "\"" .. table.concat(args, "\" ", 1, arg_index - 1) .. "\" "
end
for k, v in ipairs(result) do
result[k] = cmd_str .. "\"" .. v .. "\""
end
return result
end)

View File

@@ -0,0 +1,289 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local istable, isstring = sam.istable, sam.isstring
local commands = {}
local arguments = {}
do
local command = {}
local current_category = "other"
function command.set_category(category)
if isstring(category) then
current_category = category
end
end
function command.get_commands()
return commands
end
function command.get_command(name)
for i = 1, #commands do
local cmd = commands[i]
if cmd.name == name then
return cmd, i
end
local aliases = cmd.aliases
for i2 = 1, #aliases do
local alias = aliases[i2]
if alias == name then
return cmd, i
end
end
end
return false
end
function command.remove_command(name)
local cmd, index = command.get_command(name)
if index then
table.remove(commands, index)
hook.Call("SAM.CommandRemoved", nil, cmd.name, cmd, index)
return index
end
return false
end
function command.get_arguments()
return arguments
end
do
local argument_methods = {
OnExecute = function(self, func)
if isfunction(func) and SERVER then
self.on_execute = func
end
return self
end,
Menu = function(self, func)
if isfunction(func) and CLIENT then
self.menu = func
end
return self
end,
AutoComplete = function(self, func)
if isfunction(func) and CLIENT then
self.auto_complete = func
end
return self
end,
End = function(self)
if SERVER then
arguments[self.name] = self.on_execute
else
arguments[self.name] = self
end
end
}
local argument_meta = {__index = argument_methods}
function command.new_argument(name)
if isstring(name) then
return setmetatable({name = name}, argument_meta)
end
end
end
if CLIENT then
function command.run_commands(to_run)
local time = 0
for i = 1, #to_run do
timer.Simple(time, function()
RunConsoleCommand("sam", unpack(to_run[i]))
end)
time = time + 0.76
end
end
end
--
-- Methods
--
local Command_Methods = {}
local Command_meta = {__index = Command_Methods}
function command.new(cmd)
if not isstring(cmd) then return end
local new_command = setmetatable({}, Command_meta)
new_command.can_console_run = true
new_command.args = {}
new_command.name = cmd:lower()
new_command.aliases = {}
new_command.category = current_category
return new_command
end
local AddMethod = function(name, func)
Command_Methods[name] = func
end
AddMethod("Aliases", function(self, ...)
for k, v in ipairs({...}) do
table.insert(self.aliases, v)
end
return self
end)
AddMethod("AddArg", function(self, name, data)
if not isstring(name) then return end
if not istable(data) then
data = {}
end
data.name = name
table.insert(self.args, data)
return self
end)
AddMethod("DisallowConsole", function(self, disallow)
self.can_console_run = isbool(disallow) and disallow or false
return self
end)
AddMethod("SetCategory", function(self, category)
if isstring(category) then
self.category = category
end
return self
end)
AddMethod("Help", function(self, help)
if isstring(help) then
self.help = sam.language.get(help) or help
end
return self
end)
AddMethod("OnExecute", function(self, func)
if isfunction(func) and SERVER then
self.on_execute = func
end
return self
end)
AddMethod("SetPermission", function(self, perm, default_rank)
if isstring(perm) then
self.permission = perm
self.default_rank = default_rank
end
return self
end)
AddMethod("GetRestArgs", function(self, get)
if not isbool(get) then
get = true
end
self.get_rest_args = get
return self
end)
AddMethod("MenuHide", function(self, should_hide)
if isbool(should_hide) then
self.menu_hide = should_hide
else
self.menu_hide = true
end
return self
end)
AddMethod("DisableNotify", function(self, disable)
if isbool(disable) then
self.disable_notify = disable
else
self.disable_notify = true
end
return self
end)
AddMethod("End", function(self)
local name = self.name
if SERVER and not self.on_execute then
sam.print("need an OnExecute function for the command!")
debug.Trace()
return
end
if self.permission then
sam.permissions.add(self.permission, "Commands - " .. self.category, self.default_rank)
end
local _, index = command.get_command(name)
if index then
commands[index] = self
hook.Call("SAM.CommandModified", nil, name, self, index)
else
hook.Call("SAM.CommandAdded", nil, name, self, table.insert(commands, self))
end
end)
AddMethod("GetRequiredArgs", function(self)
local required_args = {}
local args = self.args
for i = 1, #args do
local v = args[i]
if not v.optional then
table.insert(required_args, v)
end
end
return required_args
end)
AddMethod("GetOptionalArgs", function(self)
local optional_args = {}
local args = self.args
for i = 1, #args do
local v = args[i]
if v.optional then
table.insert(optional_args, v)
end
end
return optional_args
end)
AddMethod("ArgsToString", function(self, return_table)
local str_table = {}
local args = self.args
for i = 1, #self.args do
local v = args[i]
if not v.optional then
table.insert(str_table, "<" .. (v.hint or v.name) .. ">")
else
table.insert(str_table, "[" .. (v.hint or v.name) .. "]")
end
end
return return_table and str_table or table.concat(str_table, " ")
end)
AddMethod("HasArg", function(self, arg)
local args = self.args
for i = 1, #self.args do
if args[i].name == arg then
return true
end
end
return false
end)
command.add_method = AddMethod
sam.command = command
end
sam.__commands = commands
sam.__arguments = arguments

View 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/
--]]
if SAM_LOADED then return end
local sam = sam
local command = sam.command
local sub, match, lower = string.sub, string.match, string.lower
local WHITE, RED, BLUE, GREEN = Color(236, 240, 241), Color(244, 67, 54), Color(13, 130, 223), Color(0, 230, 64)
local prefix
local send_syntax = function(ply, args, cmd_name, cmd_args, cmd_args_n, from_console)
local tbl = {
WHITE,
from_console and "sam " or prefix,
cmd_name
}
for i = 1, cmd_args_n do
table.insert(tbl, " ")
local cmd_arg = cmd_args[i]
local arg = args[i]
if arg == "" then
arg = nil
end
local optional = cmd_arg.optional
local c_1, c_2 = "<", ">"
if optional then
c_1, c_2 = "[", "]"
end
table.insert(tbl, WHITE)
table.insert(tbl, arg and "\"" or c_1)
table.insert(tbl, cmd_arg.optional and BLUE or RED)
table.insert(tbl, arg or cmd_arg.hint or cmd_arg.name)
if not arg then
local default = cmd_arg.default
if default then
table.insert(tbl, WHITE)
table.insert(tbl, " = ")
table.insert(tbl, GREEN)
table.insert(tbl, tostring(default))
end
end
table.insert(tbl, WHITE)
table.insert(tbl, arg and "\"" or c_2)
end
sam.player.add_text(ply, unpack(tbl))
return ""
end
local run_command = function(ply, text, from_console)
local args = sam.parse_args(text)
local cmd_name = args[1]
if not cmd_name then return end
cmd_name = lower(cmd_name)
local cmd = command.get_command(cmd_name)
if not cmd then return end
if not cmd.can_console_run and sam.isconsole(ply) then
ply:sam_send_message("cant_use_as_console", {
S = cmd_name
})
return ""
end
if cmd.permission and not sam.isconsole(ply) and not ply:HasPermission(cmd.permission) then
ply:sam_send_message("no_permission", {
S = cmd_name
})
return ""
end
local can_run = hook.Call("SAM.CanRunCommand", nil, ply, cmd_name, args, cmd)
if can_run == false then return "" end
table.remove(args, 1)
local cmd_args = cmd.args
local cmd_args_n = #cmd_args
-- !kick srlion fuck off > !kick "srlion" "fuck off"
if cmd.get_rest_args then
local arg = table.concat(args, " ", cmd_args_n)
if arg ~= "" then
args[cmd_args_n] = arg
for i = cmd_args_n + 1, #args do
args[i] = nil
end
end
end
-- we need to make sure that all required arguments are there
for i = 1, cmd_args_n do
if not cmd_args[i].optional then
local arg = args[i]
if arg == nil or arg == "" then
send_syntax(ply, args, cmd_name, cmd_args, cmd_args_n, from_console)
return ""
end
end
end
local result = {}
local args_count = 0
local arguments = command.get_arguments()
for i = 1, cmd_args_n do
local cmd_arg = cmd_args[i]
local arg = args[i]
if arg == nil or arg == "" then
arg = cmd_arg.default
else
args_count = args_count + 1
end
if arguments[cmd_arg.name](cmd_arg, arg, ply, cmd, result, i) == false then
return ""
end
end
cmd.on_execute(ply, unpack(result, 1, table.maxn(result)))
if not cmd.disable_notify then
sam.print(
RED, ply:Name(),
WHITE, "(",
BLUE, ply:SteamID(),
WHITE, ") ran command '",
RED, cmd_name,
WHITE,
args_count > 0
and "' with arguments: \"" .. table.concat(args, "\" \"") .. "\""
or "'"
)
end
hook.Call("SAM.RanCommand", nil, ply, cmd_name, args, cmd, result)
return ""
end
hook.Add("PlayerSay", "SAM.Command.RunCommand", function(ply, text)
prefix = sub(text, 1, 1)
if prefix ~= "!" then return end
if match(sub(text, 2, 2), "%S") == nil then return end
return run_command(ply, sub(text, 2), false)
end)
local console_run_command = function(ply, _, _, text)
if match(sub(text, 2, 2), "%S") == nil then return end
if not IsValid(ply) then
ply = sam.console
else
-- making it same as PlayerSay delay
-- https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/sp/src/game/server/client.cpp#L747
-- no delay for server console
if not ply:sam_check_cooldown("RunCommand", 0.66) then
return
end
end
run_command(ply, text, true, false)
end
concommand.Add("sam", console_run_command)
concommand.Add("sam_run", console_run_command) -- for some dumb reason i cant make "sam" command clientside just for auto-complete

View File

@@ -0,0 +1,63 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local mp = sam.mp
local config = sam.config
function config.set(key, value, force)
if not sam.isstring(key) then
error("invalid setting name")
end
if not mp.packers[sam.type(value)] then
error("not supported value type")
end
if not force and config.get(key) == value then return end
sam.netstream.Start("Config.Set", key, value)
end
function config.get(key, default)
local value = sam.get_global("Config")[key]
if value ~= nil then
return value
end
return default
end
local menu_settings = {}
function config.add_menu_setting(title, func)
local i = #menu_settings + 1
for k, v in ipairs(menu_settings) do
if v.title == title then
i = k
break
end
end
menu_settings[i] = {
title = title,
func = func,
}
end
function config.get_menu_settings()
return menu_settings
end
hook.Add("SAM.ChangedGlobalVar", "SAM.CheckLoadedConfig", function(key, value)
if key == "Config" then
config.loaded = true
hook.Call("SAM.LoadedConfig", nil, value)
hook.Remove("SAM.ChangedGlobalVar", "SAM.CheckLoadedConfig")
end
end)

View File

@@ -0,0 +1,61 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local config = sam.config
sam.permissions.add("manage_config", nil, "superadmin")
local updates = {}
function config.hook(keys, func)
for i = #keys, 1, -1 do
keys[keys[i]] = true
keys[i] = nil
end
local id = table.insert(updates, {
keys = keys,
func = func
})
if config.loaded then
func()
end
return id
end
function config.get_updated(key, default)
local setting = {}
config.hook({key}, function()
setting.value = config.get(key, default)
end)
return setting
end
function config.remove_hook(key)
updates[key] = nil
end
hook.Add("SAM.LoadedConfig", "RunHooks", function()
for k, v in pairs(updates) do
v.func()
end
end)
hook.Add("SAM.UpdatedConfig", "RunHooks", function(key, value, old)
for k, v in pairs(updates) do
if v.keys[key] then
v.func(value, old)
end
end
end)

View File

@@ -0,0 +1,109 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local SQL = sam.SQL
local mp = sam.mp
local config = sam.config
local _config = {}
function config.sync()
sam.set_global("Config", _config, true)
end
local to_hex
do
local byte = string.byte
local gsub = string.gsub
local format = string.format
local hex = function(c)
return format("%02X", byte(c))
end
function to_hex(text)
return (gsub(text, ".", hex))
end
end
function config.set(key, value, force)
if not sam.isstring(key) then
error("invalid setting name")
end
if not mp.packers[sam.type(value)] then
error("not supported value type")
end
local old = _config[key]
if not force and value == old then return end
SQL.FQuery([[
REPLACE INTO
`sam_config`(
`key`,
`value`
)
VALUES
({1}, X{2})
]], {key, to_hex(mp.pack(value))})
_config[key] = value
config.sync()
sam.hook_call("SAM.UpdatedConfig", key, value, old)
end
function config.get(key, default)
local value = _config[key]
if value ~= nil then
return value
end
return default
end
config.sync()
hook.Add("SAM.DatabaseLoaded", "LoadConfig", function()
SQL.Query([[
SELECT
*
FROM
`sam_config`
]], function(sam_config)
for _, v in ipairs(sam_config) do
_config[v.key] = mp.unpack(v.value)
end
config.loaded = true
config.sync()
hook.Call("SAM.LoadedConfig", nil, _config)
end):wait()
end)
sam.netstream.Hook("Config.Set", function(ply, key, value)
config.set(key, value)
value = tostring(value)
if value == "" then
sam.player.send_message(nil, "{A} changed {S Blue} setting to: {S_2 Red}", {
A = ply, S = key, S_2 = "none"
})
else
sam.player.send_message(nil, "{A} changed {S Blue} setting to: {S_2}", {
A = ply, S = key, S_2 = value
})
end
end, function(ply)
return ply:HasPermission("manage_config")
end)

View File

@@ -0,0 +1,190 @@
--[[
| 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 sam, SQL = sam, sam.SQL
local von = include("sam/importers/serverguard/sam_von.lua")
if not von then
return sam.print("Download the importer folder again from gmodstore.")
end
local no_bans_msg = "No bans to import from serverguard."
local import_bans = function()
SQL.TableExists("serverguard_bans", function(exists)
if not exists then
return sam.print(no_bans_msg)
end
SQL.Query([[
DELETE FROM `sam_bans`
]])
SQL.Query([[
INSERT INTO
`sam_bans`(
`id`,
`steamid`,
`reason`,
`admin`,
`unban_date`
)
SELECT
`id`,
`steam_id`,
`reason`,
'Console',
`end_time`
FROM
`serverguard_bans`
]], function()
sam.print("Imported bans from serverguard.")
end)
end)
end
local no_ranks_msg = "No ranks to import from serverguard."
local import_ranks = function()
SQL.TableExists("serverguard_ranks", function(exists)
if not exists then
return sam.print(no_ranks_msg)
end
SQL.Query([[
SELECT
*
FROM
`serverguard_ranks`
]], function(serverguard_ranks)
for _, v in pairs(serverguard_ranks) do
local name = v.unique_id
if not sam.ranks.is_rank(name) then
sam.ranks.add_rank(name, "user", tonumber(v.immunity), tonumber(v.banlimit))
end
local data = sam.isstring(v.data) and util.JSONToTable(v.data)
local tools = sam.istable(data) and sam.istable(data.Restrictions) and sam.istable(data.Restrictions.Tools) and data.Restrictions.Tools
if not tools then continue end
for tool_name, value in pairs(tools) do
if value == false then
sam.ranks.take_permission(name, tool_name)
else
sam.ranks.give_permission(name, tool_name)
end
end
end
sam.print("Imported ranks from serverguard.")
end)
end)
end
local no_users_msg = "No users to import from serverguard."
local import_expires = function(data)
if data and #data > 0 then
local began = false
for _, v in ipairs(data) do
local ply_data = v.data and von.deserialize(v.data) or {}
if not sam.isnumber(tonumber(ply_data.groupExpire)) then continue end
if not began then
began = true
SQL.Begin()
end
SQL.FQuery([[
UPDATE
`sam_players`
SET
`expiry_date` = {1}
WHERE
`steamid` = {2}
]], {tonumber(ply_data.groupExpire), v.steam_id})
end
SQL.Commit(function()
sam.print("Imported users from serverguard.")
end)
else
sam.print("Imported users from serverguard.")
end
end
local import_users = function()
SQL.TableExists("serverguard_users", function(exists)
if not exists then
return sam.print(no_users_msg)
end
SQL.Query([[
DELETE FROM `sam_players`
]])
SQL.Query([[
INSERT INTO
`sam_players`(
`id`,
`steamid`,
`name`,
`rank`,
`expiry_date`,
`first_join`,
`last_join`,
`play_time`
)
SELECT
`id`,
`steam_id`,
`name`,
`rank`,
0,
`last_played`,
`last_played`,
0
FROM
`serverguard_users`
]], function()
SQL.Query([[
SELECT
`steam_id`,
`data`
FROM
`serverguard_users`
]], import_expires)
end)
end)
end
if not sam.ranks.ranks_loaded() then
return sam.print("Server is connecting to the Database, try again when it connects.")
end
for k, v in ipairs(player.GetAll()) do
v:Kick("Importing data.")
end
for k, v in pairs(sam.get_connected_players()) do
sam.player.kick_id(k, "Importing data.")
end
hook.Add("CheckPassword", "SAM.ImportingData", function()
return false, "Importing data."
end)
sam.print("Starting to import data from serverguard...")
timer.Simple(0, function()
import_bans()
import_ranks()
import_users()
hook.Remove("CheckPassword", "SAM.ImportingData")
end)

View File

@@ -0,0 +1,828 @@
--[[
| 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/
--]]
--[[ vON 1.3.4
Copyright 2012-2014 Alexandru-Mihai Maftei
aka Vercas
GitHub Repository:
https://github.com/vercas/vON
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 (Vercas) if you publish your work based on (and/or using) this.
If you modify the code for any purpose, the above obligations still apply.
If you make any interesting modifications, try forking the GitHub repository instead.
Instead of copying this code over for sharing, rather use the link:
https://github.com/vercas/vON/blob/master/von.lua
The author may not be held responsible for any damage or losses directly or indirectly caused by
the use of vON.
If you disagree with the above, don't use the code.
-----------------------------------------------------------------------------------------------------------------------------
Thanks to the following people for their contribution:
- Divran Suggested improvements for making the code quicker.
Suggested an excellent new way of deserializing strings.
Lead me to finding an extreme flaw in string parsing.
- pennerlord Provided some performance tests to help me improve the code.
- Chessnut Reported bug with handling of nil values when deserializing array components.
- People who contributed on the GitHub repository by reporting bugs, posting fixes, etc.
-----------------------------------------------------------------------------------------------------------------------------
The vanilla types supported in this release of vON are:
- table
- number
- boolean
- string
- nil
The Garry's Mod-specific types supported in this release are:
- Vector
- Angle
+ Entities:
- Entity
- Vehicle
- Weapon
- NPC
- Player
- NextBot
These are the types one would normally serialize.
-----------------------------------------------------------------------------------------------------------------------------
New in this version:
- Fixed addition of extra entity types. I messed up really badly.
--]]
local _deserialize, _serialize, _d_meta, _s_meta, d_findVariable, s_anyVariable
local sub, gsub, find, insert, concat, error, tonumber, tostring, type, next = string.sub, string.gsub, string.find, table.insert, table.concat, error, tonumber, tostring, type, next
--[[ This section contains localized functions which (de)serialize
variables according to the types found. ]]
-- This is kept away from the table for speed.
function d_findVariable(s, i, len, lastType, jobstate)
local i, c, typeRead, val = i or 1
-- Keep looping through the string.
while true do
-- Stop at the end. Throw an error. This function MUST NOT meet the end!
if i > len then
error("vON: Reached end of string, cannot form proper variable.")
end
-- Cache the character. Nobody wants to look for the same character ten times.
c = sub(s, i, i)
-- If it just read a type definition, then a variable HAS to come after it.
if typeRead then
-- Attempt to deserialize a variable of the freshly read type.
val, i = _deserialize[lastType](s, i, len, false, jobstate)
-- Return the value read, the index of the last processed character, and the type of the last read variable.
return val, i, lastType
-- @ means nil. It should not even appear in the output string of the serializer. Nils are useless to store.
elseif c == "@" then
return nil, i, lastType
-- $ means a table reference will follow - a number basically.
elseif c == "$" then
lastType = "table_reference"
typeRead = true
-- n means a number will follow. Base 10... :C
elseif c == "n" then
lastType = "number"
typeRead = true
-- b means boolean flags.
elseif c == "b" then
lastType = "boolean"
typeRead = true
-- ' means the start of a string.
elseif c == "'" then
lastType = "string"
typeRead = true
-- " means the start of a string prior to version 1.2.0.
elseif c == "\"" then
lastType = "oldstring"
typeRead = true
-- { means the start of a table!
elseif c == "{" then
lastType = "table"
typeRead = true
--[[ Garry's Mod types go here ]]
-- e means an entity ID will follow.
elseif c == "e" then
lastType = "Entity"
typeRead = true
--[[
-- c means a vehicle ID will follow.
elseif c == "c" then
lastType = "Vehicle"
typeRead = true
-- w means a weapon entity ID will follow.
elseif c == "w" then
lastType = "Weapon"
typeRead = true
-- x means a NPC ID will follow.
elseif c == "x" then
lastType = "NPC"
typeRead = true
--]]
-- p means a player ID will follow.
-- Kept for backwards compatibility.
elseif c == "p" then
lastType = "Entity"
typeRead = true
-- v means a vector will follow. 3 numbers.
elseif c == "v" then
lastType = "Vector"
typeRead = true
-- a means an Euler angle will follow. 3 numbers.
elseif c == "a" then
lastType = "Angle"
typeRead = true
--[[ Garry's Mod types end here ]]
-- If no type has been found, attempt to deserialize the last type read.
elseif lastType then
val, i = _deserialize[lastType](s, i, len, false, jobstate)
return val, i, lastType
-- This will occur if the very first character in the vON code is wrong.
else
error("vON: Malformed data... Can't find a proper type definition. Char#" .. i .. ":" .. c)
end
-- Move the pointer one step forward.
i = i + 1
end
end
-- This is kept away from the table for speed.
-- Yeah, ton of parameters.
function s_anyVariable(data, lastType, isNumeric, isKey, isLast, jobstate)
local tp = type(data)
if jobstate[1] and jobstate[2][data] then
tp = "table_reference"
end
-- Basically, if the type changes.
if lastType ~= tp then
-- Remember the new type. Caching the type is useless.
lastType = tp
if _serialize[lastType] then
-- Return the serialized data and the (new) last type.
-- The second argument, which is true now, means that the data type was just changed.
return _serialize[lastType](data, true, isNumeric, isKey, isLast, false, jobstate), lastType
else
error("vON: No serializer defined for type \"" .. lastType .. "\"!")
end
end
-- Otherwise, simply serialize the data.
return _serialize[lastType](data, false, isNumeric, isKey, isLast, false, jobstate), lastType
end
--[[ This section contains the tables with the functions necessary
for decoding basic Lua data types. ]]
_deserialize = {
-- Well, tables are very loose...
-- The first table doesn't have to begin and end with { and }.
["table"] = function(s, i, len, unnecessaryEnd, jobstate)
local ret, numeric, i, c, lastType, val, ind, expectValue, key = {}, true, i or 1, nil, nil, nil, 1
-- Locals, locals, locals, locals, locals, locals, locals, locals and locals.
if sub(s, i, i) == "#" then
local e = find(s, "#", i + 2, true)
if e then
local id = tonumber(sub(s, i + 1, e - 1))
if id then
if jobstate[1][id] and not jobstate[2] then
error("vON: There already is a table of reference #" .. id .. "! Missing an option maybe?")
end
jobstate[1][id] = ret
i = e + 1
else
error("vON: Malformed table! Reference ID starting at char #" .. i .. " doesn't contain a number!")
end
else
error("vON: Malformed table! Cannot find end of reference ID start at char #" .. i .. "!")
end
end
-- Keep looping.
while true do
-- Until it meets the end.
if i > len then
-- Yeah, if the end is unnecessary, it won't spit an error. The main chunk doesn't require an end, for example.
if unnecessaryEnd then
return ret, i
-- Otherwise, the data has to be damaged.
else
error("vON: Reached end of string, incomplete table definition.")
end
end
-- Cache the character.
c = sub(s, i, i)
--print(i, "table char:", c, tostring(unnecessaryEnd))
-- If it's the end of a table definition, return.
if c == "}" then
return ret, i
-- If it's the component separator, switch to key:value pairs.
elseif c == "~" then
numeric = false
elseif c == ";" then
-- Lol, nothing!
-- Remenant from numbers, for faster parsing.
-- OK, now, if it's on the numeric component, simply add everything encountered.
elseif numeric then
-- Find a variable and it's value
val, i, lastType = d_findVariable(s, i, len, lastType, jobstate)
-- Add it to the table.
ret[ind] = val
ind = ind + 1
-- Otherwise, if it's the key:value component...
else
-- If a value is expected...
if expectValue then
-- Read it.
val, i, lastType = d_findVariable(s, i, len, lastType, jobstate)
-- Add it?
ret[key] = val
-- Clean up.
expectValue, key = false, nil
-- If it's the separator...
elseif c == ":" then
-- Expect a value next.
expectValue = true
-- But, if there's a key read already...
elseif key then
-- Then this is malformed.
error("vON: Malformed table... Two keys declared successively? Char#" .. i .. ":" .. c)
-- Otherwise the key will be read.
else
-- I love multi-return and multi-assignement.
key, i, lastType = d_findVariable(s, i, len, lastType, jobstate)
end
end
i = i + 1
end
return nil, i
end,
-- Just a number which points to a table.
["table_reference"] = function(s, i, len, unnecessaryEnd, jobstate)
local i, a = i or 1
-- Locals, locals, locals, locals
a = find(s, "[;:}~]", i)
if a then
local n = tonumber(sub(s, i, a - 1))
if n then
return jobstate[1][n] or error("vON: Table reference does not point to a (yet) known table!"), a - 1
else
error("vON: Table reference definition does not contain a valid number!")
end
end
-- Using %D breaks identification of negative numbers. :(
error("vON: Number definition started... Found no end.")
end,
-- Numbers are weakly defined.
-- The declaration is not very explicit. It'll do it's best to parse the number.
-- Has various endings: \n, }, ~, : and ;, some of which will force the table deserializer to go one char backwards.
["number"] = function(s, i, len, unnecessaryEnd, jobstate)
local i, a = i or 1
-- Locals, locals, locals, locals
a = find(s, "[;:}~]", i)
if a then
return tonumber(sub(s, i, a - 1)) or error("vON: Number definition does not contain a valid number!"), a - 1
end
-- Using %D breaks identification of negative numbers. :(
error("vON: Number definition started... Found no end.")
end,
-- A boolean is A SINGLE CHARACTER, either 1 for true or 0 for false.
-- Any other attempt at boolean declaration will result in a failure.
["boolean"] = function(s, i, len, unnecessaryEnd, jobstate)
local c = sub(s,i,i)
-- Only one character is needed.
-- If it's 1, then it's true
if c == "1" then
return true, i
-- If it's 0, then it's false.
elseif c == "0" then
return false, i
end
-- Any other supposely "boolean" is just a sign of malformed data.
error("vON: Invalid value on boolean type... Char#" .. i .. ": " .. c)
end,
-- Strings prior to 1.2.0
["oldstring"] = function(s, i, len, unnecessaryEnd, jobstate)
local res, i, a = "", i or 1
-- Locals, locals, locals, locals
while true do
a = find(s, "\"", i, true)
if a then
if sub(s, a - 1, a - 1) == "\\" then
res = res .. sub(s, i, a - 2) .. "\""
i = a + 1
else
return res .. sub(s, i, a - 2), a
end
else
error("vON: Old string definition started... Found no end.")
end
end
end,
-- Strings after 1.2.0
["string"] = function(s, i, len, unnecessaryEnd, jobstate)
local res, i, a = "", i or 1
-- Locals, locals, locals, locals
while true do
a = find(s, "\"", i, true)
if a then
if sub(s, a - 1, a - 1) == "\\" then
res = res .. sub(s, i, a - 2) .. "\""
i = a + 1
else
return res .. sub(s, i, a - 1), a
end
else
error("vON: String definition started... Found no end.")
end
end
end,
}
_serialize = {
-- Uh. Nothing to comment.
-- Ton of parameters.
-- Makes stuff faster than simply passing it around in locals.
-- table.concat works better than normal concatenations WITH LARGE-ISH STRINGS ONLY.
["table"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
--print(string.format("data: %s; mustInitiate: %s; isKey: %s; isLast: %s; nice: %s; indent: %s; first: %s", tostring(data), tostring(mustInitiate), tostring(isKey), tostring(isLast), tostring(nice), tostring(indent), tostring(first)))
local result, keyvals, len, keyvalsLen, keyvalsProgress, val, lastType, newIndent, indentString = {}, {}, #data, 0, 0
-- Locals, locals, locals, locals, locals, locals, locals, locals, locals and locals.
-- First thing to be done is separate the numeric and key:value components of the given table in two tables.
-- pairs(data) is slower than next, data as far as my tests tell me.
for k, v in next, data do
-- Skip the numeric keyz.
if type(k) ~= "number" or k < 1 or k > len or (k % 1 ~= 0) then -- k % 1 == 0 is, as proven by personal benchmarks,
keyvals[#keyvals + 1] = k -- the quickest way to check if a number is an integer.
end -- k % 1 ~= 0 is the fastest way to check if a number
end -- is NOT an integer. > is proven slower.
keyvalsLen = #keyvals
-- Main chunk - no initial character.
if not first then
result[#result + 1] = "{"
end
if jobstate[1] and jobstate[1][data] then
if jobstate[2][data] then
error("vON: Table #" .. jobstate[1][data] .. " written twice..?")
end
result[#result + 1] = "#"
result[#result + 1] = jobstate[1][data]
result[#result + 1] = "#"
jobstate[2][data] = true
end
-- Add numeric values.
if len > 0 then
for i = 1, len do
val, lastType = s_anyVariable(data[i], lastType, true, false, i == len and not first, jobstate)
result[#result + 1] = val
end
end
-- If there are key:value pairs.
if keyvalsLen > 0 then
-- Insert delimiter.
result[#result + 1] = "~"
-- Insert key:value pairs.
for _i = 1, keyvalsLen do
keyvalsProgress = keyvalsProgress + 1
val, lastType = s_anyVariable(keyvals[_i], lastType, false, true, false, jobstate)
result[#result + 1] = val..":"
val, lastType = s_anyVariable(data[keyvals[_i]], lastType, false, false, keyvalsProgress == keyvalsLen and not first, jobstate)
result[#result + 1] = val
end
end
-- Main chunk needs no ending character.
if not first then
result[#result + 1] = "}"
end
return concat(result)
end,
-- Number which points to table.
["table_reference"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
data = jobstate[1][data]
-- If a number hasn't been written before, add the type prefix.
if mustInitiate then
if isKey or isLast then
return "$"..data
else
return "$"..data..";"
end
end
if isKey or isLast then
return data
else
return data..";"
end
end,
-- Normal concatenations is a lot faster with small strings than table.concat
-- Also, not so branched-ish.
["number"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
-- If a number hasn't been written before, add the type prefix.
if mustInitiate then
if isKey or isLast then
return "n"..data
else
return "n"..data..";"
end
end
if isKey or isLast then
return data
else
return data..";"
end
end,
-- I hope gsub is fast enough.
["string"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
if sub(data, #data, #data) == "\\" then -- Hah, old strings fix this best.
return "\"" .. gsub(data, "\"", "\\\"") .. "v\""
end
return "'" .. gsub(data, "\"", "\\\"") .. "\""
end,
-- Fastest.
["boolean"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
-- Prefix if we must.
if mustInitiate then
if data then
return "b1"
else
return "b0"
end
end
if data then
return "1"
else
return "0"
end
end,
-- Fastest.
["nil"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
return "@"
end,
}
--[[ This section handles additions necessary for Garry's Mod. ]]
if gmod then -- Luckily, a specific table named after the game is present in Garry's Mod.
local Entity = Entity
local extra_deserialize = {
-- Entities are stored simply by the ID. They're meant to be transfered, not stored anyway.
-- Exactly like a number definition, except it begins with "e".
["Entity"] = function(s, i, len, unnecessaryEnd, jobstate)
local i, a = i or 1
-- Locals, locals, locals, locals
a = find(s, "[;:}~]", i)
if a then
return Entity(tonumber(sub(s, i, a - 1))), a - 1
end
error("vON: Entity ID definition started... Found no end.")
end,
-- A pair of 3 numbers separated by a comma (,).
["Vector"] = function(s, i, len, unnecessaryEnd, jobstate)
local i, a, x, y, z = i or 1
-- Locals, locals, locals, locals
a = find(s, ",", i)
if a then
x = tonumber(sub(s, i, a - 1))
i = a + 1
end
a = find(s, ",", i)
if a then
y = tonumber(sub(s, i, a - 1))
i = a + 1
end
a = find(s, "[;:}~]", i)
if a then
z = tonumber(sub(s, i, a - 1))
end
if x and y and z then
return Vector(x, y, z), a - 1
end
error("vON: Vector definition started... Found no end.")
end,
-- A pair of 3 numbers separated by a comma (,).
["Angle"] = function(s, i, len, unnecessaryEnd, jobstate)
local i, a, p, y, r = i or 1
-- Locals, locals, locals, locals
a = find(s, ",", i)
if a then
p = tonumber(sub(s, i, a - 1))
i = a + 1
end
a = find(s, ",", i)
if a then
y = tonumber(sub(s, i, a - 1))
i = a + 1
end
a = find(s, "[;:}~]", i)
if a then
r = tonumber(sub(s, i, a - 1))
end
if p and y and r then
return Angle(p, y, r), a - 1
end
error("vON: Angle definition started... Found no end.")
end,
}
local extra_serialize = {
-- Same as numbers, except they start with "e" instead of "n".
["Entity"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
data = data:EntIndex()
if mustInitiate then
if isKey or isLast then
return "e"..data
else
return "e"..data..";"
end
end
if isKey or isLast then
return data
else
return data..";"
end
end,
-- 3 numbers separated by a comma.
["Vector"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
if mustInitiate then
if isKey or isLast then
return "v"..data.x..","..data.y..","..data.z
else
return "v"..data.x..","..data.y..","..data.z..";"
end
end
if isKey or isLast then
return data.x..","..data.y..","..data.z
else
return data.x..","..data.y..","..data.z..";"
end
end,
-- 3 numbers separated by a comma.
["Angle"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
if mustInitiate then
if isKey or isLast then
return "a"..data.p..","..data.y..","..data.r
else
return "a"..data.p..","..data.y..","..data.r..";"
end
end
if isKey or isLast then
return data.p..","..data.y..","..data.r
else
return data.p..","..data.y..","..data.r..";"
end
end,
}
for k, v in pairs(extra_serialize) do
_serialize[k] = v
end
for k, v in pairs(extra_deserialize) do
_deserialize[k] = v
end
local extraEntityTypes = { "Vehicle", "Weapon", "NPC", "Player", "NextBot" }
for i = 1, #extraEntityTypes do
_serialize[extraEntityTypes[i]] = _serialize.Entity
end
end
--[[ This section exposes the functions of the library. ]]
local function checkTableForRecursion(tab, checked, assoc)
local id = checked.ID
if not checked[tab] and not assoc[tab] then
assoc[tab] = id
checked.ID = id + 1
else
checked[tab] = true
end
for k, v in pairs(tab) do
if type(k) == "table" and not checked[k] then
checkTableForRecursion(k, checked, assoc)
end
if type(v) == "table" and not checked[v] then
checkTableForRecursion(v, checked, assoc)
end
end
end
local _s_table = _serialize.table
local _d_table = _deserialize.table
_d_meta = {
__call = function(self, str, allowIdRewriting)
if type(str) == "string" then
return _d_table(str, nil, #str, true, {{}, allowIdRewriting})
end
error("vON: You must deserialize a string, not a "..type(str))
end
}
_s_meta = {
__call = function(self, data, checkRecursion)
if type(data) == "table" then
if checkRecursion then
local assoc, checked = {}, {ID = 1}
checkTableForRecursion(data, checked, assoc)
return _s_table(data, nil, nil, nil, nil, true, {assoc, {}})
end
return _s_table(data, nil, nil, nil, nil, true, {false})
end
error("vON: You must serialize a table, not a "..type(data))
end
}
local von = {
version = "1.3.4",
versionNumber = 1003004, -- Reserving 3 digits per version component.
deserialize = setmetatable(_deserialize,_d_meta),
serialize = setmetatable(_serialize,_s_meta)
}
return von

View File

@@ -0,0 +1,429 @@
--[[
| 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/
--]]
-- PLEASE READ
-- PLEASE READ
-- PLEASE READ
-- PLEASE READ
-- Set the timing module to import times from when importing users
-- Supported modules: "utime"
-- If your timing module is not here then STOP and make a support ticket so I can add support for it before you import your users
local TIME_MODULE = "utime" -- Set this to nil if you don't want to import times
-- PLEASE READ
-- PLEASE READ
-- PLEASE READ
-- PLEASE READ
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
local sam, SQL = sam, sam.SQL
local ulib_unescape_backslash = function(s)
return s:gsub("\\\\", "\\")
end
local ulib_explode = function(separator, str, limit)
local t = {}
local curpos = 1
while true do -- We have a break in the loop
local newpos, endpos = str:find(separator, curpos) -- find the next separator in the string
if newpos ~= nil then -- if found then..
table.insert(t, str:sub(curpos, newpos - 1)) -- Save it in our table.
curpos = endpos + 1 -- save just after where we found it for searching next time.
else
if limit and #t > limit then
return t -- Reached limit
end
table.insert(t, str:sub(curpos)) -- Save what's left in our array.
break
end
end
return t
end
local ulib_split_args = function(args, start_token, end_token)
args = args:Trim()
local argv = {}
local curpos = 1 -- Our current position within the string
local in_quote = false -- Is the text we're currently processing in a quote?
start_token = start_token or "\""
end_token = end_token or "\""
local args_len = args:len()
while in_quote or curpos <= args_len do
local quotepos = args:find(in_quote and end_token or start_token, curpos, true)
-- The string up to the quote, the whole string if no quote was found
local prefix = args:sub(curpos, (quotepos or 0) - 1)
if not in_quote then
local trimmed = prefix:Trim()
if trimmed ~= "" then -- Something to be had from this...
local t = ulib_explode("%s+", trimmed)
table.Add(argv, t)
end
else
table.insert(argv, prefix)
end
-- If a quote was found, reduce our position and note our state
if quotepos ~= nil then
curpos = quotepos + 1
in_quote = not in_quote
else -- Otherwise we've processed the whole string now
break
end
end
return argv, in_quote
end
local ulib_parse_key_values = function(str, convert)
local lines = ulib_explode("\r?\n", str)
local parent_tables = {} -- Traces our way to root
local current_table, n = {}, 0
local is_insert_last_op = false
local tmp_string = string.char(01, 02, 03) -- Replacement
for i, line in ipairs(lines) do
local tokens = ulib_split_args((line:gsub("\\\"", tmp_string)))
for i, token in ipairs(tokens) do
tokens[ i ] = ulib_unescape_backslash(token):gsub(tmp_string, "\"")
end
local num_tokens = #tokens
if num_tokens == 1 then
local token = tokens[ 1 ]
if token == "{" then
local new_table = {}
if is_insert_last_op then
current_table[ table.remove(current_table) ] = new_table
else
table.insert(current_table, new_table)
end
is_insert_last_op = false
table.insert(parent_tables, current_table)
current_table = new_table
elseif token == "}" then
is_insert_last_op = false
current_table = table.remove(parent_tables)
if current_table == nil then
return nil, "Mismatched recursive tables on line " .. i
end
else
is_insert_last_op = true
table.insert(current_table, tokens[ 1 ])
end
elseif num_tokens == 2 then
is_insert_last_op = false
if convert and tonumber(tokens[ 1 ]) then
tokens[ 1 ] = tonumber(tokens[ 1 ])
end
current_table[ tokens[ 1 ] ] = tokens[ 2 ]
elseif num_tokens > 2 then
return nil, "Bad input on line " .. i
end
end
if #parent_tables ~= 0 then
return nil, "Mismatched recursive tables"
end
if convert and n == 1 and
type(current_table.Out) == "table" then -- If we caught a stupid garry-wrapper
current_table = current_table.Out
end
return current_table
end
local ulib_remove_comment_header = function(data, comment_char)
comment_char = comment_char or ";"
local lines = ulib_explode("\r?\n", data)
local end_comment_line = 0
for _, line in ipairs(lines) do
local trimmed = line:Trim()
if trimmed == "" or trimmed:sub(1, 1) == comment_char then
end_comment_line = end_comment_line + 1
else
break
end
end
local not_comment = table.concat(lines, "\n", end_comment_line + 1)
return not_comment:Trim()
end
local no_bans_msg = "No bans to import from ulx."
local import_bans = function()
sam.print("Importing bans...")
if not sql.TableExists("ulib_bans") then
return sam.print(no_bans_msg)
end
local bans = sql.Query("SELECT `steamid`, `unban`, `reason`, `admin` FROM `ulib_bans`")
if not sam.istable(bans) then
return sam.print(no_bans_msg)
end
local bans_count = #bans
if bans_count == 0 then
return sam.print(no_bans_msg)
end
local began, imported_count = false, 0
for i = 1, bans_count do
local ban = bans[i]
local steamid = ban.steamid
if sam.is_steamid64(steamid) then
steamid = util.SteamIDFrom64(ban.steamid)
end
if sam.is_steamid(steamid) then
if not began then
began = true
SQL.Begin()
SQL.Query([[
DELETE FROM `sam_bans`
]])
end
local name, reason, admin, unban_date = ban.name, ban.reason, ban.admin, tonumber(ban.unban)
if name == "NULL" then
name = ""
end
if reason == "NULL" then
reason = "none"
end
if sam.isstring(admin) and admin ~= "NULL" and admin ~= "(Console)" then
admin = admin:match("%(STEAM_%w:%w:%w*%)"):sub(2, -2)
else
admin = "Console"
end
if not sam.isnumber(unban_date) or unban_date < 0 then
unban_date = 0
end
SQL.FQuery([[
INSERT INTO
`sam_bans`(
`steamid`,
`reason`,
`admin`,
`unban_date`
)
VALUES
({1}, {2}, {3}, {4})
]], {steamid, reason, admin, unban_date})
imported_count = imported_count + 1
end
end
if began then
SQL.Commit(function()
sam.print("Imported " .. imported_count .. " ban(s).")
end)
else
sam.print(no_bans_msg)
end
end
local groups_path = "ulib/groups.txt"
local no_ranks_msg = "No ranks to import from ulx."
local imported_ranks
local function add_rank(rank, info, ranks)
if not sam.ranks.is_rank(rank) then
local inherit = info.inherit_from
if inherit and ranks[inherit] and not sam.ranks.is_rank(inherit) then
add_rank(inherit, ranks[inherit], ranks)
end
imported_ranks = imported_ranks + 1
sam.ranks.add_rank(rank, inherit)
end
end
local import_ranks = function()
sam.print("Importing ranks...")
if not file.Exists(groups_path, "DATA") then
return sam.print(no_ranks_msg)
end
local ranks_text = file.Read(groups_path, "DATA")
if not sam.isstring(ranks_text) then
return sam.print(no_ranks_msg)
end
local ranks, err = ulib_parse_key_values(ulib_remove_comment_header(ranks_text, "/"))
if not ranks then
return sam.print("ULX ranks file was not formatted correctly, make a support ticket. (include the error message if there is one)" .. (err and (" | error: " .. err) or ""))
end
imported_ranks = 0
for rank, info in pairs(ranks) do
if not sam.ranks.is_rank(rank) then
add_rank(rank, info, ranks)
end
end
if imported_ranks == 0 then
sam.print(no_ranks_msg)
else
sam.print("Imported " .. imported_ranks .. " rank(s).")
end
end
local users_path = "ulib/users.txt"
local no_users_msg = "No users to import from ulx."
local import_users = function()
sam.print("Importing users...")
if not file.Exists(users_path, "DATA") then
return sam.print(no_users_msg)
end
local users_text = file.Read(users_path, "DATA")
if not sam.isstring(users_text) then
return sam.print(no_users_msg)
end
local users, err = ulib_parse_key_values(ulib_remove_comment_header(users_text, "/"))
if not users then
return sam.print("ULX users file was not formatted correctly, make a support ticket. (include the error message if there is one)" .. (err and (" | error: " .. err) or ""))
end
local current_time = os.time()
local imported_users = 0
local times = false
if TIME_MODULE == "utime" then
local utime = sql.Query("SELECT `player`, `lastvisit`, `totaltime` FROM `utime`")
if sam.istable(utime) then
times = {}
for i = 1, #utime do
local v = utime[i]
times[v.player] = v
end
end
end
local began = false
for steamid, info in pairs(users) do
if sam.is_steamid64(steamid) then
steamid = util.SteamIDFrom64(steamid)
end
if sam.is_steamid(steamid) then
if not began then
began = true
SQL.Begin()
SQL.Query([[
DELETE FROM `sam_players`
]])
end
local first_join, last_join, play_time = current_time, current_time, 0
if times and TIME_MODULE == "utime" then
local utime_user = times[util.CRC("gm_" .. steamid .. "_gm")]
if utime_user then
last_join, play_time = utime_user.lastvisit, math.floor(utime_user.totaltime)
first_join = last_join
end
end
local rank = info.group
if not rank or not sam.ranks.is_rank(rank) then
rank = "user"
end
SQL.FQuery([[
INSERT INTO
`sam_players`(
`steamid`,
`name`,
`rank`,
`expiry_date`,
`first_join`,
`last_join`,
`play_time`
)
VALUES
({1}, {2}, {3}, {4}, {5}, {6}, {7})
]], {steamid, info.name or "", rank, 0, first_join, last_join, play_time})
imported_users = imported_users + 1
end
end
if began then
SQL.Commit(function()
sam.print("Imported " .. imported_users .. " user(s).")
end)
else
sam.print(no_users_msg)
end
end
if not sam.ranks.ranks_loaded() then
return sam.print("Server is connecting to the Database, try again when it connects.")
end
for k, v in ipairs(player.GetAll()) do
v:Kick("Importing data.")
end
for k, v in pairs(sam.get_connected_players()) do
sam.player.kick_id(k, "Importing data.")
end
hook.Add("CheckPassword", "SAM.ImportingData", function()
return false, "Importing data."
end)
sam.print("Starting to import data from ulx...")
timer.Simple(0, function()
import_bans()
import_ranks()
import_users()
hook.Remove("CheckPassword", "SAM.ImportingData")
end)

View 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/
--]]
--
-- CONFIG
--
local UsersTableName = "xadminusers"
local GroupsTableName = "xadmingroups"
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
local sam, SQL = sam, sam.SQL
local no_bans_msg = "No bans to import from xadmin."
local import_bans = function()
SQL.TableExists("xadminbans", function(exists)
if not exists then
return sam.print(no_bans_msg)
end
SQL.Query([[
SELECT
`SteamID`,
`Admin`,
`Reason`,
`EndTime`
FROM
`xadminbans`
]], function(xadmin_bans)
SQL.Begin()
SQL.Query([[
DELETE FROM `sam_bans`
]])
local args, steamid, admin = {}, nil, nil
for _, v in ipairs(xadmin_bans) do
steamid, admin = util.SteamIDFrom64(v.SteamID), v.Admin
if admin == "CONSOLE" then
admin = "Console"
else
admin = util.SteamIDFrom64(admin)
end
args[1] = steamid
args[2] = v.Reason
args[3] = admin
args[4] = v.EndTime
SQL.FQuery([[
INSERT INTO
`sam_bans`(
`steamid`,
`reason`,
`admin`,
`unban_date`
)
VALUES
({1}, {2}, {3}, {4})
]], args)
end
SQL.Commit(function()
sam.print("Imported bans from xadmin.")
end)
end)
end):wait()
end
local get_rank = function(rank)
if rank == "Super Admin" then
return "superadmin"
elseif rank == "Admin" then
return "admin"
elseif rank == "Moderator" then
return "moderator"
elseif rank == "User" then
return "user"
end
return rank
end
local no_ranks_msg = "No ranks to import from xadmin."
local import_ranks = function()
SQL.TableExists(GroupsTableName, function(exists)
if not exists then
return sam.print(no_ranks_msg)
end
SQL.Query(([[
SELECT
`Name`,
`Power`
FROM
`%s`
]]):format(GroupsTableName), function(xadmin_ranks)
for _, v in ipairs(xadmin_ranks) do
local name = get_rank(v.Name)
if not sam.ranks.is_rank(name) then
sam.ranks.add_rank(name, "user", tonumber(v.Power), 20160)
end
end
sam.print("Imported ranks from xadmin.")
end):wait()
end):wait()
end
local no_users_msg = "No users to import from xadmin."
local import_users = function()
SQL.TableExists(UsersTableName, function(exists)
if not exists then
return sam.print(no_users_msg)
end
SQL.Query(([[
SELECT
`%s`.`SteamID`,
`%s`.`AccessLevel`,
IFNULL(SUM(`time`), 0) `total`
FROM
`%s`
LEFT OUTER JOIN `xadmintimetracking` `times`
on `%s`.`SteamID` = `times`.`SteamID`
GROUP BY
`%s`.`SteamID`
]]):format(UsersTableName, UsersTableName, UsersTableName, UsersTableName, UsersTableName), function(xadmin_players)
SQL.Begin()
SQL.Query([[
DELETE FROM `sam_players`
]])
local args = {}
args[2] = ""
args[4] = 0
args[5] = os.time()
args[6] = os.time()
for k, v in ipairs(xadmin_players) do
args[1] = v.SteamID
local rank = get_rank(v.AccessLevel)
if not sam.ranks.is_rank(rank) then
rank = "user"
end
args[3] = rank
args[7] = v.total
SQL.FQuery([[
INSERT INTO
`sam_players`(
`steamid`,
`name`,
`rank`,
`expiry_date`,
`first_join`,
`last_join`,
`play_time`
)
VALUES
({1}, {2}, {3}, {4}, {5}, {6}, {7})
]], args)
end
SQL.Commit(function()
sam.print("Imported users from xadmin.")
end)
end):wait()
end):wait()
end
if not sam.ranks.ranks_loaded() then
return sam.print("Server is connecting to the Database, try again when it connects.")
end
for k, v in ipairs(player.GetAll()) do
v:Kick("Importing data.")
end
for k, v in pairs(sam.get_connected_players()) do
sam.player.kick_id(k, "Importing data.")
end
hook.Add("CheckPassword", "SAM.ImportingData", function()
return false, "Importing data."
end)
sam.print("Starting to import data from xadmin...")
timer.Simple(0, function()
import_bans()
import_ranks()
import_users()
hook.Remove("CheckPassword", "SAM.ImportingData")
end)

View File

@@ -0,0 +1,30 @@
lua-MessagePack License
--------------------------
lua-MessagePack is licensed under the terms of the MIT/X11 license reproduced below.
===============================================================================
Copyright (C) 2012-2019 Francois Perrad.
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.
===============================================================================
(end of COPYRIGHT)

View File

@@ -0,0 +1,874 @@
--[[
| 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 SAM_LOADED then return end
--
-- lua-MessagePack : <https://fperrad.frama.io/lua-MessagePack/>
--
local SIZEOF_NUMBER = string.pack and #string.pack('n', 0.0) or 8
local maxinteger
local mininteger
local assert = assert
local error = error
local pairs = pairs
local pcall = pcall
local setmetatable = setmetatable
local tostring = tostring
local char = string.char
local format = string.format
local floor = math.floor
local tointeger = floor
local frexp = math.frexp
local ldexp = math.ldexp
local huge = math.huge
local tconcat = table.concat
local type = sam.type
local isnumber = sam.isnumber
local _ENV = nil
local m = {}
--[[ debug only
local function hexadump (s)
return (s:gsub('.', function (c) return format('%02X ', c:byte()) end))
end
m.hexadump = hexadump
--]]
local function argerror(caller, narg, extramsg)
error("bad argument #" .. tostring(narg) .. " to " .. caller .. " (" .. extramsg .. ")")
end
local function typeerror(caller, narg, arg, tname)
argerror(caller, narg, tname .. " expected, got " .. type(arg))
end
local function checktype(caller, narg, arg, tname)
if type(arg) ~= tname then
typeerror(caller, narg, arg, tname)
end
end
local packers = setmetatable({}, {
__index = function(t, k)
if k == 1 then return end -- allows ipairs
error("pack '" .. k .. "' is unimplemented")
end
})
m.packers = packers
packers["nil"] = function(buffer)
buffer[#buffer + 1] = char(0xC0) -- nil
end
packers["boolean"] = function(buffer, bool)
if bool then
buffer[#buffer + 1] = char(0xC3) -- true
else
buffer[#buffer + 1] = char(0xC2) -- false
end
end
packers["string_compat"] = function(buffer, str)
local n = #str
if n <= 0x1F then
buffer[#buffer + 1] = char(0xA0 + n) -- fixstr
elseif n <= 0xFFFF then
buffer[#buffer + 1] = char(0xDA, floor(n / 0x100), n % 0x100) -- str16
elseif n <= 4294967295.0 then
buffer[#buffer + 1] = char(0xDB, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- str32
else
error"overflow in pack 'string_compat'"
end
buffer[#buffer + 1] = str
end
packers["_string"] = function(buffer, str)
local n = #str
if n <= 0x1F then
buffer[#buffer + 1] = char(0xA0 + n) -- fixstr
elseif n <= 0xFF then
buffer[#buffer + 1] = char(0xD9, n) -- str8
elseif n <= 0xFFFF then
buffer[#buffer + 1] = char(0xDA, floor(n / 0x100), n % 0x100) -- str16
elseif n <= 4294967295.0 then
buffer[#buffer + 1] = char(0xDB, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- str32
else
error("overflow in pack 'string'")
end
buffer[#buffer + 1] = str
end
packers["binary"] = function(buffer, str)
local n = #str
if n <= 0xFF then
buffer[#buffer + 1] = char(0xC4, n) -- bin8
elseif n <= 0xFFFF then
buffer[#buffer + 1] = char(0xC5, floor(n / 0x100), n % 0x100) -- bin16
elseif n <= 4294967295.0 then
buffer[#buffer + 1] = char(0xC6, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- bin32
else
error("overflow in pack 'binary'")
end
buffer[#buffer + 1] = str
end
local set_string = function(str)
if str == "string_compat" then
packers["string"] = packers["string_compat"]
elseif str == "string" then
packers["string"] = packers["_string"]
elseif str == "binary" then
packers["string"] = packers["binary"]
else
argerror("set_string", 1, "invalid option '" .. str .. "'")
end
end
m.set_string = set_string
packers["map"] = function(buffer, tbl, n)
if n <= 0x0F then
buffer[#buffer + 1] = char(0x80 + n) -- fixmap
elseif n <= 0xFFFF then
buffer[#buffer + 1] = char(0xDE, floor(n / 0x100), n % 0x100) -- map16
elseif n <= 4294967295.0 then
buffer[#buffer + 1] = char(0xDF, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- map32
else
error("overflow in pack 'map'")
end
for k, v in pairs(tbl) do
packers[type(k)](buffer, k)
packers[type(v)](buffer, v)
end
end
packers["array"] = function(buffer, tbl, n)
if n <= 0x0F then
buffer[#buffer + 1] = char(0x90 + n) -- fixarray
elseif n <= 0xFFFF then
buffer[#buffer + 1] = char(0xDC, floor(n / 0x100), n % 0x100) -- array16
elseif n <= 4294967295.0 then
buffer[#buffer + 1] = char(0xDD, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- array32
else
error("overflow in pack 'array'")
end
for i = 1, n do
local v = tbl[i]
packers[type(v)](buffer, v)
end
end
local set_array = function(array)
if array == "without_hole" then
packers["_table"] = function(buffer, tbl)
local is_map, n, max = false, 0, 0
for k in pairs(tbl) do
if isnumber(k) and k > 0 then
if k > max then
max = k
end
else
is_map = true
end
n = n + 1
end
-- there are holes
if max ~= n then
is_map = true
end
if is_map then
packers["map"](buffer, tbl, n)
else
packers["array"](buffer, tbl, n)
end
end
elseif array == "with_hole" then
packers["_table"] = function(buffer, tbl)
local is_map, n, max = false, 0, 0
for k in pairs(tbl) do
if isnumber(k) and k > 0 then
if k > max then
max = k
end
else
is_map = true
end
n = n + 1
end
if is_map then
packers["map"](buffer, tbl, n)
else
packers["array"](buffer, tbl, max)
end
end
elseif array == "always_as_map" then
packers["_table"] = function(buffer, tbl)
local n = 0
for k in pairs(tbl) do
n = n + 1
end
packers["map"](buffer, tbl, n)
end
else
argerror("set_array", 1, "invalid option '" .. array .. "'")
end
end
m.set_array = set_array
packers["table"] = function(buffer, tbl)
packers["_table"](buffer, tbl)
end
packers["unsigned"] = function(buffer, n)
if n >= 0 then
if n <= 0x7F then
buffer[#buffer + 1] = char(n) -- fixnum_pos
elseif n <= 0xFF then
buffer[#buffer + 1] = char(0xCC, n) -- uint8
elseif n <= 0xFFFF then
buffer[#buffer + 1] = char(0xCD, floor(n / 0x100), n % 0x100) -- uint16
elseif n <= 4294967295.0 then
buffer[#buffer + 1] = char(0xCE, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- uint32
else
buffer[#buffer + 1] = char(0xCF, 0, floor(n / 0x1000000000000) % 0x100, floor(n / 0x10000000000) % 0x100, floor(n / 0x100000000) % 0x100, floor(n / 0x1000000) % 0x100, floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- uint64 -- only 53 bits from double
end
else
if n >= -0x20 then
buffer[#buffer + 1] = char(0x100 + n) -- fixnum_neg
elseif n >= -0x80 then
buffer[#buffer + 1] = char(0xD0, 0x100 + n) -- int8
elseif n >= -0x8000 then
n = 0x10000 + n
buffer[#buffer + 1] = char(0xD1, floor(n / 0x100), n % 0x100) -- int16
elseif n >= -0x80000000 then
n = 4294967296.0 + n
buffer[#buffer + 1] = char(0xD2, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- int32
else
buffer[#buffer + 1] = char(0xD3, 0xFF, floor(n / 0x1000000000000) % 0x100, floor(n / 0x10000000000) % 0x100, floor(n / 0x100000000) % 0x100, floor(n / 0x1000000) % 0x100, floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- int64 -- only 53 bits from double
end
end
end
packers["signed"] = function(buffer, n)
if n >= 0 then
if n <= 0x7F then
buffer[#buffer + 1] = char(n) -- fixnum_pos
elseif n <= 0x7FFF then
buffer[#buffer + 1] = char(0xD1, floor(n / 0x100), n % 0x100) -- int16
elseif n <= 0x7FFFFFFF then
buffer[#buffer + 1] = char(0xD2, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- int32
else
buffer[#buffer + 1] = char(0xD3, 0, floor(n / 0x1000000000000) % 0x100, floor(n / 0x10000000000) % 0x100, floor(n / 0x100000000) % 0x100, floor(n / 0x1000000) % 0x100, floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- int64 -- only 53 bits from double
end
else
if n >= -0x20 then
buffer[#buffer + 1] = char(0xE0 + 0x20 + n) -- fixnum_neg
elseif n >= -0x80 then
buffer[#buffer + 1] = char(0xD0, 0x100 + n) -- int8
elseif n >= -0x8000 then
n = 0x10000 + n
buffer[#buffer + 1] = char(0xD1, floor(n / 0x100), n % 0x100) -- int16
elseif n >= -0x80000000 then
n = 4294967296.0 + n
buffer[#buffer + 1] = char(0xD2, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- int32
else
buffer[#buffer + 1] = char(0xD3, 0xFF, floor(n / 0x1000000000000) % 0x100, floor(n / 0x10000000000) % 0x100, floor(n / 0x100000000) % 0x100, floor(n / 0x1000000) % 0x100, floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- int64 -- only 53 bits from double
end
end
end
local set_integer = function(integer)
if integer == "unsigned" then
packers["integer"] = packers["unsigned"]
elseif integer == "signed" then
packers["integer"] = packers["signed"]
else
argerror("set_integer", 1, "invalid option '" .. integer .. "'")
end
end
m.set_integer = set_integer
packers["float"] = function(buffer, n)
local sign = 0
if n < 0.0 then
sign = 0x80
n = -n
end
local mant, expo = frexp(n)
if mant ~= mant then
buffer[#buffer + 1] = char(0xCA, 0xFF, 0x88, 0x00, 0x00) -- nan
elseif mant == huge or expo > 0x80 then
if sign == 0 then
buffer[#buffer + 1] = char(0xCA, 0x7F, 0x80, 0x00, 0x00) -- inf
else
buffer[#buffer + 1] = char(0xCA, 0xFF, 0x80, 0x00, 0x00) -- -inf
end
elseif (mant == 0.0 and expo == 0) or expo < -0x7E then
buffer[#buffer + 1] = char(0xCA, sign, 0x00, 0x00, 0x00) -- zero
else
expo = expo + 0x7E
mant = floor((mant * 2.0 - 1.0) * ldexp(0.5, 24))
buffer[#buffer + 1] = char(0xCA, sign + floor(expo / 0x2), (expo % 0x2) * 0x80 + floor(mant / 0x10000), floor(mant / 0x100) % 0x100, mant % 0x100)
end
end
packers["double"] = function(buffer, n)
local sign = 0
if n < 0.0 then
sign = 0x80
n = -n
end
local mant, expo = frexp(n)
if mant ~= mant then
buffer[#buffer + 1] = char(0xCB, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) -- nan
elseif mant == huge or expo > 0x400 then
if sign == 0 then
buffer[#buffer + 1] = char(0xCB, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) -- inf
else
buffer[#buffer + 1] = char(0xCB, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) -- -inf
end
elseif (mant == 0.0 and expo == 0) or expo < -0x3FE then
buffer[#buffer + 1] = char(0xCB, sign, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) -- zero
else
expo = expo + 0x3FE
mant = floor((mant * 2.0 - 1.0) * ldexp(0.5, 53))
buffer[#buffer + 1] = char(0xCB, sign + floor(expo / 0x10), (expo % 0x10) * 0x10 + floor(mant / 0x1000000000000), floor(mant / 0x10000000000) % 0x100, floor(mant / 0x100000000) % 0x100, floor(mant / 0x1000000) % 0x100, floor(mant / 0x10000) % 0x100, floor(mant / 0x100) % 0x100, mant % 0x100)
end
end
local set_number = function(number)
if number == "float" then
packers["number"] = function(buffer, n)
if floor(n) == n and n < maxinteger and n > mininteger then
packers["integer"](buffer, n)
else
packers["float"](buffer, n)
end
end
elseif number == "double" then
packers["number"] = function(buffer, n)
if floor(n) == n and n < maxinteger and n > mininteger then
packers["integer"](buffer, n)
else
packers["double"](buffer, n)
end
end
else
argerror("set_number", 1, "invalid option '" .. number .. "'")
end
end
m.set_number = set_number
for k = 0, 4 do
local n = tointeger(2 ^ k)
local fixext = 0xD4 + k
packers["fixext" .. tostring(n)] = function(buffer, tag, data)
assert(#data == n, "bad length for fixext" .. tostring(n))
buffer[#buffer + 1] = char(fixext, tag < 0 and tag + 0x100 or tag)
buffer[#buffer + 1] = data
end
end
packers["ext"] = function(buffer, tag, data)
local n = #data
if n <= 0xFF then
buffer[#buffer + 1] = char(0xC7, n, tag < 0 and tag + 0x100 or tag) -- ext8
elseif n <= 0xFFFF then
buffer[#buffer + 1] = char(0xC8, floor(n / 0x100), n % 0x100, tag < 0 and tag + 0x100 or tag) -- ext16
elseif n <= 4294967295.0 then
buffer[#buffer + 1] = char(0xC9, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100, tag < 0 and tag + 0x100 or tag) -- ext&32
else
error("overflow in pack 'ext'")
end
buffer[#buffer + 1] = data
end
function m.pack(data)
local buffer = {}
packers[type(data)](buffer, data)
return tconcat(buffer)
end
local unpackers -- forward declaration
local function unpack_cursor(c)
local s, i, j = c.s, c.i, c.j
if i > j then
c:underflow(i)
s, i, j = c.s, c.i, c.j
end
local val = s:byte(i)
c.i = i + 1
return unpackers[val](c, val)
end
m.unpack_cursor = unpack_cursor
local function unpack_str(c, n)
local s, i, j = c.s, c.i, c.j
local e = i + n - 1
if e > j or n < 0 then
c:underflow(e)
s, i, j = c.s, c.i, c.j
e = i + n - 1
end
c.i = i + n
return s:sub(i, e)
end
local function unpack_array(c, n)
local t = {}
for i = 1, n do
t[i] = unpack_cursor(c)
end
return t
end
local function unpack_map(c, n)
local t = {}
for i = 1, n do
local k = unpack_cursor(c)
local val = unpack_cursor(c)
if k == nil or k ~= k then
k = m.sentinel
end
if k ~= nil then
t[k] = val
end
end
return t
end
local function unpack_float(c)
local s, i, j = c.s, c.i, c.j
if i + 3 > j then
c:underflow(i + 3)
s, i, j = c.s, c.i, c.j
end
local b1, b2, b3, b4 = s:byte(i, i + 3)
local sign = b1 > 0x7F
local expo = (b1 % 0x80) * 0x2 + floor(b2 / 0x80)
local mant = ((b2 % 0x80) * 0x100 + b3) * 0x100 + b4
if sign then
sign = -1
else
sign = 1
end
local n
if mant == 0 and expo == 0 then
n = sign * 0.0
elseif expo == 0xFF then
if mant == 0 then
n = sign * huge
else
n = 0.0 / 0.0
end
else
n = sign * ldexp(1.0 + mant / 0x800000, expo - 0x7F)
end
c.i = i + 4
return n
end
local function unpack_double(c)
local s, i, j = c.s, c.i, c.j
if i + 7 > j then
c:underflow(i + 7)
s, i, j = c.s, c.i, c.j
end
local b1, b2, b3, b4, b5, b6, b7, b8 = s:byte(i, i + 7)
local sign = b1 > 0x7F
local expo = (b1 % 0x80) * 0x10 + floor(b2 / 0x10)
local mant = ((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
if sign then
sign = -1
else
sign = 1
end
local n
if mant == 0 and expo == 0 then
n = sign * 0.0
elseif expo == 0x7FF then
if mant == 0 then
n = sign * huge
else
n = 0.0 / 0.0
end
else
n = sign * ldexp(1.0 + mant / 4503599627370496.0, expo - 0x3FF)
end
c.i = i + 8
return n
end
local function unpack_uint8(c)
local s, i, j = c.s, c.i, c.j
if i > j then
c:underflow(i)
s, i, j = c.s, c.i, c.j
end
local b1 = s:byte(i)
c.i = i + 1
return b1
end
local function unpack_uint16(c)
local s, i, j = c.s, c.i, c.j
if i + 1 > j then
c:underflow(i + 1)
s, i, j = c.s, c.i, c.j
end
local b1, b2 = s:byte(i, i + 1)
c.i = i + 2
return b1 * 0x100 + b2
end
local function unpack_uint32(c)
local s, i, j = c.s, c.i, c.j
if i + 3 > j then
c:underflow(i + 3)
s, i, j = c.s, c.i, c.j
end
local b1, b2, b3, b4 = s:byte(i, i + 3)
c.i = i + 4
return ((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4
end
local function unpack_uint64(c)
local s, i, j = c.s, c.i, c.j
if i + 7 > j then
c:underflow(i + 7)
s, i, j = c.s, c.i, c.j
end
local b1, b2, b3, b4, b5, b6, b7, b8 = s:byte(i, i + 7)
c.i = i + 8
return ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
end
local function unpack_int8(c)
local s, i, j = c.s, c.i, c.j
if i > j then
c:underflow(i)
s, i, j = c.s, c.i, c.j
end
local b1 = s:byte(i)
c.i = i + 1
if b1 < 0x80 then
return b1
else
return b1 - 0x100
end
end
local function unpack_int16(c)
local s, i, j = c.s, c.i, c.j
if i + 1 > j then
c:underflow(i + 1)
s, i, j = c.s, c.i, c.j
end
local b1, b2 = s:byte(i, i + 1)
c.i = i + 2
if b1 < 0x80 then
return b1 * 0x100 + b2
else
return ((b1 - 0xFF) * 0x100 + (b2 - 0xFF)) - 1
end
end
local function unpack_int32(c)
local s, i, j = c.s, c.i, c.j
if i + 3 > j then
c:underflow(i + 3)
s, i, j = c.s, c.i, c.j
end
local b1, b2, b3, b4 = s:byte(i, i + 3)
c.i = i + 4
if b1 < 0x80 then
return ((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4
else
return ((((b1 - 0xFF) * 0x100 + (b2 - 0xFF)) * 0x100 + (b3 - 0xFF)) * 0x100 + (b4 - 0xFF)) - 1
end
end
local function unpack_int64(c)
local s, i, j = c.s, c.i, c.j
if i + 7 > j then
c:underflow(i + 7)
s, i, j = c.s, c.i, c.j
end
local b1, b2, b3, b4, b5, b6, b7, b8 = s:byte(i, i + 7)
c.i = i + 8
if b1 < 0x80 then
return ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
else
return ((((((((b1 - 0xFF) * 0x100 + (b2 - 0xFF)) * 0x100 + (b3 - 0xFF)) * 0x100 + (b4 - 0xFF)) * 0x100 + (b5 - 0xFF)) * 0x100 + (b6 - 0xFF)) * 0x100 + (b7 - 0xFF)) * 0x100 + (b8 - 0xFF)) - 1
end
end
function m.build_ext(tag, data)
return nil
end
local function unpack_ext(c, n, tag)
local s, i, j = c.s, c.i, c.j
local e = i + n - 1
if e > j or n < 0 then
c:underflow(e)
s, i, j = c.s, c.i, c.j
e = i + n - 1
end
c.i = i + n
return m.build_ext(tag, s:sub(i, e))
end
local fn_1 = function(c, val) return val end
local fn_2 = function(c, val) return unpack_map(c, val % 0x10) end
local fn_3 = function(c, val) return unpack_array(c, val % 0x10) end
local fn_4 = function(c, val) return unpack_str(c, val % 0x20) end
local fn_5 = function(c, val) return val - 0x100 end
unpackers = setmetatable({
[0xC0] = function() return nil end,
[0xC2] = function() return false end,
[0xC3] = function() return true end,
[0xC4] = function(c) return unpack_str(c, unpack_uint8(c)) end, -- bin8
[0xC5] = function(c) return unpack_str(c, unpack_uint16(c)) end, -- bin16
[0xC6] = function(c) return unpack_str(c, unpack_uint32(c)) end, -- bin32
[0xC7] = function(c) return unpack_ext(c, unpack_uint8(c), unpack_int8(c)) end,
[0xC8] = function(c) return unpack_ext(c, unpack_uint16(c), unpack_int8(c)) end,
[0xC9] = function(c) return unpack_ext(c, unpack_uint32(c), unpack_int8(c)) end,
[0xCA] = unpack_float,
[0xCB] = unpack_double,
[0xCC] = unpack_uint8,
[0xCD] = unpack_uint16,
[0xCE] = unpack_uint32,
[0xCF] = unpack_uint64,
[0xD0] = unpack_int8,
[0xD1] = unpack_int16,
[0xD2] = unpack_int32,
[0xD3] = unpack_int64,
[0xD4] = function(c) return unpack_ext(c, 1, unpack_int8(c)) end,
[0xD5] = function(c) return unpack_ext(c, 2, unpack_int8(c)) end,
[0xD6] = function(c) return unpack_ext(c, 4, unpack_int8(c)) end,
[0xD7] = function(c) return unpack_ext(c, 8, unpack_int8(c)) end,
[0xD8] = function(c) return unpack_ext(c, 16, unpack_int8(c)) end,
[0xD9] = function(c) return unpack_str(c, unpack_uint8(c)) end,
[0xDA] = function(c) return unpack_str(c, unpack_uint16(c)) end,
[0xDB] = function(c) return unpack_str(c, unpack_uint32(c)) end,
[0xDC] = function(c) return unpack_array(c, unpack_uint16(c)) end,
[0xDD] = function(c) return unpack_array(c, unpack_uint32(c)) end,
[0xDE] = function(c) return unpack_map(c, unpack_uint16(c)) end,
[0xDF] = function(c) return unpack_map(c, unpack_uint32(c)) end
}, {
__index = function(t, k)
if k < 0xC0 then
if k < 0x80 then
return fn_1
elseif k < 0x90 then
return fn_2
elseif k < 0xA0 then
return fn_3
else
return fn_4
end
elseif k > 0xDF then
return fn_5
else
return function()
error("unpack '" .. format("%#x", k) .. "' is unimplemented")
end
end
end
})
local function cursor_string(str)
return {
s = str,
i = 1,
j = #str,
underflow = function()
error"missing bytes"
end
}
end
local function cursor_loader(ld)
return {
s = '',
i = 1,
j = 0,
underflow = function(self, e)
self.s = self.s:sub(self.i)
e = e - self.i + 1
self.i = 1
self.j = 0
while e > self.j do
local chunk = ld()
if not chunk then
error"missing bytes"
end
self.s = self.s .. chunk
self.j = #self.s
end
end
}
end
function m.unpack(s)
checktype("unpack", 1, s, "string")
local cursor = cursor_string(s)
local data = unpack_cursor(cursor)
if cursor.i <= cursor.j then
error("extra bytes")
end
return data
end
function m.unpacker(src)
if type(src) == "string" then
local cursor = cursor_string(src)
return function()
if cursor.i <= cursor.j then return cursor.i, unpack_cursor(cursor) end
end
elseif type(src) == "function" then
local cursor = cursor_loader(src)
return function()
if cursor.i > cursor.j then
pcall(cursor.underflow, cursor, cursor.i)
end
if cursor.i <= cursor.j then return true, unpack_cursor(cursor) end
end
else
argerror("unpacker", 1, "string or function expected, got " .. type(src))
end
end
set_string("string")
set_integer("unsigned")
if SIZEOF_NUMBER == 4 then
maxinteger = 16777215
mininteger = -maxinteger
m.small_lua = true
unpackers[0xCB] = nil -- double
unpackers[0xCF] = nil -- uint64
unpackers[0xD3] = nil -- int64
set_number("float")
else
maxinteger = 9007199254740991
mininteger = -maxinteger
set_number("double")
if SIZEOF_NUMBER > 8 then
m.long_double = true
end
end
set_array("always_as_map")
m._VERSION = "0.5.2"
m._DESCRIPTION = "lua-MessagePack : a pure Lua implementation"
m._COPYRIGHT = "Copyright (c) 2012-2019 Francois Perrad"
return m
--
-- This library is licensed under the terms of the MIT/X11 license,
-- like Lua itself.
--

View File

@@ -0,0 +1,53 @@
--[[
| 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 SAM_LOADED then return end
local netstream = sam.netstream
netstream.async = {}
if SERVER then
local IsValid = IsValid
function netstream.async.Hook(name, fn, check)
netstream.Hook(name, function(ply, i, ...)
if not sam.isnumber(i) then return end
local res = function(...)
if IsValid(ply) then
netstream.Start(ply, name, i, ...)
end
end
fn(res, ply, ...)
end, check)
end
else
local count = 0
local receivers = {}
local hook_fn = function(i, ...)
local receiver = receivers[i]
if receiver[2] then
receiver[2]()
end
receiver[1]:resolve(...)
receivers[i] = nil
end
function netstream.async.Start(name, func_to_call, ...)
local promise = sam.Promise.new()
count = count + 1
receivers[count] = {promise, func_to_call}
netstream.Hook(name, hook_fn)
if func_to_call then
func_to_call()
end
netstream.Start(name, count, ...)
return promise
end
end

111
lua/sam/libs/sh_globals.lua Normal file
View File

@@ -0,0 +1,111 @@
--[[
| 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 SAM_LOADED then return end
local sam, netstream = sam, sam.netstream
local globals
if SERVER then
globals = {}
local order = {}
local get_order_key = function(key)
for i = 1, #order do
if order[i] == key then
return i
end
end
end
function sam.set_global(key, value, force)
if force or globals[key] ~= value then
globals[key] = value
if value ~= nil then
if not get_order_key(key) then
table.insert(order, key)
end
else
local i = get_order_key(key)
if i then
table.remove(order, i)
end
end
netstream.Start(nil, "SetGlobal", key, value)
end
end
hook.Add("OnEntityCreated", "SAM.Globals", function(ent)
if ent:IsPlayer() and ent:IsValid() then
local delay = FrameTime() * 5
local first = true
for _, key in ipairs(order) do
if (key == "Ranks") then
for rank, value in pairs(globals[key]) do
timer.Simple(delay, function() netstream.Start(ent, "SendRank", rank, value) end)
delay = delay + FrameTime() * 2
end
timer.Simple(delay, function() netstream.Start(ent, "SendRank", "Last") end)
delay = delay + FrameTime() * 5
else
if (first) then
timer.Simple(delay, function() netstream.Start(ent, "SendGlobals", {[key] = globals[key]}, {key}) end)
first = false
else
timer.Simple(delay, function() netstream.Start(ent, "SetGlobal", key, globals[key]) end)
end
delay = delay + FrameTime() * 5
end
end
end
end)
end
if CLIENT then
function sam.set_global(key, value)
if globals then
globals[key] = value
hook.Call("SAM.ChangedGlobalVar", nil, key, value)
end
end
netstream.Hook("SetGlobal", sam.set_global)
netstream.Hook("SendGlobals", function(vars, order)
globals = vars
for _, key in ipairs(order) do
hook.Call("SAM.ChangedGlobalVar", nil, key, vars[key])
end
end)
netstream.Hook("SendRank", function(rank, value)
if (rank ~= "Last" and value) then
globals = globals or {}
globals["Ranks"] = globals["Ranks"] or {}
globals["Ranks"][rank] = value
elseif (rank == "Last") then
hook.Call("SAM.ChangedGlobalVar", nil, "Ranks", globals["Ranks"])
end
end)
end
function sam.get_global(key, default)
if globals then
local value = globals[key]
if value ~= nil then
return value
end
end
return default
end

157
lua/sam/libs/sh_mp.lua Normal file
View File

@@ -0,0 +1,157 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local mp = sam.load_file("sam/libs/message_pack/sh_messagepack.lua")
local EXT_VECTOR = 1
local EXT_ANGLE = 2
local EXT_ENTITY = 3
local EXT_PLAYER = 4
local EXT_COLOR = 5
local EXT_CONSOLE = 6
mp.packers["Entity"] = function(buffer, ent)
local buf = {}
mp.packers["number"](buf, ent:EntIndex())
mp.packers["ext"](buffer, EXT_ENTITY, buf[1])
end
mp.packers["Vehicle"] = mp.packers["Entity"]
mp.packers["Weapon"] = mp.packers["Entity"]
mp.packers["NPC"] = mp.packers["Entity"]
mp.packers["NextBot"] = mp.packers["Entity"]
mp.packers["PhysObj"] = mp.packers["Entity"]
mp.packers["Player"] = function(buffer, ply)
local buf = {}
mp.packers["number"](buf, ply:UserID())
mp.packers["ext"](buffer, EXT_PLAYER, buf[1])
end
local VECTOR = {}
mp.packers["Vector"] = function(buffer, vec)
VECTOR[1] = vec.x
VECTOR[2] = vec.y
VECTOR[3] = vec.z
local buf = {}
mp.packers["_table"](buf, VECTOR)
mp.packers["ext"](buffer, EXT_VECTOR, table.concat(buf))
end
local ANGLE = {}
mp.packers["Angle"] = function(buffer, ang)
ANGLE[1] = ang.p
ANGLE[2] = ang.y
ANGLE[3] = ang.r
local buf = {}
mp.packers["_table"](buf, ANGLE)
mp.packers["ext"](buffer, EXT_ANGLE, table.concat(buf))
end
local COLOR = {}
mp.packers["Color"] = function(buffer, col)
COLOR[1] = col.r
COLOR[2] = col.g
COLOR[3] = col.b
COLOR[4] = col.a
local buf = {}
mp.packers["_table"](buf, COLOR)
mp.packers["ext"](buffer, EXT_COLOR, table.concat(buf))
end
mp.packers["console"] = function(buffer)
mp.packers["ext"](buffer, EXT_CONSOLE, "")
end
local Entity = Entity
local _Player
local Color = Color
local Vector = Vector
local Angle = Angle
local unpackers = {
[EXT_ENTITY] = function(v)
return Entity(v)
end,
[EXT_PLAYER] = function(v)
return _Player(v)
end,
[EXT_VECTOR] = function(v)
return Vector(v[1], v[2], v[3])
end,
[EXT_ANGLE] = function(v)
return Angle(v[1], v[2], v[3])
end,
[EXT_COLOR] = function(v)
return Color(v[1], v[2], v[3], v[4])
end,
[EXT_CONSOLE] = function(v)
return sam.console
end
}
local Player = Player
if CLIENT then
local players = {}
local Name = function(s)
return s.name
end
_Player = function(id)
local ply = Player(id)
if not IsValid(ply) then
local name = players[id]
if name then
return {
name = name,
Name = Name
}
end
end
return ply
end
hook.Add("OnEntityCreated", "SAM.GetPlayerName", function(ent)
if ent:IsPlayer() and ent:IsValid() then
ent.sam_userid = ent:UserID() -- userid is -1 in EntityRemoved?????
end
end)
hook.Add("EntityRemoved", "SAM.GetPlayerName", function(ent)
if not ent:IsPlayer() then return end
local id = ent.sam_userid
if not id then return end
players[id] = ent:Name()
timer.Simple(60, function()
if not IsValid(ent) then
players[id] = nil
end
end)
end)
else
_Player = Player
end
mp.build_ext = function(tag, data)
local f = mp.unpacker(data)
local _, v = f()
return unpackers[tag](v)
end
sam.mp = mp

View File

@@ -0,0 +1,177 @@
--[[
| 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 SAM_LOADED then return end
--[[
NetStream - 2.0.1
https://github.com/alexgrist/NetStream/blob/master/netstream2.lua
Alexander Grist-Hucker
http://www.revotech.org
]]--
--[[
if SERVER then
netstream.Hook("Hi", function(ply, ...) -- Third argument is called to check if the player has permission to send the net message before decoding
print(...)
end, function(ply)
if not ply:IsAdmin() then
return false
end
end)
-- OR
netstream.Hook("Hi", function(ply, ...)
print(...)
end)
netstream.Start(Entity(1), "Hi", "a", 1, {}, true, false, nil, "!") -- First argument player or table of players or any other argument to send to all players
netstream.Start({Entity(1), Entity(2)}, "Hi", "a", 1, {}, true, false, nil, "!")
netstream.Start(nil, "Hi", "a", 1, {}, true, false, nil, "!")
end
if CLIENT then
netstream.Hook("Hi", function(...)
print(...)
end)
netstream.Start("Hi", "a", 1, {}, true, false, nil, "!")
end
]]--
-- Config
local addonName = "SAM"
local mainTable = sam -- _G.netstream = netstream
local mp = sam.mp
--
local type = sam.type
local pcall = pcall
local unpack = unpack
local net = net
local table_maxn = table.maxn
local netStreamSend = addonName .. ".NetStreamDS.Sending"
local netstream = {}
if istable(mainTable) then
mainTable.netstream = netstream
end
local checks = {}
local receivers = {}
local concat = table.concat
local pack = function(t, n)
local buffer = {}
mp.packers["array"](buffer, t, n)
return concat(buffer)
end
if SERVER then
util.AddNetworkString(netStreamSend)
-- local str_sub = string.sub
-- local function Split(str, buffer, result)
-- if not result then
-- result = {}
-- end
-- if not buffer then
-- buffer = 32768
-- end
-- local len = #str
-- if len >= buffer then
-- result[#result + 1] = str_sub(str, 1, buffer - 1)
-- str = str_sub(str, buffer, len)
-- else
-- result[#result + 1] = str
-- return result
-- end
-- return Split(str, buffer, result)
-- end
local player_GetAll = player.GetAll
function netstream.Start(ply, name, ...)
local ply_type = type(ply)
if ply_type ~= "Player" and ply_type ~= "table" then
ply = player_GetAll()
end
local encoded_data = pack({...}, select("#", ...))
local length = #encoded_data
net.Start(netStreamSend)
net.WriteString(name)
net.WriteUInt(length, 17)
net.WriteData(encoded_data, length)
net.Send(ply)
end
function netstream.Hook(name, callback, check)
receivers[name] = callback
if type(check) == "function" then
checks[name] = check
end
end
net.Receive(netStreamSend, function(_, ply)
local name = net.ReadString()
local callback = receivers[name]
if not callback then return end
local length = net.ReadUInt(17)
local check = checks[name]
if check and check(ply, length) == false then return end
local data = net.ReadData(length)
local status
status, data = pcall(mp.unpack, data)
if not status or not sam.istable(data) then return end
callback(ply, unpack(data, 1, table_maxn(data)))
end)
else
checks = nil
function netstream.Start(name, ...)
local encoded_data = pack({...}, select("#", ...))
local length = #encoded_data
net.Start(netStreamSend)
net.WriteString(name)
net.WriteUInt(length, 17)
net.WriteData(encoded_data, length)
net.SendToServer()
end
function netstream.Hook(name, callback)
receivers[name] = callback
end
net.Receive(netStreamSend, function()
local callback = receivers[net.ReadString()]
if not callback then return end
local length = net.ReadUInt(17)
local data = net.ReadData(length)
data = mp.unpack(data)
callback(unpack(data, 1, table_maxn(data)))
end)
end
return netstream

458
lua/sam/libs/sh_pon.lua Normal file
View File

@@ -0,0 +1,458 @@
--[[
| 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 SAM_LOADED then return end
--[[
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 = {}
sam.pon = pon
do
local type = sam.type
local IsColor = IsColor
local tonumber = tonumber
local format = string.format
local encode = {}
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
-- 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
elseif IsColor(v) then
self.Color(self, v, output, cache)
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)
if not self[tk] or not self[tv] then continue end
-- 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
elseif IsColor(v) then
self.Color(self, v, output, cache)
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
elseif IsColor(v) then
self.Color(self, v, output, cache)
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['Color'] = function(self, val, output)
output[#output + 1] = ('C' .. val.r .. ',' .. val.g .. ',' .. val.b) .. (',' .. val.a .. ';')
end
encode['console'] = function(self, val, output)
output[#output + 1] = 's'
end
encode['nil'] = function(self, val, output)
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 concat = table.concat
function pon.encode(tbl)
local output = {nil, nil, nil, nil, nil, nil, nil, nil}
cacheSize = 0
encode['table'](encode, tbl, output, {})
return concat(output)
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)
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)
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)
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)
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)
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)
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
-- COLOR
decode['C'] = function(self, index, str)
local finish = find(str, ';', index, true)
local colStr = sub(str, index, finish - 1)
index = finish + 1 -- update the index.
local segs = Explode(',', colStr, false)
return index, Color(segs[1], segs[2], segs[3], segs[4])
end
-- PLAYER
decode['P'] = function(self, index, str)
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) return index + 1, nil end
-- SAM CONSOLE
decode['s'] = function(self, index) return index, sam.console end
function pon.decode(data)
local _, res = decode[sub(data, 1, 1)](decode, 2, data, {})
return res
end
end

View File

@@ -0,0 +1,76 @@
--[[
| 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 SAM_LOADED then return end
-- not real promises, just really simple one
local isfunction = sam and sam.isfunction or isfunction
local null = {}
local Promise = {}
local PromiseMethods = {}
local Promise_meta = {__index = PromiseMethods}
function Promise.new()
return setmetatable({
value = null,
null = null
}, Promise_meta)
end
function Promise.IsPromise(v)
return getmetatable(v) == Promise_meta
end
function PromiseMethods:resolve(v)
if self.value ~= null then return end
if self.done_callback then
self.done_callback(v)
else
self.value = v
self.callback = 0
end
end
function PromiseMethods:reject(v)
if self.value ~= null then return end
if self.catch_callback then
self.catch_callback(v)
else
self.value = v
self.callback = 1
end
end
function PromiseMethods:done(func)
if isfunction(func) then
if self.value ~= null and self.callback == 0 then
func(self.value)
else
self.done_callback = func
end
end
return self
end
function PromiseMethods:catch(func)
if isfunction(func) then
if self.value ~= null and self.callback == 1 then
func(self.value)
else
self.catch_callback = func
end
end
return self
end
return Promise

94
lua/sam/libs/sh_types.lua Normal file
View File

@@ -0,0 +1,94 @@
--[[
| 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 SAM_LOADED then return end
-- https://gist.github.com/CapsAdmin/0d9c1e77d0fc22d910e182bfeb9812e5
local getmetatable = getmetatable
do
local types = {
["string"] = "",
["boolean"] = true,
["number"] = 0,
["function"] = function() end,
["thread"] = coroutine.create(getmetatable),
["Color"] = Color(0, 0, 0),
}
for k, v in pairs(types) do
if not getmetatable(v) then
debug.setmetatable(v, {MetaName = k})
else
getmetatable(v).MetaName = k
end
end
end
function sam.type(value)
if value == nil then
return "nil"
end
local meta = getmetatable(value)
if meta then
meta = meta.MetaName
if meta then
return meta
end
end
return "table"
end
do
local function add(name)
local new_name = name
if name == "bool" then
new_name = "boolean"
end
sam["is" .. name:lower()] = function(value)
local meta = getmetatable(value)
if meta and meta.MetaName == new_name then
return true
else
return false
end
end
end
add("string")
add("number")
add("bool")
add("function")
add("Angle")
add("Vector")
add("Panel")
add("Matrix")
end
function sam.isentity(value)
local meta = getmetatable(value)
if meta then
if meta.MetaName == "Entity" then
return true
end
meta = meta.MetaBaseClass
if meta then
return meta.MetaName == "Entity"
end
end
return false
end
sam.IsEntity = sam.isentity
local type = sam.type
function sam.istable(value)
return type(value) == "table"
end

View File

@@ -0,0 +1,191 @@
--[[
| 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 _SQL = sam.SQL
local _error = _SQL.Error
local traceback = debug.traceback
local _mysqloo, database = nil, nil
local SQL = {}
function SQL.Connect(callback, failed_callback, config)
if database then
local status = database:status()
if status == _mysqloo.DATABASE_CONNECTING or status == _mysqloo.DATABASE_CONNECTED then
return true
end
end
_SQL.SetConnected(false)
require("mysqloo")
if not mysqloo then
_error("mysqloo module doesn't exist, get it from https://github.com/FredyH/MySQLOO")
return false
end
_mysqloo = mysqloo
database = _mysqloo.connect(
config.Host,
config.Username,
config.Password,
config.Database,
config.Port
)
function database.onConnected()
callback()
end
function database.onConnectionFailed(_, error_text)
failed_callback(error_text)
end
database:connect()
return true
end
--
--
--
local transaction
local add_transaction = function(query)
transaction:addQuery(database:query(query))
end
function SQL.Begin()
transaction = database:createTransaction()
return add_transaction
end
function SQL.Commit(callback)
transaction.SQL_traceback = traceback("", 2)
transaction.onSuccess = callback
transaction.onError = transaction_onError
transaction:start()
transaction = nil
end
--
--
--
--
--
--
local on_query_success = function(query, data)
if query.SQL_first_row then
data = data[1]
end
query.SQL_callback(data, query.SQL_callback_obj)
end
local on_query_fail = function(query, error_text)
local status = database:status()
-- https://github.com/Kamshak/LibK/blob/master/lua/libk/server/sv_libk_database.lua#L129
if status == _mysqloo.DATABASE_NOT_CONNECTED or status == _mysqloo.DATABASE_CONNECTING or error_text:find("Lost connection to MySQL server during query", 1, true) then
_SQL.SetConnected(false)
SQL.Query(query.SQL_query_string, query.SQL_callback, query.SQL_first_row, query.SQL_callback_obj)
else
-- 3cb9b992975d0cc0ba1b28f92ab5d1b700a08080a59b058f1424736060a73552
_error("Query error: " .. error_text, query.SQL_traceback)
end
end
function SQL.Query(query, callback, first_row, callback_obj)
local status = database:status()
if status == _mysqloo.DATABASE_NOT_CONNECTED or status == _mysqloo.DATABASE_INTERNAL_ERROR then
_SQL.Connect()
database:wait()
end
local query_string = query
query = database:query(query)
query.SQL_query_string = query_string
if callback then
query.onSuccess = on_query_success
query.SQL_callback = callback
query.SQL_first_row = first_row
query.SQL_callback_obj = callback_obj
end
query.SQL_traceback = traceback("", 2)
query.onError = on_query_fail
query:start()
return query
end
-- local prepared_set_values = function(prepared_query, values)
-- for i = 1, #values do
-- local v = values[i]
-- local value_type = type(v)
-- if value_type == "string" then
-- prepared_query:setString(i, v)
-- elseif value_type == "number" then
-- prepared_query:setNumber(i, v)
-- else
-- error(
-- string.format(
-- "%s invalid type '%s' was passed to escape '%s'",
-- "(" .. SQL.GetAddonName() .. " | MySQL)",
-- value_type,
-- v
-- )
-- )
-- end
-- end
-- end
-- function SQL.Prepare(query, callback, first_row, callback_obj)
-- local prepared_query = database:prepare(query)
-- prepared_query.SetValues = prepared_set_values
-- if callback then
-- prepared_query.onSuccess = on_query_success
-- prepared_query.SQL_callback = callback
-- prepared_query.SQL_first_row = first_row
-- prepared_query.SQL_callback_obj = callback_obj
-- end
-- prepared_query.SQL_traceback = traceback("", 2)
-- prepared_query.onError = on_query_fail
-- return prepared_query
-- end
--
--
--
function SQL.EscapeString(value, no_quotes)
if no_quotes then
return database:escape(value)
else
return "'" .. database:escape(value) .. "'"
end
end
function SQL.TableExistsQuery(name)
return "SHOW TABLES LIKE " .. SQL.EscapeString(name)
end
return SQL

View File

@@ -0,0 +1,148 @@
--[[
| 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 _SQL = sam.SQL
local _error = sam.SQL.Error
local sql_query = sql.Query
local SQL = {}
function SQL.Connect(callback)
timer.Simple(0, callback)
return true
end
--
--
--
local transactions
local add_transaction = function(query)
table.insert(transactions, query)
end
function SQL.Begin()
transactions = {}
sql_query("BEGIN TRANSACTION")
return add_transaction
end
function SQL.Commit(callback)
for i = 1, #transactions do
if sql_query(transactions[i]) == false then
sql_query("ROLLBACK TRANSACTION")
transactions = nil
_error("Transaction error: " .. sql.LastError())
return
end
end
transactions = nil
sql_query("COMMIT TRANSACTION")
if callback then
callback()
end
end
--
--
--
--
--
--
local query_obj = {
wait = function() end -- mysqloo has query:wait()
}
function SQL.Query(query, callback, first_row, callback_obj)
local data = sql_query(query)
if data == false then
_error("Query error: " .. sql.LastError())
elseif callback then
if data == nil then
if not first_row then
data = {}
end
elseif first_row then
data = data[1]
end
callback(data, callback_obj)
end
return query_obj
end
-- local concat = table.concat
-- local prepared_set_values = function(prepared_query, values)
-- for i = 1, prepared_query.args_n do
-- prepared_query[prepared_query[-i]] = _SQL.Escape(values[i])
-- end
-- return concat(prepared_query, "", 1, prepared_query.n)
-- end
-- local prepared_start = function()
-- end
-- local sub, find = string.sub, string.find
-- function SQL.Prepare(query, callback, first_row, callback_obj)
-- local prepared_query = {}
-- prepared_query.wait = query_obj.wait
-- prepared_query.Start = prepared_start
-- prepared_query.SetValues = prepared_set_values
-- local count, args_n = 0, 0
-- local pos, start, _end = 0, nil, 0
-- while true do
-- start, _end = find(query, "?", _end + 1, true)
-- if not start then
-- break
-- end
-- if pos ~= start then
-- count = count + 1; prepared_query[count] = sub(query, pos, start - 1)
-- end
-- count = count + 1; prepared_query[count] = "NULL"
-- args_n = args_n - 1; prepared_query[args_n] = count
-- pos = _end + 1
-- end
-- if pos <= #query then
-- count = count + 1; prepared_query[count] = sub(query, pos)
-- end
-- prepared_query.n = count
-- prepared_query.args_n = abs(args_n)
-- return prepared_query
-- end
--
--
--
local SQLStr = SQLStr
function SQL.EscapeString(value, no_quotes)
return SQLStr(value, no_quotes)
end
function SQL.TableExistsQuery(name)
return "SELECT `name` FROM `sqlite_master` WHERE `name` = " .. SQL.EscapeString(name) .. " AND `type` = 'table'"
end
--
--
--
return SQL

View File

@@ -0,0 +1,152 @@
--[[
| 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 SAM_LOADED then return end
local format, isstring, istable, tonumber = string.format, sam.isstring, sam.istable, tonumber
local config = {}
local SQL, _SQL = {}, nil
function SQL.Print(...)
MsgC(
Color(255, 255, 255), "(",
Color(244, 67, 54), SQL.GetAddonName(),
Color(255, 255, 255), " | ",
Color(244, 67, 54), SQL.IsMySQL() and "MySQL" or "SQLite",
Color(255, 255, 255), ") ",
...
)
Msg("\n")
end
function SQL.Error(err, trace)
SQL.Print(err, trace or debug.traceback("", 2))
end
function SQL.Connect()
return _SQL.Connect(SQL.OnConnected, SQL.OnConnectionFailed, config)
end
do
local in_transaction, old_query = false, nil
function SQL.Begin()
if in_transaction then
return SQL.Error("transaction on going!")
end
in_transaction = true
SQL.Query, old_query = _SQL.Begin(), SQL.Query
end
function SQL.Commit(callback)
if not in_transaction then return end
in_transaction = false
SQL.Query, old_query = old_query, nil
return _SQL.Commit(callback)
end
end
local gsub = string.gsub
function SQL.FQuery(query, args, callback, first_row, callback_obj)
query = gsub(query, "{(%d)(f?)}", function(i, no_escape)
return SQL.Escape(args[tonumber(i)], no_escape ~= "")
end)
return SQL.Query(query, callback, first_row, callback_obj)
end
do
local table_exists = function(data, callback)
callback(data and true or false)
end
function SQL.TableExists(name, callback)
return SQL.Query(_SQL.TableExistsQuery(name), table_exists, true, callback)
end
end
function SQL.IsMySQL()
return config.MySQL == true
end
do
local connected = false
function SQL.IsConnected()
return connected
end
function SQL.SetConnected(is_connected)
connected = is_connected
end
end
function SQL.Escape(value, no_quotes)
local value_type = type(value)
if value_type == "string" then
return _SQL.EscapeString(value, no_quotes)
elseif value_type == "number" then
return value
else
error(
format(
"%s invalid type '%s' was passed to escape '%s'",
"(" .. SQL.GetAddonName() .. " | " .. (SQL.IsMySQL() and "MySQL" or "SQLite") .. ")",
value_type,
value
)
)
end
end
function SQL.OnConnected()
SQL.SetConnected(true)
hook.Call(SQL.GetAddonName() .. ".DatabaseConnected")
end
function SQL.OnConnectionFailed(error_text)
SQL.Error("Failed to connect to the server: " .. error_text)
hook.Call(SQL.GetAddonName() .. ".DatabaseConnectionFailed", nil, error_text)
end
function SQL.SetConfig(new_config)
if not istable(new_config) then return end
if new_config.MySQL == true then
for _, v in ipairs({"Host", "Username", "Password", "Database"}) do
if not isstring(new_config[v]) then
return SQL.Error(
format("config value for '%s' is invalid '%s' needs to be a string!", v, config[v])
)
end
end
new_config.Port = tonumber(new_config.Port) or 3306
_SQL = sam.load_file("sam/libs/sql/databases/mysql.lua", "sv_")
else
_SQL = sam.load_file("sam/libs/sql/databases/sqlite.lua", "sv_")
end
SQL.Query = _SQL.Query
config = new_config
end
do
local addon_name = "NO NAME"
function SQL.SetAddonName(name)
addon_name = name
end
function SQL.GetAddonName()
return addon_name
end
end
sam.SQL = SQL

265
lua/sam/menu/cl_init.lua Normal file
View File

@@ -0,0 +1,265 @@
--[[
| 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 SAM_LOADED then return end
local vgui = vgui
local draw = draw
local sam = sam
local sui = sui
local TDLib = sui.TDLib
local config = sam.config
do
local funcs = {
["SAM.ComboBox"] = {
event = "OnSelect",
function(s, _, value)
config.set(s.config_key, value)
end
},
["SAM.TextEntry"] = {
event = "OnEnter",
function(s)
local v = s:GetText()
if s:GetNumeric() then
v = tonumber(v)
end
config.set(s.config_key, v)
end
},
["SAM.ToggleButton"] = {
event = "OnChange",
function(s, v)
config.set(s.config_key, v)
end
}
}
sam.SUI = sam.SUI or sui.new("SAM", true, {
SetConfig = function(s, key, default)
s.config_key = key
local i = config.hook({key}, function(value, old)
local v = config.get(key, default)
s:SetValue(v)
end)
local t = funcs[s:GetName()]
s[t.event] = t[1]
s:On("OnRemove", function()
config.remove_hook(i)
end)
end
})
end
local SUI = sam.SUI
local GetColor = SUI.GetColor
sam.menu = {}
local tabs = {}
function sam.menu.add_tab(icon, func, check, pos)
local tab = {
icon = icon,
func = func,
check = check,
pos = pos
}
for k, v in ipairs(tabs) do
if v.icon == icon then
tabs[k] = tab
return
end
end
table.insert(tabs, tab)
end
function sam.menu.remove_tab(name)
for k, v in ipairs(tabs) do
if v.name == name then
table.remove(tabs, k)
break
end
end
end
SAM_TAB_TITLE_FONT = SUI.CreateFont("TabTitle", "Roboto Bold", 22)
SAM_TAB_DESC_FONT = SUI.CreateFont("TabDesc", "Roboto Medium", 15)
local MENU_LOADING = SUI.CreateFont("MenuLoading", "Roboto", 30)
SUI.AddToTheme("Dark", {
frame = "#181818",
scroll_panel = "#181818",
menu_tabs_title = "#ffffff",
--=--
player_list_titles = "#f2f1ef",
player_list_names = "#eeeeee",
player_list_names_2 = "#ff6347",
player_list_data = "#e8e8e8",
player_list_rank = "#41b9ff",
player_list_console = "#00c853",
player_list_rank_text = "#2c3e50",
player_list_steamid = "#a4a4a4",
--=--
--=--
actions_button = Color(0, 0, 0, 0),
actions_button_hover = Color(200, 200, 200, 60),
actions_button_icon = "#aaaaaa",
actions_button_icon_hover = "#ffffff",
--=--
--=--
page_switch_bg = "#222222",
--=--
})
SUI.SetTheme("Dark")
function SUI.panels.Frame:Paint(w, h)
if GetColor("frame_blur") then
TDLib.BlurPanel(self)
end
draw.RoundedBox(8, 0, 0, w, h, GetColor("frame"))
end
function SUI.panels.Frame:HeaderPaint(w, h)
draw.RoundedBoxEx(8, 0, 0, w, h, GetColor("header"), true, true, false, false)
draw.RoundedBox(0, 0, h - 1, w, 1, GetColor("line"))
end
do
function sam.menu.add_loading_panel(parent)
local is_loading = false
local loading_panel = parent:Add("Panel")
loading_panel:SetVisible(false)
loading_panel:SetZPos(999999)
loading_panel:SetMouseInputEnabled(false)
function loading_panel:Paint(w, h)
draw.RoundedBox(3, 0, 0, w, h, Color(50, 50, 50, 200))
draw.SimpleText(string.rep(".", (CurTime() * 3) % 3), MENU_LOADING, w/2, h/2, Color(200, 200, 200, 200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
parent:SUI_TDLib()
parent:On("PerformLayout", function(s, w, h)
loading_panel:SetSize(w, h)
end)
local first = true
local toggle_loading = function(bool)
if not IsValid(loading_panel) then return end
is_loading = bool or not is_loading
if is_loading and not first then
loading_panel:SetVisible(is_loading and true or false)
loading_panel:SetMouseInputEnabled(is_loading)
else
timer.Simple(0.2, function()
if not IsValid(loading_panel) then return end
loading_panel:SetVisible(is_loading and true or false)
loading_panel:SetMouseInputEnabled(is_loading)
end)
end
first = false
end
return toggle_loading, function()
return is_loading
end
end
end
local sam_menu
function sam.menu.open_menu()
if IsValid(sam_menu) then
return sam_menu:IsVisible() and sam_menu:Hide() or sam_menu:Show()
-- sam_menu:Remove()
end
sam_menu = vgui.Create("SAM.Frame")
sam_menu:Center()
sam_menu:MakePopup()
sam_menu:SetTitle("SAM")
sam_menu:AddAnimations(800, 600)
sam_menu.close.DoClick = function()
sam_menu:Hide()
end
local sheet = sam_menu:Add("SAM.ColumnSheet")
sheet:Dock(FILL)
sheet:InvalidateParent(true)
sheet:InvalidateLayout(true)
sheet.Paint = nil
local tab_scroller = sheet.tab_scroller
tab_scroller:DockMargin(0, 1, 0, 1)
function tab_scroller:Paint(w, h)
draw.RoundedBoxEx(8, 0, 0, w, h, GetColor("column_sheet_bar"), false, false, true, false)
end
local sheets = {}
for _, v in SortedPairsByMemberValue(tabs, "pos") do
sheets[v.icon] = sheet:AddSheet(v.icon, v.func)
end
tab_scroller = tab_scroller:GetCanvas()
sam_menu:On("Think", function()
for _, v in ipairs(tabs) do
local tab = sheets[v.icon]
if v.check and not v.check() then
if tab:IsVisible() then
tab:SetVisible(false)
if sheet:GetActiveTab() == tab then
sheet:SetActiveTab(sheet.tabs[1])
end
tab_scroller:InvalidateLayout()
end
elseif not tab:IsVisible() then
tab:SetVisible(true)
tab_scroller:InvalidateLayout()
end
end
end)
end
function sam.menu.get()
return sam_menu
end
hook.Add("GUIMouseReleased", "SAM.CloseMenu", function(mouse_code)
local panel = vgui.GetHoveredPanel()
if mouse_code == MOUSE_LEFT and panel == vgui.GetWorldPanel() and IsValid(sam_menu) and sam_menu:HasHierarchicalFocus() then
sam_menu:Hide()
end
end)
for _, f in ipairs(file.Find("sam/menu/tabs/*.lua", "LUA")) do
sam.load_file("sam/menu/tabs/" .. f, "sh")
end

34
lua/sam/menu/sh_init.lua Normal file
View File

@@ -0,0 +1,34 @@
--[[
| 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 SAM_LOADED then return end
require("sui")
sam.command.new("menu")
:Help("Open admin mod menu")
:MenuHide()
:DisableNotify()
:OnExecute(function(ply)
sam.netstream.Start(ply, "OpenMenu")
end)
:End()
if CLIENT then
sam.netstream.Hook("OpenMenu", function()
sam.menu.open_menu()
end)
end
if SERVER then
for _, f in ipairs(file.Find("sam/menu/tabs/*.lua", "LUA")) do
sam.load_file("sam/menu/tabs/" .. f)
end
end

419
lua/sam/menu/tabs/bans.lua Normal file
View File

@@ -0,0 +1,419 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local SQL = sam.SQL
local SUI = sam.SUI
local netstream = sam.netstream
sam.permissions.add("manage_bans", nil, "superadmin")
local get_pages_count = function(bans_count)
bans_count = bans_count / 35
local i2 = math.floor(bans_count)
return bans_count ~= i2 and i2 + 1 or bans_count
end
if SERVER then
local check = function(ply)
return ply:HasPermission("manage_bans") and ply:sam_check_cooldown("MenuManageBans", 0.1)
end
local limit = 35
local get_page_count = function(res, callback, page, order_by, keyword)
local current_time = os.time()
local query = [[
SELECT
COUNT(`steamid`) AS `count`
FROM
`sam_bans`
WHERE
(`unban_date` >= %d OR `unban_date` = 0)]]
query = query:format(current_time)
if keyword then
query = query .. " AND `steamid` LIKE " .. SQL.Escape("%" .. keyword .. "%")
end
SQL.Query(query, callback, true, {res, page, order_by, keyword, current_time})
end
local resolve_promise = function(data, arguments)
local res = arguments[1]
arguments[1] = data
res(arguments)
end
local get_bans = function(count_data, arguments)
local res, page, order_by, keyword, current_time = unpack(arguments)
local count = count_data.count
local current_page
if page < 1 then
page, current_page = 1, 1
end
local pages_count = get_pages_count(count)
if page > pages_count then
page, current_page = pages_count, pages_count
end
local query = [[
SELECT
`sam_bans`.*,
IFNULL(`p1`.`name`, '') AS `name`,
IFNULL(`p2`.`name`, '') AS `admin_name`
FROM
`sam_bans`
LEFT OUTER JOIN
`sam_players` AS `p1`
ON
`sam_bans`.`steamid` = `p1`.`steamid`
LEFT OUTER JOIN
`sam_players` AS `p2`
ON
`sam_bans`.`admin` = `p2`.`steamid`
WHERE
(`sam_bans`.`unban_date` >= %d OR `sam_bans`.`unban_date` = 0)]]
query = query:format(current_time)
if keyword then
query = query .. " AND `sam_bans`.`steamid` LIKE " .. SQL.Escape("%" .. keyword .. "%")
end
local offset = math.abs(limit * (page - 1))
query = query .. ([[
ORDER BY
`sam_bans`.`id` %s
LIMIT
%d OFFSET %d]]):format(order_by, limit, offset)
SQL.Query(query, resolve_promise, nil, {res, count, current_page})
end
netstream.async.Hook("SAM.GetBans", function(res, ply, page, order_by, keyword)
if not isnumber(page) then return end
if order_by ~= "ASC" and order_by ~= "DESC" then return end
if keyword ~= nil and not sam.isstring(keyword) then return end
get_page_count(res, get_bans, page, order_by, keyword)
end, check)
return
end
local GetColor = SUI.GetColor
local Line = sui.TDLib.LibClasses.Line
local COLUMN_FONT = SUI.CreateFont("Column", "Roboto", 18)
local LINE_FONT = SUI.CreateFont("Line", "Roboto", 16)
local NEXT_FONT = SUI.CreateFont("NextButton", "Roboto", 18)
sam.menu.add_tab("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sam/ban-user.png", function(column_sheet)
local refresh, pages
local current_page, current_order, keyword = nil, "DESC", nil
local bans_body = column_sheet:Add("Panel")
bans_body:Dock(FILL)
bans_body:DockMargin(0, 1, 0, 0)
bans_body:DockPadding(10, 10, 10, 10)
local toggle_loading, is_loading = sam.menu.add_loading_panel(bans_body)
local title = bans_body:Add("SAM.Label")
title:Dock(TOP)
title:SetFont(SAM_TAB_TITLE_FONT)
title:SetText("Bans")
title:SetTextColor(GetColor("menu_tabs_title"))
title:SizeToContents()
local total = bans_body:Add("SAM.Label")
total:Dock(TOP)
total:DockMargin(0, 6, 0, 0)
total:SetFont(SAM_TAB_DESC_FONT)
total:SetText("60 total bans")
total:SetTextColor(GetColor("menu_tabs_title"))
total:SetPos(10, SUI.Scale(40))
total:SizeToContents()
do
local container = bans_body:Add("SAM.Panel")
container:Dock(TOP)
container:DockMargin(0, 6, 10, 0)
container:SetTall(30)
local sort_order = container:Add("SAM.ComboBox")
sort_order:Dock(RIGHT)
sort_order:SetWide(96)
sort_order:SetValue("Desc")
sort_order:AddChoice("Desc")
sort_order:AddChoice("Asc")
function sort_order:OnSelect(_, value)
value = value:upper()
if current_order ~= value then
current_order = value
refresh()
end
end
local search_entry = container:Add("SAM.TextEntry")
search_entry:Dock(LEFT)
search_entry:SetNoBar(true)
search_entry:SetPlaceholder("Search...")
search_entry:SetRadius(4)
search_entry:SetWide(220)
function search_entry:OnEnter()
local value = self:GetValue()
if keyword ~= value then
keyword = value ~= "" and value or nil
refresh()
end
end
end
Line(bans_body, nil, -5, 15, -5, 0)
do
local columns = bans_body:Add("Panel")
columns:Dock(TOP)
columns:DockMargin(0, 10, 0, 0)
local info = columns:Add("SAM.Label")
info:Dock(LEFT)
info:DockMargin(4, 0, 0, 0)
info:SetFont(COLUMN_FONT)
info:SetText("Player")
info:SetTextColor(GetColor("player_list_titles"))
info:SetWide(SUI.Scale(280) + SUI.Scale(34))
info:SizeToContentsY(3)
local time_left = columns:Add("SAM.Label")
time_left:Dock(LEFT)
time_left:DockMargin(-4, 0, 0, 0)
time_left:SetFont(COLUMN_FONT)
time_left:SetText("Time Left")
time_left:SetTextColor(GetColor("player_list_titles"))
time_left:SetWide(SUI.Scale(180))
time_left:SizeToContentsY(3)
local reason = columns:Add("SAM.Label")
reason:Dock(LEFT)
reason:DockMargin(-4, 0, 0, 0)
reason:SetFont(COLUMN_FONT)
reason:SetText("Reason")
reason:SetTextColor(GetColor("player_list_titles"))
reason:SetWide(SUI.Scale(280))
reason:SizeToContentsY(3)
columns:SizeToChildren(false, true)
end
local body = bans_body:Add("SAM.ScrollPanel")
body:Dock(FILL)
body:DockMargin(0, 10, 0, 0)
body:SetVBarPadding(6)
local set_data = function(data)
body:GetCanvas():Clear()
body.VBar.Scroll = 0
local bans, bans_count, current_page_2 = unpack(data)
total:SetText(bans_count .. " total bans")
pages = get_pages_count(bans_count)
current_page.i = pages == 0 and 0 or current_page_2 or current_page.i
current_page:SetText(current_page.i .. "/" .. pages)
body:Line()
for k, v in ipairs(bans) do
local line = body:Add("SAM.PlayerLine")
line:DockMargin(0, 0, 0, 10)
local name = v.name ~= "" and v.name or nil
local admin_name = v.admin_name ~= "" and v.admin_name or nil
line:SetInfo({
steamid = v.steamid,
name = name,
rank = admin_name or (v.admin == "Console" and "Console"),
rank_bg = not admin_name and GetColor("player_list_console")
})
local unban_date = tonumber(v.unban_date)
local time_left = line:Add("SAM.Label")
time_left:Dock(LEFT)
time_left:DockMargin(-3, 0, 0, 0)
time_left:SetFont(LINE_FONT)
time_left:SetText(unban_date == 0 and "Never" or sam.reverse_parse_length((unban_date - os.time()) / 60))
time_left:SetTextColor(GetColor("player_list_data"))
time_left:SetContentAlignment(4)
time_left:SetWide(SUI.Scale(180))
local reason = line:Add("SAM.Label")
reason:Dock(LEFT)
reason:DockMargin(4, 0, 0, 0)
reason:SetFont(LINE_FONT)
reason:SetText(v.reason)
reason:SetTextColor(GetColor("player_list_data"))
reason:SetContentAlignment(4)
reason:SetWrap(true)
reason:SetWide(SUI.Scale(200))
local old_tall = line.size
function reason:PerformLayout()
local _, h = self:GetTextSize()
if old_tall < h then
line:SetTall(h)
end
end
local but = line:Actions()
but:On("DoClick", function()
local dmenu = vgui.Create("SAM.Menu")
dmenu:SetInternal(but)
if name then
dmenu:AddOption("Copy Name", function()
SetClipboardText(name)
end)
end
dmenu:AddOption("Copy SteamID", function()
SetClipboardText(v.steamid)
end)
dmenu:AddOption("Copy Reason", function()
SetClipboardText(v.reason)
end)
dmenu:AddOption("Copy Time Left", function()
SetClipboardText(time_left:GetText())
end)
if v.admin ~= "Console" then
dmenu:AddSpacer()
if admin_name then
dmenu:AddOption("Copy Admin Name", function()
SetClipboardText(admin_name)
end)
end
dmenu:AddOption("Copy Admin SteamID", function()
SetClipboardText(v.admin)
end)
end
if LocalPlayer():HasPermission("unban") then
dmenu:AddSpacer()
dmenu:AddOption("Unban", function()
local user = name and ("%s (%s)"):format(name, v.steamid) or v.steamid
local querybox = vgui.Create("SAM.QueryBox")
querybox:SetWide(350)
querybox:SetTitle(user)
local check = querybox:Add("SAM.Label")
check:SetText(sui.wrap_text("Are you sure that you want to unban\n" .. user, LINE_FONT, SUI.Scale(350)))
check:SetFont(LINE_FONT)
check:SizeToContents()
querybox:Done()
querybox.save:SetEnabled(true)
querybox.save:SetText("UNBAN")
querybox.save:SetContained(false)
querybox.save:SetColors(GetColor("query_box_cancel"), GetColor("query_box_cancel_text"))
querybox.cancel:SetContained(true)
querybox.cancel:SetColors()
querybox:SetCallback(function()
RunConsoleCommand("sam", "unban", v.steamid)
end)
end)
end
dmenu:Open()
end)
body:Line()
end
body:InvalidateLayout(true)
body:GetCanvas():InvalidateLayout(true)
end
refresh = function()
if not is_loading() and LocalPlayer():HasPermission("manage_bans") then
local refresh_query = netstream.async.Start("SAM.GetBans", toggle_loading, current_page.i, current_order, keyword)
refresh_query:done(set_data)
end
end
local bottom_panel = bans_body:Add("SAM.Panel")
bottom_panel:Dock(BOTTOM)
bottom_panel:DockMargin(0, 6, 0, 0)
bottom_panel:SetTall(30)
bottom_panel:Background(GetColor("page_switch_bg"))
local previous_page = bottom_panel:Add("SAM.Button")
previous_page:Dock(LEFT)
previous_page:SetWide(30)
previous_page:SetText("<")
previous_page:SetFont(NEXT_FONT)
previous_page:On("DoClick", function()
if current_page.i <= 1 then return end
current_page.i = current_page.i - 1
refresh()
end)
current_page = bottom_panel:Add("SAM.Label")
current_page:Dock(FILL)
current_page:SetContentAlignment(5)
current_page:SetFont(SAM_TAB_DESC_FONT)
current_page:SetText("loading...")
current_page.i = 1
local next_page = bottom_panel:Add("SAM.Button")
next_page:Dock(RIGHT)
next_page:SetWide(30)
next_page:SetText(">")
next_page:SetFont(NEXT_FONT)
next_page:On("DoClick", function()
if current_page.i == pages then return end
current_page.i = current_page.i + 1
refresh()
end)
function bottom_panel:Think()
next_page:SetEnabled(current_page.i ~= pages)
previous_page:SetEnabled(current_page.i > 1)
end
for k, v in ipairs({"SAM.BannedPlayer", "SAM.BannedSteamID", "SAM.EditedBan", "SAM.UnbannedSteamID"}) do
hook.Add(v, "SAM.MenuBans", function()
if IsValid(body) then
refresh()
end
end)
end
refresh()
return bans_body
end, function()
return LocalPlayer():HasPermission("manage_bans")
end, 4)

View File

@@ -0,0 +1,233 @@
--[[
| 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 SAM_LOADED then return end
if SERVER then return end
local sam = sam
local SUI = sam.SUI
local type = sam.type
local Line = sui.TDLib.LibClasses.Line
local COMMAND_HELP = SUI.CreateFont("CommandHelp", "Roboto", 14)
local COMMAND_RUN = SUI.CreateFont("CommandRun", "Roboto Medium", 14)
sam.menu.add_tab("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sam/command_window.png", function(column_sheet)
local tab_body = column_sheet:Add("Panel")
tab_body:Dock(FILL)
tab_body:DockMargin(0, 1, 0, 0)
do
local title = tab_body:Add("SAM.Label")
title:Dock(TOP)
title:DockMargin(10, 10, 0, 0)
title:SetFont(SAM_TAB_TITLE_FONT)
title:SetText("Commands")
title:SetTextColor(SUI.GetColor("menu_tabs_title"))
title:SizeToContents()
end
local body = tab_body:Add("Panel")
body:Dock(FILL)
body:DockMargin(10, 5, 10, 10)
Line(body)
local left_body = body:Add("SAM.Panel")
left_body:Dock(LEFT)
left_body:SetWide(148)
local search_entry = left_body:Add("SAM.TextEntry")
search_entry:Dock(TOP)
search_entry:SetNoBar(true)
search_entry:SetPlaceholder("Search...")
search_entry:SetRadius(4)
search_entry:SetTall(27)
local category_list = left_body:Add("SAM.CollapseCategory")
category_list:Dock(FILL)
category_list:DockMargin(0, SUI.Scale(10), 0, 0)
local canvas = category_list:GetCanvas()
local commands_refresh = function()
if not IsValid(category_list) then return end
canvas:Clear()
table.Empty(category_list.items)
table.Empty(category_list.categories)
for k, v in ipairs(sam.command.get_commands()) do
if (v.permission and not LocalPlayer():HasPermission(v.permission)) or v.menu_hide then
continue
end
local item = category_list:add_item(v.name, v.category)
item:InvalidateParent(true)
item.help = v.help
item.command = v
item.names = {v.name:lower()}
for _, aliase in ipairs(v.aliases) do
table.insert(item.names, aliase:lower())
end
end
end
commands_refresh()
do
local hooks = {
"SAM.CommandAdded", "SAM.CommandModified", "SAM.CommandRemoved",
"SAM.RemovedPermission",
{"SAM.ChangedPlayerRank", func = function(ply, rank, old_rank)
if rank == old_rank then return end
if ply == LocalPlayer() then
commands_refresh()
end
end},
{
"SAM.RankPermissionGiven", "SAM.RankPermissionTaken", "SAM.ChangedInheritRank",
func = function(rank)
if rank == LocalPlayer():sam_getrank() then
commands_refresh()
end
end
},
{
"SAM.AddedPermission", "SAM.PermissionModified",
func = function(_, _, rank)
if rank == LocalPlayer():sam_getrank() then
commands_refresh()
end
end
}
}
for _, v in ipairs(hooks) do
if type(v) == "table" then
for _, v2 in ipairs(v) do
hook.Add(v2, "SAM.Menu.RefreshCommands", v.func)
end
else
hook.Add(v, "SAM.Menu.RefreshCommands", commands_refresh)
end
end
end
function search_entry:OnValueChange(text)
category_list:Search(text:lower())
end
do
local line = Line(body, LEFT)
line:DockMargin(10, 0, 10, 0)
line:SetWide(1)
end
local buttons = body:Add("SAM.ScrollPanel")
buttons:Dock(FILL)
local childs = {}
local pos = 0
buttons:GetCanvas():On("OnChildAdded", function(s, child)
child:Dock(TOP)
child:DockMargin(0, 0, 0, 5)
child:SetAlpha(0)
child:SetVisible(false)
table.insert(childs, child)
pos = pos + 1
child:SetZPos(pos)
end)
local run_command = buttons:Add("SAM.Button")
run_command:Dock(TOP)
run_command:SetTall(25)
run_command:SetFont(COMMAND_RUN)
run_command:SetZPos(100)
run_command:SetEnabled(false)
run_command:On("DoClick", function(self)
LocalPlayer():ConCommand("sam\"" .. self:GetText() .. "\"\"" .. table.concat(self.input_arguments, "\"\"") .. "\"")
end)
local help = buttons:Add("SAM.Label")
help:Dock(TOP)
help:SetFont(COMMAND_HELP)
help:SetZPos(101)
help:SetWrap(true)
help:SetAutoStretchVertical(true)
sam.menu.get():On("OnKeyCodePressed", function(s, key_code)
if key_code == KEY_ENTER and IsValid(run_command) and run_command:IsEnabled() and run_command:IsMouseInputEnabled() and tab_body:IsVisible() then
run_command:DoClick()
end
end)
function category_list:item_selected(item)
local arguments = sam.command.get_arguments()
local command = item.command
local command_arguments = command.args
local input_arguments = {}
for i = #childs, 3, -1 do
local v = childs[i]
if not v.no_change or not command:HasArg(v.no_change) then
if v.no_remove ~= true then
v:Remove()
else
v:Hide()
end
end
end
local NIL = {}
for _, v in ipairs(command_arguments) do
local func = arguments[v.name]["menu"]
if not func then continue end
local i = table.insert(input_arguments, NIL)
local p = func(function(allow)
if not IsValid(run_command) then return end
input_arguments[i] = allow == nil and NIL or allow
for i_2 = 1, #input_arguments do
if input_arguments[i_2] == NIL then
run_command:SetEnabled(false)
return
end
end
run_command:SetEnabled(true)
end, body, buttons, v, childs)
if p then
p:AnimatedSetVisible(true)
end
end
if #command_arguments == 0 then
run_command:SetEnabled(true)
end
run_command:SetText(command.name)
run_command:AnimatedSetVisible(true)
run_command.input_arguments = input_arguments
if command.help then
help:SetText(command.help)
help:AnimatedSetVisible(true)
help:SizeToContents()
else
help:AnimatedSetVisible(false)
end
buttons:InvalidateLayout(true)
end
return tab_body
end, nil, 1)

View File

@@ -0,0 +1,107 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local config = sam.config
local tabs = {}
if CLIENT then
function config.add_tab(name, func, check, pos)
local tab = {
name = name,
func = func,
check = check,
pos = pos
}
for k, v in ipairs(tabs) do
if v.name == name then
tabs[k] = tab
return
end
end
table.insert(tabs, tab)
end
end
for _, f in ipairs(file.Find("sam/menu/tabs/config/*.lua", "LUA")) do
sam.load_file("sam/menu/tabs/config/" .. f, "cl_")
end
if SERVER then return end
local SUI = sam.SUI
local GetColor = SUI.GetColor
local Line = sui.TDLib.LibClasses.Line
sam.menu.add_tab("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sam/config.png", function(column_sheet)
local tab_body = column_sheet:Add("Panel")
tab_body:Dock(FILL)
tab_body:DockMargin(0, 1, 0, 0)
do
local title = tab_body:Add("SAM.Label")
title:Dock(TOP)
title:DockMargin(10, 10, 0, 0)
title:SetFont(SAM_TAB_TITLE_FONT)
title:SetText("Config")
title:SetTextColor(GetColor("menu_tabs_title"))
title:SizeToContents()
local total = tab_body:Add("SAM.Label")
total:Dock(TOP)
total:DockMargin(10, 6, 0, 0)
total:SetFont(SAM_TAB_DESC_FONT)
total:SetText("Some settings may require a server restart")
total:SetTextColor(GetColor("menu_tabs_title"))
total:SetPos(10, SUI.Scale(40))
total:SizeToContents()
end
local body = tab_body:Add("Panel")
body:Dock(FILL)
body:DockMargin(10, 5, 10, 10)
Line(body, nil, 0, 0, 0, 10)
local sheet = body:Add("SAM.PropertySheet")
sheet:Dock(FILL)
sheet:InvalidateParent(true)
sheet:InvalidateLayout(true)
local sheets = {}
for _, v in SortedPairsByMemberValue(tabs, "pos") do
sheets[v.name] = sheet:AddSheet(v.name, v.func)
end
local tab_scroller = sheet.tab_scroller:GetCanvas()
function tab_body.Think()
for _, v in ipairs(tabs) do
local tab = sheets[v.name]
if v.check and not v.check() then
if tab:IsVisible() then
tab:SetVisible(false)
if sheet:GetActiveTab() == tab then
sheet:SetActiveTab(sheet.tabs[1])
end
tab_scroller:InvalidateLayout()
end
elseif not tab:IsVisible() then
tab:SetVisible(true)
tab_scroller:InvalidateLayout()
end
end
end
return tab_body
end, function()
return LocalPlayer():HasPermission("manage_config")
end, 5)

View File

@@ -0,0 +1,171 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local config = sam.config
local not_empty = function(s)
return s and s ~= ""
end
local number_entry = function(setting, config_key, default)
local entry = setting:Add("SAM.TextEntry")
entry:SetWide(50)
entry:SetPlaceholder("")
entry:SetBackground(Color(34, 34, 34))
entry:SetNumeric(true)
entry:DisallowFloats()
entry:DisallowNegative()
entry:SetCheck(not_empty)
entry:SetConfig(config_key, default)
return entry
end
config.add_tab("Reports", function(parent)
local body = parent:Add("SAM.ScrollPanel")
body:Dock(FILL)
body:LineMargin(0, 6, 0, 0)
local i = 0
body:GetCanvas():On("OnChildAdded", function(s, child)
i = i + 1
child:SetZPos(i)
if not body.making_line then
body:Line()
end
end)
do
local setting = body:Add("SAM.LabelPanel")
setting:Dock(TOP)
setting:SetLabel("Enable")
setting:DockMargin(8, 6, 8, 0)
local enable = setting:Add("SAM.ToggleButton")
enable:SetConfig("Reports", true)
end
do
local setting = body:Add("SAM.LabelPanel")
setting:Dock(TOP)
setting:SetLabel("Commands")
setting:DockMargin(8, 6, 8, 0)
local entry = setting:Add("SAM.TextEntry")
entry:SetWide(200)
entry:SetNoBar(true)
entry:SetPlaceholder("")
entry:SetMultiline(true)
entry:SetConfig("Reports.Commands")
entry.no_scale = true
function entry:OnValueChange()
self:SetTall(self:GetNumLines() * (sam.SUI.Scale(16) --[[font size]] + 1) + 1 + 2)
end
entry:OnValueChange()
end
do
local setting = body:Add("SAM.LabelPanel")
setting:Dock(TOP)
setting:SetLabel("Max Reports (Number of reports that can show on your screen)")
setting:DockMargin(8, 6, 8, 0)
number_entry(setting, "Reports.MaxReports", 4)
end
do
local setting = body:Add("SAM.LabelPanel")
setting:Dock(TOP)
setting:SetLabel("Auto Close Time (Time to wait before automatically closing claimed reports)")
setting:DockMargin(8, 6, 8, 0)
local entry = setting:Add("SAM.TextEntry")
entry:SetWide(70)
entry:SetNoBar(false)
entry:SetPlaceholder("")
entry:SetCheck(function(time)
time = sam.parse_length(time)
if not time then
return false
end
end)
entry:SetConfig("Reports.AutoCloseTime", "10m")
end
do
local setting = body:Add("SAM.LabelPanel")
setting:Dock(TOP)
setting:SetLabel("Always Show (Show the popups even if you are not on duty)")
setting:DockMargin(8, 6, 8, 0)
local enable = setting:Add("SAM.ToggleButton")
enable:SetConfig("Reports.AlwaysShow", true)
end
do
local setting = body:Add("SAM.LabelPanel")
setting:Dock(TOP)
setting:SetLabel("On Duty Jobs")
setting:DockMargin(8, 6, 8, 0)
local entry = setting:Add("SAM.TextEntry")
entry:SetWide(300)
entry:SetNoBar(true)
entry:SetPlaceholder("")
entry:SetMultiline(true)
entry:SetConfig("Reports.DutyJobs", "")
entry.no_scale = true
function entry:OnValueChange()
self:SetTall(self:GetNumLines() * (sam.SUI.Scale(16) --[[font size]] + 1) + 1 + 2)
end
entry:OnValueChange()
end
do
local setting = body:Add("SAM.LabelPanel")
setting:Dock(TOP)
setting:SetLabel("Position")
setting:DockMargin(8, 6, 8, 0)
local combo = setting:Add("SAM.ComboBox")
combo:SetWide(60)
combo:AddChoice("Left", nil, true)
combo:AddChoice("Right")
combo:SetConfig("Reports.Position", "Left")
end
do
local setting = body:Add("SAM.LabelPanel")
setting:Dock(TOP)
setting:SetLabel("X Padding")
setting:DockMargin(8, 6, 8, 0)
number_entry(setting, "Reports.XPadding", 5)
end
do
local setting = body:Add("SAM.LabelPanel")
setting:Dock(TOP)
setting:SetLabel("Y Padding")
setting:DockMargin(8, 6, 8, 0)
number_entry(setting, "Reports.YPadding", 5)
end
return body
end, function()
return LocalPlayer():HasPermission("manage_config")
end, 2)

View File

@@ -0,0 +1,42 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local config = sam.config
config.add_tab("Server", function(parent)
local server_body = parent:Add("SAM.ScrollPanel")
server_body:Dock(FILL)
server_body:LineMargin(0, 6, 0, 0)
local i = 0
server_body:GetCanvas():On("OnChildAdded", function(s, child)
i = i + 1
child:SetZPos(i)
end)
for k, v in ipairs(sam.config.get_menu_settings()) do
local panel = v.func(server_body)
if ispanel(panel) then
local setting = server_body:Add("SAM.LabelPanel")
setting:DockMargin(8, 6, 8, 0)
setting:SetLabel(v.title)
setting:SetPanel(panel)
end
server_body:Line()
end
return server_body
end, function()
return LocalPlayer():HasPermission("manage_config")
end, 1)

View File

@@ -0,0 +1,462 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local SQL = sam.SQL
local SUI = sam.SUI
local netstream = sam.netstream
sam.permissions.add("manage_players", nil, "superadmin")
local get_pages_count = function(count)
count = count / 35
local i2 = math.floor(count)
return count ~= i2 and i2 + 1 or count
end
if SERVER then
local check = function(ply)
return ply:HasPermission("manage_players") and ply:sam_check_cooldown("MenuViewPlayers", 0.1)
end
local limit = 35
local get_page_count = function(callback, res, page, column, order_by, sort_by, keyword)
local query = [[
SELECT
COUNT(`steamid`) AS `count`
FROM
`sam_players`]]
if keyword then
if column == "steamid" and sam.is_steamid64(keyword) then
keyword = util.SteamIDFrom64(keyword)
end
query = string.format("%s WHERE `%s` LIKE %s", query, column, SQL.Escape("%" .. keyword .. "%"))
end
SQL.Query(query, callback, true, {res, page, column, order_by, sort_by, keyword})
end
local valid_columns = {
steamid = true,
name = true,
rank = true
}
local valid_sorts = {
id = true,
name = true,
rank = true,
play_time = true,
last_join = true
}
local resolve_promise = function(data, arguments)
local res = arguments[1]
arguments[1] = data
res(arguments)
end
local get_players = function(count_data, arguments)
local res, page, column, order_by, sort_by, keyword = unpack(arguments)
local count = count_data.count
local current_page
if page < 1 then
page, current_page = 1, 1
end
local pages_count = get_pages_count(count)
if page > pages_count then
page, current_page = pages_count, pages_count
end
local query = [[
SELECT
`steamid`,
`name`,
`rank`,
`expiry_date`,
`first_join`,
`last_join`,
`play_time`
FROM
`sam_players`
]]
local args = {}
if keyword then
args[1] = column
args[2] = "%" .. keyword .. "%"
query = query .. [[
WHERE
`{1f}` LIKE {2}
]]
end
args[3] = sort_by
if order_by == "DESC" then
query = query .. [[
ORDER BY `{3f}` DESC
]]
else
query = query .. [[
ORDER BY `{3f}` ASC
]]
end
args[4] = limit
args[5] = math.abs(limit * (page - 1))
query = query .. [[
LIMIT {4} OFFSET {5}
]]
SQL.FQuery(query, args, resolve_promise, false, {res, count, current_page})
end
netstream.async.Hook("SAM.GetPlayers", function(res, ply, page, column, order_by, sort_by, keyword)
if not isnumber(page) then return end
if not valid_columns[column] then return end
if order_by ~= "ASC" and order_by ~= "DESC" then return end
if not valid_sorts[sort_by] then return end
if keyword ~= nil and not sam.isstring(keyword) then return end
get_page_count(get_players, res, page, column, order_by, sort_by, keyword)
end, check)
return
end
local GetColor = SUI.GetColor
local Line = sui.TDLib.LibClasses.Line
local COLUMN_FONT = SUI.CreateFont("Column", "Roboto", 18)
local LINE_FONT = SUI.CreateFont("Line", "Roboto", 16)
local NEXT_FONT = SUI.CreateFont("NextButton", "Roboto", 18)
local button_click = function(s)
local v = s.v
local dmenu = vgui.Create("SAM.Menu")
dmenu:SetInternal(s)
if v.name and v.name ~= "" then
dmenu:AddOption("Copy Name", function()
SetClipboardText(v.name)
end)
end
dmenu:AddOption("Copy SteamID", function()
SetClipboardText(v.steamid)
end)
dmenu:AddOption("Copy Rank", function()
SetClipboardText(v.rank)
end)
dmenu:AddOption("Copy Play Time", function()
SetClipboardText(sam.reverse_parse_length(tonumber(v.play_time) / 60))
end)
dmenu:AddSpacer()
dmenu:AddOption("Change Rank", function()
local querybox = vgui.Create("SAM.QueryBox")
querybox:SetTitle(string.format("Change rank for '%s'", v.name or v.steamid))
querybox:SetWide(360)
local ranks = querybox:Add("SAM.ComboBox")
ranks:SetTall(28)
for rank_name in SortedPairsByMemberValue(sam.ranks.get_ranks(), "immunity", true) do
if v.rank ~= rank_name then
ranks:AddChoice(rank_name, nil, true)
end
end
querybox:Done()
querybox.save:SetEnabled(true)
querybox:SetCallback(function()
RunConsoleCommand("sam", "setrankid", v.steamid, ranks:GetValue())
end)
end)
dmenu:Open()
end
sam.menu.add_tab("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sam/user.png", function(column_sheet)
local refresh, pages
local current_page, current_column, current_order, current_sort, keyword = nil, "steamid", "DESC", "id", nil
local players_body = column_sheet:Add("Panel")
players_body:Dock(FILL)
players_body:DockMargin(0, 1, 0, 0)
players_body:DockPadding(10, 10, 10, 10)
local toggle_loading, is_loading = sam.menu.add_loading_panel(players_body)
local title = players_body:Add("SAM.Label")
title:Dock(TOP)
title:SetFont(SAM_TAB_TITLE_FONT)
title:SetText("Players")
title:SetTextColor(GetColor("menu_tabs_title"))
title:SizeToContents()
local total = players_body:Add("SAM.Label")
total:Dock(TOP)
total:DockMargin(0, 6, 0, 0)
total:SetFont(SAM_TAB_DESC_FONT)
total:SetText("60 total players")
total:SetTextColor(GetColor("menu_tabs_title"))
total:SetPos(10, SUI.Scale(40))
total:SizeToContents()
local search_entry
do
local container = players_body:Add("SAM.Panel")
container:Dock(TOP)
container:DockMargin(0, 6, 10, 0)
container:SetTall(30)
local sort_by = container:Add("SAM.ComboBox")
sort_by:Dock(RIGHT)
sort_by:DockMargin(4, 0, 0, 0)
sort_by:SetWide(106)
sort_by:SetValue("Sort By (ID)")
sort_by:AddChoice("ID")
sort_by:AddChoice("Name")
sort_by:AddChoice("Rank")
sort_by:AddChoice("Play Time")
function sort_by:OnSelect(_, value)
value = value:lower():gsub(" ", "_")
if current_sort ~= value then
current_sort = value
refresh()
end
end
local sort_order = container:Add("SAM.ComboBox")
sort_order:Dock(RIGHT)
sort_order:SetWide(96)
sort_order:SetValue("Desc")
sort_order:AddChoice("Desc")
sort_order:AddChoice("Asc")
function sort_order:OnSelect(_, value)
value = value:upper()
if current_order ~= value then
current_order = value
refresh()
end
end
local column = container:Add("SAM.ComboBox")
column:Dock(RIGHT)
column:DockMargin(0, 0, 4, 0)
column:SetWide(140)
column:SetValue("Search (SteamID)")
column:AddChoice("SteamID")
column:AddChoice("Name")
column:AddChoice("Rank")
function column:OnSelect(_, value)
value = value:lower()
if current_column ~= value then
current_column = value
refresh()
end
end
search_entry = container:Add("SAM.TextEntry")
search_entry:Dock(LEFT)
search_entry:SetNoBar(true)
search_entry:SetPlaceholder("Search...")
search_entry:SetRadius(4)
search_entry:SetWide(220)
function search_entry:OnEnter(no_refresh)
local value = self:GetValue()
if keyword ~= value then
keyword = value ~= "" and value or nil
if not no_refresh then
refresh()
end
end
end
end
Line(players_body, nil, -5, SUI.Scale(15), -5, 0)
do
local columns = players_body:Add("Panel")
columns:Dock(TOP)
columns:DockMargin(0, 10, 0, 0)
local info = columns:Add("SAM.Label")
info:Dock(LEFT)
info:DockMargin(4, 0, 0, 0)
info:SetFont(COLUMN_FONT)
info:SetText("Player")
info:SetTextColor(GetColor("player_list_titles"))
info:SetWide(SUI.Scale(280) + SUI.Scale(34))
info:SizeToContentsY(3)
local play_time = columns:Add("SAM.Label")
play_time:Dock(LEFT)
play_time:DockMargin(-4, 0, 0, 0)
play_time:SetFont(COLUMN_FONT)
play_time:SetText("Play Time")
play_time:SetTextColor(GetColor("player_list_titles"))
play_time:SetWide(SUI.Scale(180))
play_time:SizeToContentsY(3)
local rank_expiry = columns:Add("SAM.Label")
rank_expiry:Dock(LEFT)
rank_expiry:DockMargin(-4, 0, 0, 0)
rank_expiry:SetFont(COLUMN_FONT)
rank_expiry:SetText("Rank Expiry")
rank_expiry:SetTextColor(GetColor("player_list_titles"))
rank_expiry:SetWide(SUI.Scale(280))
rank_expiry:SizeToContentsY(3)
columns:SizeToChildren(false, true)
end
local body = players_body:Add("SAM.ScrollPanel")
body:Dock(FILL)
body:DockMargin(0, 10, 0, 0)
body:SetVBarPadding(6)
local set_data = function(data)
body:GetCanvas():Clear()
body.VBar.Scroll = 0
local players, players_count, current_page_2 = unpack(data)
total:SetText(players_count .. " total players")
pages = get_pages_count(players_count)
current_page.i = pages == 0 and 0 or current_page_2 or current_page.i
current_page:SetText(current_page.i .. "/" .. pages)
body:Line()
for k, v in ipairs(players) do
local line = body:Add("SAM.PlayerLine")
line:DockMargin(0, 0, 0, 10)
local name = v.name ~= "" and v.name or nil
line:SetInfo({
steamid = v.steamid,
name = name,
rank = v.rank
})
local play_time = line:Add("SAM.Label")
play_time:Dock(LEFT)
play_time:DockMargin(4, 0, 0, 0)
play_time:SetFont(LINE_FONT)
play_time:SetText(sam.reverse_parse_length(tonumber(v.play_time) / 60))
play_time:SetTextColor(GetColor("player_list_data"))
play_time:SetContentAlignment(4)
play_time:SetWide(SUI.Scale(180))
local expiry_date = tonumber(v.expiry_date)
local rank_expiry = line:Add("SAM.Label")
rank_expiry:Dock(LEFT)
rank_expiry:DockMargin(-3, 0, 0, 0)
rank_expiry:SetFont(LINE_FONT)
rank_expiry:SetText(expiry_date == 0 and "Never" or sam.reverse_parse_length((expiry_date - os.time()) / 60))
rank_expiry:SetTextColor(GetColor("player_list_data"))
rank_expiry:SetContentAlignment(4)
rank_expiry:SizeToContents()
local but = line:Actions()
but.v = v
but:On("DoClick", button_click)
body:Line()
end
end
refresh = function()
if not is_loading() and LocalPlayer():HasPermission("manage_players") then
search_entry:OnEnter(true)
local refresh_query = netstream.async.Start("SAM.GetPlayers", toggle_loading, current_page.i, current_column, current_order, current_sort, keyword)
refresh_query:done(set_data)
end
end
local bottom_panel = players_body:Add("SAM.Panel")
bottom_panel:Dock(BOTTOM)
bottom_panel:DockMargin(0, 6, 0, 0)
bottom_panel:SetTall(30)
bottom_panel:Background(GetColor("page_switch_bg"))
local previous_page = bottom_panel:Add("SAM.Button")
previous_page:Dock(LEFT)
previous_page:SetWide(30)
previous_page:SetText("<")
previous_page:SetFont(NEXT_FONT)
previous_page:On("DoClick", function()
if current_page.i <= 1 then return end
current_page.i = current_page.i - 1
refresh()
end)
current_page = bottom_panel:Add("SAM.Label")
current_page:Dock(FILL)
current_page:SetContentAlignment(5)
current_page:SetFont(SAM_TAB_DESC_FONT)
current_page:SetText("loading...")
current_page.i = 1
local next_page = bottom_panel:Add("SAM.Button")
next_page:Dock(RIGHT)
next_page:SetWide(30)
next_page:SetText(">")
next_page:SetFont(NEXT_FONT)
next_page:On("DoClick", function()
if current_page.i == pages then return end
current_page.i = current_page.i + 1
refresh()
end)
function bottom_panel:Think()
next_page:SetEnabled(current_page.i ~= pages)
previous_page:SetEnabled(current_page.i > 1)
end
do
local refresh_2 = function()
timer.Simple(1, refresh)
end
for k, v in ipairs({"SAM.AuthedPlayer", "SAM.ChangedPlayerRank", "SAM.ChangedSteamIDRank"}) do
hook.Add(v, "SAM.MenuPlayers", refresh_2)
end
end
refresh()
return players_body
end, function()
return LocalPlayer():HasPermission("manage_players")
end, 2)

637
lua/sam/menu/tabs/ranks.lua Normal file
View File

@@ -0,0 +1,637 @@
--[[
| 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 SAM_LOADED then return end
if SERVER then return end
local sam = sam
local SUI = sam.SUI
local GetColor = SUI.GetColor
local Line = sui.TDLib.LibClasses.Line
local AnimatedSetVisible = sui.TDLib.LibClasses.AnimatedSetVisible
local RANK_NAME = SUI.CreateFont("RankName", "Roboto Bold", 18)
local RANK_INFO = SUI.CreateFont("RankInfo", "Roboto Medium", 12)
local CREATE_RANK = SUI.CreateFont("CreateRank", "Roboto Bold", 16, 200)
local RANK_TITLE = SUI.CreateFont("RankTitle", "Roboto Bold", 20)
local rank_menu = function(rank, data)
local valid = sui.valid_options()
local imm, banlim
if rank then
imm, banlim = data.immunity, data.ban_limit
end
local edit_rank = vgui.Create("SAM.QueryBox")
edit_rank:SetWide(470)
edit_rank:SetTitle(rank and string.format("Edit Rank '%s'", rank) or "Create Rank")
local new_name = rank
if not sam.ranks.is_default_rank(rank) then
local name = edit_rank:Add("SAM.LabelPanel")
name:SetLabel("Rank Name")
local entry = name:Add("SAM.TextEntry")
entry:SetSize(210, 28)
entry:SetNoBar(false)
entry:SetPlaceholder("")
entry:SetValue(rank or "")
entry:SetCheck(function(_name)
new_name = _name
if _name == rank then return end
if _name == "" or sam.ranks.is_rank(_name) then
return false
end
end)
valid.Add(entry)
end
local new_immunity = imm
do
local immunity = edit_rank:Add("SAM.LabelPanel")
immunity:SetLabel("Immunity (2~99)")
immunity:DockMargin(0, 5, 0, 0)
local entry = immunity:Add("SAM.TextEntry")
entry:SetSize(210, 28)
entry:SetNumeric(true)
entry:DisallowFloats(true)
entry:DisallowNegative(true)
entry:SetPlaceholder("")
entry:SetValue(imm or "2")
entry:SetCheck(function(_immunity)
new_immunity = _immunity
if _immunity == "" then
return false
end
_immunity = tonumber(_immunity)
new_immunity = _immunity
if _immunity < 2 or _immunity > 99 then
return false
end
end)
valid.Add(entry)
end
local new_banlimit = banlim
do
local banlimit = edit_rank:Add("SAM.LabelPanel")
banlimit:SetLabel("Ban Limit (1y 1mo 1w 1d 1h 1m)")
banlimit:DockMargin(0, 5, 0, 0)
local entry = banlimit:Add("SAM.TextEntry")
entry:SetSize(210, 28)
entry:SetNoBar(false)
entry:SetPlaceholder("")
entry:SetValue(banlim and sam.reverse_parse_length(banlim) or "2w")
entry:SetCheck(function(_banlimit)
new_banlimit = sam.parse_length(_banlimit)
if not new_banlimit and _banlimit ~= banlim then
return false
end
end)
valid.Add(entry)
end
local inherit = rank and sam.ranks.get_rank(rank).inherit or "user"
local new_inherit = inherit
do
local inherits_from = edit_rank:Add("SAM.LabelPanel")
inherits_from:SetLabel("Inherits From")
inherits_from:DockMargin(0, 5, 0, 0)
local entry = inherits_from:Add("SAM.ComboBox")
entry:SetSize(210, 28)
entry:SetValue(inherit)
for name in SortedPairsByMemberValue(sam.ranks.get_ranks(), "immunity", true) do
if name ~= rank and not sam.ranks.inherits_from(name, rank) then
entry:AddChoice(name)
end
end
function entry:OnSelect(_, value)
new_inherit = value
end
end
edit_rank:Done()
edit_rank.save:SetEnabled(true)
edit_rank.save:SetText("SAVE")
if rank then
edit_rank:SetCallback(function()
local to_run = {}
if new_immunity ~= imm then
table.insert(to_run, {"changerankimmunity", rank, new_immunity})
end
if new_banlimit ~= banlim then
table.insert(to_run, {"changerankbanlimit", rank, new_banlimit})
end
if new_inherit ~= inherit then
table.insert(to_run, {"changeinherit", rank, new_inherit})
end
if new_name ~= rank then
table.insert(to_run, {"renamerank", rank, new_name})
end
sam.command.run_commands(to_run)
end)
else
edit_rank:SetCallback(function()
RunConsoleCommand("sam", "addrank", new_name, new_inherit, new_immunity, new_banlimit)
end)
end
function edit_rank.save:Think()
self:SetEnabled(valid.IsValid())
end
end
sam.menu.add_tab("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sam/military_rank.png", function(column_sheet)
local current_rank
local parent = column_sheet:Add("Panel")
parent:Dock(FILL)
parent:DockMargin(0, 1, 0, 0)
local title = parent:Add("SAM.Label")
title:Dock(TOP)
title:DockMargin(10, 10, 0, 0)
title:SetFont(SAM_TAB_TITLE_FONT)
title:SetText("Ranks")
title:SetTextColor(GetColor("menu_tabs_title"))
title:SizeToContents()
local total = parent:Add("SAM.Label")
total:Dock(TOP)
total:DockMargin(10, 6, 0, 0)
total:SetFont(SAM_TAB_DESC_FONT)
total:SetText(table.Count(sam.ranks.get_ranks()) .. " total ranks")
total:SetTextColor(GetColor("menu_tabs_title"))
total:SizeToContents()
local search_entry
do
local container = parent:Add("SAM.Panel")
container:Dock(TOP)
container:DockMargin(10, 6, 10, SUI.Scale(15))
container:SetTall(30)
search_entry = container:Add("SAM.TextEntry")
search_entry:Dock(LEFT)
search_entry:SetNoBar(true)
search_entry:SetPlaceholder("Search...")
search_entry:SetRadius(4)
search_entry:SetWide(220)
end
local create_rank = parent:Add("SAM.Button")
create_rank:SetFont(CREATE_RANK)
create_rank:SetText("Create Rank")
create_rank:Dock(BOTTOM)
create_rank:DockMargin(10, 0, 10, 10)
create_rank:On("DoClick", function()
rank_menu()
end)
local right_body = parent:Add("Panel")
right_body:Dock(RIGHT)
right_body:DockMargin(0, 5, 10, 10)
right_body:SetWide(0)
right_body:SetZPos(-1)
local rank_title = right_body:Add("SAM.Label")
rank_title:Dock(TOP)
rank_title:DockMargin(0, 0, 0, 5)
rank_title:SetFont(RANK_TITLE)
rank_title:SetTextColor(GetColor("menu_tabs_title"))
local permissions_body = right_body:Add("SAM.CollapseCategory")
permissions_body:Dock(FILL)
permissions_body:GetCanvas():DockPadding(0, 0, 5, 0)
local function refresh_access()
if not IsValid(current_rank) then return end
for k, v in ipairs(permissions_body.items) do
AnimatedSetVisible(v.img, sam.ranks.has_permission(current_rank.name, v.name))
end
end
for k, v in ipairs({"SAM.ChangedInheritRank", "SAM.RankPermissionGiven", "SAM.RankPermissionTaken"}) do
hook.Add(v, "SAM.Menu.RefreshPermissions ", refresh_access)
end
local function sortFunc(a, b)
if (a.category == b.category) then return a.name < b.name end
return a.category < b.category
end
local function refresh_permissions()
permissions_body:GetCanvas():Clear()
table.Empty(permissions_body.items)
table.Empty(permissions_body.categories)
local item_click = function(s)
local rank = current_rank.name
if not sam.ranks.has_permission(rank, s.name) then
RunConsoleCommand("sam", "givepermission", rank, s.name)
else
RunConsoleCommand("sam", "takepermission", rank, s.name)
end
end
local permissions = sam.permissions.get()
table.sort(permissions, sortFunc)
for k, v in ipairs(permissions) do
local item = permissions_body:add_item(v.name, v.category)
item:SetContentAlignment(4)
item:SetTextInset(6, 0)
item:SizeToContentsY(SUI.Scale(10))
item:SetZPos(k)
item.name = v.name
item.DoClick = item_click
local img = item:Add("SAM.Image")
img:Dock(RIGHT)
img:DockMargin(4, 4, 4, 4)
img:InvalidateParent(true)
img:SetWide(img:GetTall())
img:SetImageColor(Color(52, 161, 224))
img:SetImage("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sam/check_mark.png")
item.img = img
end
end
local limits_body
do
local permissions_search = right_body:Add("SAM.TextEntry")
permissions_search:Dock(TOP)
permissions_search:DockMargin(0, 0, 5, 10)
permissions_search:SetNoBar(true)
permissions_search:SetPlaceholder("Search...")
permissions_search:SetRadius(4)
permissions_search:SetTall(30)
function permissions_search:OnValueChange(text)
if limits_body and limits_body:IsVisible() then
local children = limits_body:GetCanvas():GetChildren()
for k, v in ipairs(children) do
v:AnimatedSetVisible(v.title:find(text, nil, true) ~= nil)
end
limits_body:InvalidateLayout(true)
else
permissions_body:Search(text)
end
end
Line(right_body):SetZPos(2)
end
local function load_limits()
if sam.limit_types then
if limits_body then return end
else
if limits_body then
limits_body:SetVisible(false)
permissions_body:AnimatedSetVisible(true)
limits_body:Remove()
limits_body = nil
end
return
end
limits_body = right_body:Add("SAM.ScrollPanel")
limits_body:Dock(FILL)
limits_body:GetCanvas():DockPadding(0, 0, 5, 0)
limits_body:SetVisible(false)
local item_enter = function(s)
if not IsValid(current_rank) then return end
local rank = current_rank.name
local limit = math.Clamp(s:GetValue(), -1, 1000)
if limit ~= sam.ranks.get_limit(rank, s.limit_type) then
RunConsoleCommand("sam", "changeranklimit", rank, s.limit_type, limit)
else
s:SetText(tostring(sam.ranks.get_limit(rank, s.limit_type)))
end
end
local not_empty = function(s)
return s and s ~= ""
end
local limit_values = {}
for k, v in ipairs(sam.limit_types) do
local immunity = limits_body:Add("SAM.LabelPanel")
immunity:SetLabel(v)
immunity:DockMargin(5, 0, 0, 5)
local entry = immunity:Add("SAM.TextEntry")
entry:SetSize(60, 26)
entry:SetNumeric(true)
entry:DisallowFloats(true)
entry:SetPlaceholder("")
entry:SetCheck(not_empty)
entry.limit_type = v
entry.OnEnter = item_enter
table.insert(limit_values, entry)
end
function limits_body:Refresh()
if not IsValid(current_rank) then return end
local rank = current_rank.name
for k, v in ipairs(limit_values) do
v:SetValue(tostring(sam.ranks.get_limit(rank, v.limit_type)))
end
end
local right_current_rank = right_body:Add("SAM.Button")
right_current_rank:Dock(BOTTOM)
right_current_rank:DockMargin(0, 5, 0, 0)
right_current_rank:SetFont(CREATE_RANK)
right_current_rank:SetText("Switch to Limits")
right_current_rank:On("DoClick", function()
limits_body:AnimatedToggleVisible()
permissions_body:AnimatedToggleVisible()
if permissions_body:AnimatedIsVisible() then
right_current_rank:SetText("Switch to Limits")
else
right_current_rank:SetText("Switch to Permissions")
end
end)
limits_body:On("OnRemove", function()
right_current_rank:Remove()
end)
limits_body:Refresh()
end
local function refresh_all()
timer.Create("SAM.Menu.Ranks.Refresh", 1, 1, function()
load_limits()
refresh_permissions()
refresh_access()
end)
end
sam.config.hook({"Restrictions.Limits"}, refresh_all)
for k, v in ipairs({"SAM.AddedPermission", "SAM.PermissionModified", "SAM.RemovedPermission"}) do
hook.Add(v, "SAM.Menu.RefreshPermissions", refresh_all)
end
local body = parent:Add("SAM.ScrollPanel")
body:Dock(FILL)
body:DockMargin(10, 0, 5, 10)
body:SetVBarPadding(6)
body:Line():SetZPos(-101)
local select_rank = function(s)
if not IsValid(s) then
current_rank = nil
right_body:SizeTo(0, -1, 0.3)
return
end
if IsValid(current_rank) then
current_rank.Selected = false
if current_rank == s then
current_rank = nil
right_body:SizeTo(0, -1, 0.3)
return
end
end
s.Selected = true
current_rank = s
refresh_access()
if limits_body then
limits_body:Refresh()
end
right_body:SizeTo(SUI.Scale(300), -1, 0.3)
rank_title:SetText(s.name)
rank_title:SizeToContents()
end
local ranks = {}
function search_entry:OnValueChange()
local value = self:GetValue()
for k, v in pairs(ranks) do
local show = k:find(value, nil, true)
show = show ~= nil
v.line:AnimatedSetVisible(show)
v:GetParent():AnimatedSetVisible(show)
end
end
local add_rank = function(rank_name, data)
if rank_name == "superadmin" then return end
if not IsValid(body) then return end
local line = body:Add("SAM.Panel")
line:Dock(TOP)
line:DockMargin(0, 0, 0, 10)
line:SetTall(34)
line:SetZPos(-data.immunity)
line:InvalidateLayout(true)
local container = line:Add("SAM.Button")
container:Dock(FILL)
container:DockMargin(0, 0, 5, 0)
container:DockPadding(5, 5, 0, 5)
container:SetText("")
container:SetContained(false)
container.name = rank_name
ranks[rank_name] = container
container:On("DoClick", select_rank)
function container:DoRightClick()
rank_name = container.name
if rank_name == "user" then return end
local dmenu = vgui.Create("SAM.Menu")
dmenu:SetSize(w, h)
dmenu:SetInternal(container)
dmenu:AddOption("Edit Rank", function()
rank_menu(rank_name, sam.ranks.get_rank(rank_name))
end)
if not sam.ranks.is_default_rank(rank_name) then
dmenu:AddSpacer()
dmenu:AddOption("Remove Rank", function()
local remove_rank = vgui.Create("SAM.QueryBox")
remove_rank:SetWide(350)
local check = remove_rank:Add("SAM.Label")
check:SetText("Are you sure that you want to remove '" .. rank_name .. "'?")
check:SetFont("SAMLine")
check:SetWrap(true)
check:SetAutoStretchVertical(true)
remove_rank:Done()
remove_rank.save:SetEnabled(true)
remove_rank.save:SetText("REMOVE")
remove_rank.save:SetContained(false)
remove_rank.save:SetColors(GetColor("query_box_cancel"), GetColor("query_box_cancel_text"))
remove_rank.cancel:SetContained(true)
remove_rank.cancel:SetColors()
remove_rank:SetCallback(function()
RunConsoleCommand("sam", "removerank", rank_name)
end)
end)
end
dmenu:Open()
dmenu:SetPos(input.GetCursorPos())
end
do
local name = container:Add("SAM.Label")
name:Dock(TOP)
name:DockMargin(0, 0, 0, 2)
name:SetTextColor(GetColor("player_list_names"))
name:SetFont(RANK_NAME)
name:SetText(rank_name)
name:SizeToContents()
local immunity = container:Add("SAM.Label")
immunity:Dock(TOP)
immunity:SetTextColor(GetColor("player_list_steamid"))
immunity:SetFont(RANK_INFO)
immunity:SetText("Immunity: " .. data.immunity)
immunity:SizeToContents()
local banlimit = container:Add("SAM.Label")
banlimit:Dock(TOP)
banlimit:SetTextColor(GetColor("player_list_steamid"))
banlimit:SetFont(RANK_INFO)
banlimit:SetText("Ban limit: " .. sam.reverse_parse_length(sam.parse_length(data.ban_limit)))
banlimit:SizeToContents()
local inherit = container:Add("SAM.Label")
inherit:Dock(TOP)
inherit:SetTextColor(GetColor("player_list_steamid"))
inherit:SetFont(RANK_INFO)
inherit:SetText("Inherits from: " .. (sam.isstring(data.inherit) and data.inherit or "none"))
inherit:SizeToContents()
end
container:InvalidateLayout(true)
container:SizeToChildren(false, true)
line:SizeToChildren(false, true)
local _line = body:Line()
_line:SetZPos(-data.immunity)
container.line = _line
container.data = data
end
for rank_name, v in pairs(sam.ranks.get_ranks()) do
add_rank(rank_name, v)
end
hook.Add("SAM.AddedRank", "SAM.RefreshRanksList", function(name, rank)
add_rank(name, rank)
end)
hook.Add("SAM.RemovedRank", "SAM.RefreshRanksList", function(name)
local line = ranks[name]
if not IsValid(line) then return end
line.line:Remove()
line:GetParent():Remove()
ranks[name] = nil
if line == current_rank then
select_rank()
end
end)
-- This is just better than caching panels for stuff that ain't gonna be called a lot
hook.Add("SAM.RankNameChanged", "SAM.RefreshRanksList", function(name, new_name)
local line = ranks[name]
if not IsValid(line) then return end
-- if current_rank == name then
-- rank_name:SetText(new_name)
-- end
line:GetChildren()[1]:SetText(new_name)
ranks[new_name], ranks[name] = line, nil
line.name = new_name
end)
hook.Add("SAM.RankImmunityChanged", "SAM.RefreshRanksList", function(name, immunity)
local line = ranks[name]
if not IsValid(line) then return end
line:GetChildren()[2]:SetText("Immunity: " .. immunity)
line:GetParent():SetZPos(-immunity)
-- SetZPos is kinda weird to deal with
line.line:SetZPos(-immunity + 1)
line.line:SetZPos(-immunity)
end)
hook.Add("SAM.RankBanLimitChanged", "SAM.RefreshRanksList", function(name, new_limit)
local line = ranks[name]
if IsValid(line) then
line:GetChildren()[3]:SetText("Ban limit: " .. sam.reverse_parse_length(new_limit))
end
end)
hook.Add("SAM.ChangedInheritRank", "SAM.RefreshRanksList", function(name, new_inherit)
local line = ranks[name]
if IsValid(line) then
line:GetChildren()[4]:SetText("Inherits from: " .. new_inherit)
end
end)
return parent
end, function()
return LocalPlayer():HasPermission("manage_ranks")
end, 3)

367
lua/sam/modules/cami.lua Normal file
View File

@@ -0,0 +1,367 @@
--[[
| 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 SAM_LOADED then return end
--[[
CAMI - Common Admin Mod Interface.
Copyright 2020 CAMI Contributors
Makes admin mods intercompatible and provides an abstract privilege interface
for third party addons.
Follows the specification on this page:
https://github.com/glua/CAMI/blob/master/README.md
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.
]]
-- Version number in YearMonthDay format.
local version = 20201130
if CAMI and CAMI.Version >= version then return end
CAMI = CAMI or {}
CAMI.Version = version
--- @class CAMI_USERGROUP
--- defines the charactaristics of a usergroup
--- @field Name string @The name of the usergroup
--- @field Inherits string @The name of the usergroup this usergroup inherits from
--- @class CAMI_PRIVILEGE
--- defines the charactaristics of a privilege
--- @field Name string @The name of the privilege
--- @field MinAccess "'user'" | "'admin'" | "'superadmin'" @Default group that should have this privilege
--- @field Description string | nil @Optional text describing the purpose of the privilege
local CAMI_PRIVILEGE = {}
--- Optional function to check if a player has access to this privilege
--- (and optionally execute it on another player)
---
--- ⚠ **Warning**: This function may not be called by all admin mods
--- @param actor GPlayer @The player
--- @param target GPlayer | nil @Optional - the target
--- @return boolean @If they can or not
--- @return string | nil @Optional reason
function CAMI_PRIVILEGE:HasAccess(actor, target)
end
--- Contains the registered CAMI_USERGROUP usergroup structures.
--- Indexed by usergroup name.
--- @type CAMI_USERGROUP[]
local usergroups = CAMI.GetUsergroups and CAMI.GetUsergroups() or {
user = {
Name = "user",
Inherits = "user"
},
admin = {
Name = "admin",
Inherits = "user"
},
superadmin = {
Name = "superadmin",
Inherits = "admin"
}
}
--- Contains the registered CAMI_PRIVILEGE privilege structures.
--- Indexed by privilege name.
--- @type CAMI_PRIVILEGE[]
local privileges = CAMI.GetPrivileges and CAMI.GetPrivileges() or {}
--- Registers a usergroup with CAMI.
---
--- Use the source parameter to make sure CAMI.RegisterUsergroup function and
--- the CAMI.OnUsergroupRegistered hook don't cause an infinite loop
--- @param usergroup CAMI_USERGROUP @The structure for the usergroup you want to register
--- @param source any @Identifier for your own admin mod. Can be anything.
--- @return CAMI_USERGROUP @The usergroup given as an argument
function CAMI.RegisterUsergroup(usergroup, source)
usergroups[usergroup.Name] = usergroup
hook.Call("CAMI.OnUsergroupRegistered", nil, usergroup, source)
return usergroup
end
--- Unregisters a usergroup from CAMI. This will call a hook that will notify
--- all other admin mods of the removal.
---
--- ⚠ **Warning**: Call only when the usergroup is to be permanently removed.
---
--- Use the source parameter to make sure CAMI.UnregisterUsergroup function and
--- the CAMI.OnUsergroupUnregistered hook don't cause an infinite loop
--- @param usergroupName string @The name of the usergroup.
--- @param source any @Identifier for your own admin mod. Can be anything.
--- @return boolean @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
--- Retrieves all registered usergroups.
--- @return CAMI_USERGROUP[] @Usergroups indexed by their names.
function CAMI.GetUsergroups()
return usergroups
end
--- Receives information about a usergroup.
--- @param usergroupName string
--- @return CAMI_USERGROUP | nil @Returns nil when the usergroup does not exist.
function CAMI.GetUsergroup(usergroupName)
return usergroups[usergroupName]
end
--- Checks to see if potentialAncestor is an ancestor of usergroupName.
--- All usergroups are ancestors of themselves.
---
--- Examples:
--- * `user` is an ancestor of `admin` and also `superadmin`
--- * `admin` is an ancestor of `superadmin`, but not `user`
--- @param usergroupName string @The usergroup to query
--- @param potentialAncestor string @The ancestor to query
--- @return boolean @Whether usergroupName inherits potentialAncestor.
function CAMI.UsergroupInherits(usergroupName, potentialAncestor)
repeat
if usergroupName == potentialAncestor then return true end
usergroupName = usergroups[usergroupName] and
usergroups[usergroupName].Inherits or
usergroupName
until not usergroups[usergroupName] or
usergroups[usergroupName].Inherits == usergroupName
-- One can only be sure the usergroup inherits from user if the
-- usergroup isn't registered.
return usergroupName == potentialAncestor or potentialAncestor == "user"
end
--- Find the base group a usergroup inherits from.
---
--- This function traverses down the inheritence chain, so for example if you have
--- `user` -> `group1` -> `group2`
--- this function will return `user` if you pass it `group2`.
---
--- **NOTE**: All usergroups must eventually inherit either user, admin or superadmin.
--- @param usergroupName string @The name of the usergroup
--- @return "'user'" | "'admin'" | "'superadmin'" @The name of the root usergroup
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
--- Registers an addon privilege with CAMI.
---
--- ⚠ **Warning**: This should only be used by addons. Admin mods must *NOT*
--- register their privileges using this function.
--- @param privilege CAMI_PRIVILEGE
--- @return CAMI_PRIVILEGE @The privilege given as argument.
function CAMI.RegisterPrivilege(privilege)
privileges[privilege.Name] = privilege
hook.Call("CAMI.OnPrivilegeRegistered", nil, privilege)
return privilege
end
--- Unregisters a privilege from CAMI.
--- This will call a hook that will notify any admin mods of the removal.
---
--- ⚠ **Warning**: Call only when the privilege is to be permanently removed.
--- @param privilegeName string @The name of the privilege.
--- @return boolean @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
--- Retrieves all registered privileges.
--- @return CAMI_PRIVILEGE[] @All privileges indexed by their names.
function CAMI.GetPrivileges()
return privileges
end
--- Receives information about a privilege.
--- @param privilegeName string
--- @return CAMI_PRIVILEGE | nil
function CAMI.GetPrivilege(privilegeName)
return privileges[privilegeName]
end
-- Default access handler
local defaultAccessHandler = {["CAMI.PlayerHasAccess"] =
function(_, actorPly, privilegeName, callback, targetPly, 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
local hasAccess =
priv.MinAccess == "user" or
priv.MinAccess == "admin" and actorPly:IsAdmin() or
priv.MinAccess == "superadmin" and actorPly:IsSuperAdmin()
if hasAccess and priv.HasAccess then
hasAccess = priv:HasAccess(actorPly, targetPly)
end
callback(hasAccess, "Fallback.")
end,
["CAMI.SteamIDHasAccess"] =
function(_, _, _, callback)
callback(false, "No information available.")
end
}
--- @class CAMI_ACCESS_EXTRA_INFO
--- @field Fallback "'user'" | "'admin'" | "'superadmin'" @Fallback status for if the privilege doesn't exist. Defaults to `admin`.
--- @field IgnoreImmunity boolean @Ignore any immunity mechanisms an admin mod might have.
--- @field CommandArguments table @Extra arguments that were given to the privilege command.
--- Checks if a player has access to a privilege
--- (and optionally can execute it on targetPly)
---
--- This function is designed to be asynchronous but will be invoked
--- synchronously if no callback is passed.
---
--- ⚠ **Warning**: If the currently installed admin mod does not support
--- synchronous queries, this function will throw an error!
--- @param actorPly GPlayer @The player to query
--- @param privilegeName string @The privilege to query
--- @param callback fun(hasAccess: boolean, reason: string|nil) @Callback to receive the answer, or nil for synchronous
--- @param targetPly GPlayer | nil @Optional - target for if the privilege effects another player (eg kick/ban)
--- @param extraInfoTbl CAMI_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod
--- @return boolean | nil @Synchronous only - if the player has the privilege
--- @return string | nil @Synchronous only - optional reason from admin mod
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
--- Get all the players on the server with a certain privilege
--- (and optionally who can execute it on targetPly)
---
--- **NOTE**: This is an asynchronous function!
--- @param privilegeName string @The privilege to query
--- @param callback fun(players: GPlayer[]) @Callback to receive the answer
--- @param targetPly GPlayer | nil @Optional - target for if the privilege effects another player (eg kick/ban)
--- @param extraInfoTbl CAMI_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod
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
--- @class CAMI_STEAM_ACCESS_EXTRA_INFO
--- @field IgnoreImmunity boolean @Ignore any immunity mechanisms an admin mod might have.
--- @field CommandArguments table @Extra arguments that were given to the privilege command.
--- Checks if a (potentially offline) SteamID has access to a privilege
--- (and optionally if they can execute it on a target SteamID)
---
--- **NOTE**: This is an asynchronous function!
--- @param actorSteam string | nil @The SteamID to query
--- @param privilegeName string @The privilege to query
--- @param callback fun(hasAccess: boolean, reason: string|nil) @Callback to receive the answer
--- @param targetSteam string | nil @Optional - target SteamID for if the privilege effects another player (eg kick/ban)
--- @param extraInfoTbl CAMI_STEAM_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod
function CAMI.SteamIDHasAccess(actorSteam, privilegeName, callback,
targetSteam, extraInfoTbl)
hook.Call("CAMI.SteamIDHasAccess", defaultAccessHandler, actorSteam,
privilegeName, callback, targetSteam, extraInfoTbl)
end
--- 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.
--- @param ply GPlayer @The player for which the usergroup is changed
--- @param old string @The previous usergroup of the player.
--- @param new string @The new usergroup of the player.
--- @param 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
--- 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.
--- @param steamId string @The steam ID of the player for which the usergroup is changed
--- @param old string @The previous usergroup of the player.
--- @param new string @The new usergroup of the player.
--- @param 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

238
lua/sam/modules/chat.lua Normal file
View File

@@ -0,0 +1,238 @@
--[[
| 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 SAM_LOADED then return end
local sam, command, language = sam, sam.command, sam.language
command.set_category("Chat")
command.new("pm")
:SetPermission("pm", "user")
:AddArg("player", {allow_higher_target = true, single_target = true, cant_target_self = true})
:AddArg("text", {hint = "message", check = function(str)
return str:match("%S") ~= nil
end})
:GetRestArgs()
:Help("pm_help")
:OnExecute(function(ply, targets, message)
if ply:sam_get_pdata("unmute_time") then
return ply:sam_send_message("you_muted")
end
local target = targets[1]
ply:sam_send_message("pm_to", {
T = targets, V = message
})
if ply ~= target then
target:sam_send_message("pm_from", {
A = ply, V = message
})
end
end)
:End()
do
sam.permissions.add("see_admin_chat", nil, "admin")
local reports_enabled = sam.config.get_updated("Reports", true)
command.new("asay")
:SetPermission("asay", "user")
:AddArg("text", {hint = "message"})
:GetRestArgs()
:Help("asay_help")
:OnExecute(function(ply, message)
if reports_enabled.value and not ply:HasPermission("see_admin_chat") then
local success, time = sam.player.report(ply, message)
if success == false then
ply:sam_send_message("You need to wait {S Red} seconds.", {
S = time
})
else
ply:sam_send_message("to_admins", {
A = ply, V = message
})
end
return
end
local targets = {ply}
local players = player.GetHumans()
for i = 1, #players do
local v = players[i]
if v:HasPermission("see_admin_chat") and v ~= ply then
table.insert(targets, v)
end
end
sam.player.send_message(targets, "to_admins", {
A = ply, V = message
})
end)
:End()
if SERVER then
sam.hook_last("PlayerSay", "SAM.Chat.Asay", function(ply, text)
if text:sub(1, 1) == "@" then
ply:Say("!asay " .. text:sub(2))
return ""
end
end)
end
end
do
command.new("mute")
:SetPermission("mute", "admin")
:AddArg("player")
:AddArg("length", {optional = true, default = 0, min = 0})
:AddArg("text", {hint = "reason", optional = true, default = sam.language.get("default_reason")})
:GetRestArgs()
:Help("mute_help")
:OnExecute(function(ply, targets, length, reason)
local current_time = SysTime()
for i = 1, #targets do
local target = targets[i]
target:sam_set_pdata("unmute_time", length ~= 0 and (current_time + length * 60) or 0)
end
sam.player.send_message(nil, "mute", {
A = ply, T = targets, V = sam.format_length(length), V_2 = reason
})
end)
:End()
command.new("unmute")
:SetPermission("unmute", "admin")
:AddArg("player", {optional = true})
:Help("unmute_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
targets[i]:sam_set_pdata("unmute_time", nil)
end
sam.player.send_message(nil, "unmute", {
A = ply, T = targets
})
end)
:End()
if SERVER then
sam.hook_first("PlayerSay", "SAM.Chat.Mute", function(ply, text)
local unmute_time = ply:sam_get_pdata("unmute_time")
if not unmute_time then return end
if text:sub(1, 1) == "!" and text:sub(2, 2):match("%S") ~= nil then
local args = sam.parse_args(text:sub(2))
local cmd_name = args[1]
if not cmd_name then return end
local cmd = command.get_command(cmd_name)
if cmd then
return
end
end
if unmute_time == 0 or unmute_time > SysTime() then
return ""
else
ply:sam_set_pdata("unmute_time", nil)
end
end)
end
end
do
command.new("gag")
:SetPermission("gag", "admin")
:AddArg("player")
:AddArg("length", {optional = true, default = 0, min = 0})
:AddArg("text", {hint = "reason", optional = true, default = sam.language.get("default_reason")})
:GetRestArgs()
:Help("gag_help")
:OnExecute(function(ply, targets, length, reason)
for i = 1, #targets do
local target = targets[i]
target.sam_gagged = true
if length ~= 0 then
timer.Create("SAM.UnGag" .. target:SteamID64(), length * 60, 1, function()
RunConsoleCommand("sam", "ungag", "#" .. target:EntIndex())
end)
end
end
sam.player.send_message(nil, "gag", {
A = ply, T = targets, V = sam.format_length(length), V_2 = reason
})
end)
:End()
command.new("ungag")
:SetPermission("ungag", "admin")
:AddArg("player", {optional = true})
:Help("ungag_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
local target = targets[i]
target.sam_gagged = nil
timer.Remove("SAM.UnGag" .. target:SteamID64())
end
sam.player.send_message(nil, "ungag", {
A = ply, T = targets
})
end)
:End()
if SERVER then
hook.Add("PlayerCanHearPlayersVoice", "SAM.Chat.Gag", function(_, ply)
if ply.sam_gagged then
return false
end
end)
hook.Add("PlayerInitialSpawn", "SAM.Gag", function(ply)
local gag_time = ply:sam_get_pdata("gagged")
if gag_time then
ply:sam_set_pdata("gagged", nil)
RunConsoleCommand("sam", "gag", "#" .. ply:EntIndex(), gag_time / 60, "LTAP")
end
end)
hook.Add("PlayerDisconnected", "SAM.Gag", function(ply)
if ply.sam_gagged then
ply:sam_set_pdata("gagged", timer.TimeLeft("SAM.UnGag" .. ply:SteamID64()) or 0)
end
end)
end
end

View File

@@ -0,0 +1,39 @@
--[[
| 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/
--]]
--
-- Make command notifying only for ranks you select.
-- permission is command_notify. (by default admin+ has it)
-- You can NOT use this with 'command_hide_admin_name.lua'
--
if SAM_LOADED then return end
sam.permissions.add("command_notify", nil, "admin")
if SERVER then
local get_players = function()
local players = {}
for _, v in ipairs(player.GetAll()) do
if v:HasPermission("command_notify") then
table.insert(players, v)
end
end
return players
end
sam.player.old_send_message = sam.player.old_send_message or sam.player.send_message
function sam.player.send_message(ply, msg, tbl)
if ply == nil and debug.traceback():find("lua/sam/command/", 1, true) then
sam.player.old_send_message(get_players(), msg, tbl)
else
sam.player.old_send_message(ply, msg, tbl)
end
end
end

324
lua/sam/modules/darkrp.lua Normal file
View File

@@ -0,0 +1,324 @@
--[[
| 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 SAM_LOADED then return end
local add = not GAMEMODE and hook.Add or function(_, _, fn)
fn()
end
add("PostGamemodeLoaded", "SAM.DarkRP", function()
if not DarkRP then return end
local sam, command, language = sam, sam.command, sam.language
command.set_category("DarkRP")
command.new("arrest")
:SetPermission("arrest", "superadmin")
:AddArg("player")
:AddArg("number", {hint = "time", optional = true, min = 0, default = 0, round = true})
:Help("arrest_help")
:OnExecute(function(ply, targets, time)
if time == 0 then
time = math.huge
end
for i = 1, #targets do
local v = targets[i]
if v:isArrested() then
v:unArrest()
end
v:arrest(time, ply)
end
if time == math.huge then
sam.player.send_message(nil, "arrest", {
A = ply, T = targets
})
else
sam.player.send_message(nil, "arrest2", {
A = ply, T = targets, V = time
})
end
end)
:End()
command.new("unarrest")
:SetPermission("unarrest", "superadmin")
:AddArg("player", {optional = true})
:Help("unarrest_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
targets[i]:unArrest()
end
sam.player.send_message(nil, "unarrest", {
A = ply, T = targets
})
end)
:End()
command.new("setmoney")
:SetPermission("setmoney", "superadmin")
:AddArg("player", {single_target = true})
:AddArg("number", {hint = "amount", min = 0, round = true})
:Help("setmoney_help")
:OnExecute(function(ply, targets, amount)
local target = targets[1]
amount = hook.Call("playerWalletChanged", GAMEMODE, target, amount - target:getDarkRPVar("money"), target:getDarkRPVar("money")) or amount
DarkRP.storeMoney(target, amount)
target:setDarkRPVar("money", amount)
sam.player.send_message(nil, "setmoney", {
A = ply, T = targets, V = amount
})
end)
:End()
command.new("addmoney")
:SetPermission("addmoney", "superadmin")
:AddArg("player", {single_target = true})
:AddArg("number", {hint = "amount", min = 0, round = true})
:Help("addmoney_help")
:OnExecute(function(ply, targets, amount)
targets[1]:addMoney(amount)
sam.player.send_message(nil, "addmoney", {
A = ply, T = targets, V = DarkRP.formatMoney(amount)
})
end)
:End()
command.new("selldoor")
:SetPermission("selldoor", "superadmin")
:Help("selldoor_help")
:OnExecute(function(ply)
local ent = ply:GetEyeTrace().Entity
if not IsValid(ent) or not ent.keysUnOwn then
return ply:sam_send_message("door_invalid")
end
local door_owner = ent:getDoorOwner()
if not IsValid(door_owner) then
return ply:sam_send_message("door_no_owner")
end
ent:keysUnOwn(ply)
sam.player.send_message(nil, "selldoor", {
A = ply, T = {door_owner, admin = ply}
})
end)
:End()
command.new("sellall")
:SetPermission("sellall", "superadmin")
:AddArg("player", {single_target = true})
:Help("sellall_help")
:OnExecute(function(ply, targets, amount)
targets[1]:keysUnOwnAll()
sam.player.send_message(nil, "sellall", {
A = ply, T = targets
})
end)
:End()
command.new("setjailpos")
:SetPermission("setjailpos", "superadmin")
:Help("setjailpos_help")
:OnExecute(function(ply)
DarkRP.storeJailPos(ply, false)
sam.player.send_message(nil, "s_jail_pos", {
A = ply
})
end)
:End()
command.new("addjailpos")
:SetPermission("addjailpos", "superadmin")
:Help("addjailpos_help")
:OnExecute(function(ply)
DarkRP.storeJailPos(ply, true)
sam.player.send_message(nil, "a_jail_pos", {
A = ply
})
end)
:End()
local RPExtraTeams = RPExtraTeams
local job_index = nil
command.new("setjob")
:SetPermission("setjob", "admin")
:AddArg("player")
:AddArg("text", {hint = "job", check = function(job)
job = job:lower()
for i = 1, #RPExtraTeams do
local v = RPExtraTeams[i]
if v.name:lower() == job or v.command:lower() == job then
job_index = v.team
return true
end
end
return false
end})
:Help("setjob_help")
:OnExecute(function(ply, targets, job)
for i = 1, #targets do
targets[i]:changeTeam(job_index, true, true, true)
end
sam.player.send_message(nil, "setjob", {
A = ply, T = targets, V = job
})
end)
:End()
do
local get_shipment = function(name)
local found, key = DarkRP.getShipmentByName(name)
if found then return found, key end
name = name:lower()
local shipments = CustomShipments
for i = 1, #shipments do
local shipment = shipments[i]
if shipment.entity == name then
return DarkRP.getShipmentByName(shipment.name)
end
end
return false
end
local place_entity = function(ent, tr, ply)
local ang = ply:EyeAngles()
ang.pitch = 0
ang.yaw = ang.yaw - 90
ang.roll = 0
ent:SetAngles(ang)
local flush_point = tr.HitPos - (tr.HitNormal * 512)
flush_point = ent:NearestPoint(flush_point)
flush_point = ent:GetPos() - flush_point
flush_point = tr.HitPos + flush_point
ent:SetPos(flush_point)
end
command.new("shipment")
:SetPermission("shipment", "superadmin")
:AddArg("text", {hint = "weapon", check = get_shipment})
:Help("shipment_help")
:OnExecute(function(ply, weapon_name)
local trace = {}
trace.start = ply:EyePos()
trace.endpos = trace.start + ply:GetAimVector() * 85
trace.filter = ply
local tr = util.TraceLine(trace)
local shipment_info, shipment_key = get_shipment(weapon_name)
local crate = ents.Create(shipment_info.shipmentClass or "spawned_shipment")
crate.SID = ply.SID
crate:Setowning_ent(ply)
crate:SetContents(shipment_key, shipment_info.amount)
crate:SetPos(Vector(tr.HitPos.x, tr.HitPos.y, tr.HitPos.z))
crate.nodupe = true
crate.ammoadd = shipment_info.spareammo
crate.clip1 = shipment_info.clip1
crate.clip2 = shipment_info.clip2
crate:Spawn()
crate:SetPlayer(ply)
place_entity(crate, tr, ply)
local phys = crate:GetPhysicsObject()
phys:Wake()
if shipment_info.weight then
phys:SetMass(shipment_info.weight)
end
sam.player.send_message(nil, "shipment", {
A = ply, V = weapon_name
})
end)
:End()
end
sam.command.new("forcename")
:SetPermission("forcename", "superadmin")
:AddArg("player")
:AddArg("text", {hint = "name"})
:Help("forcename_help")
:OnExecute(function(ply, targets, name)
local target = targets[1]
DarkRP.retrieveRPNames(name, function(taken)
if not IsValid(target) then return end
if taken then
ply:sam_send_message("forcename_taken", {
V = name
})
return
end
sam.player.send_message(nil, "forcename", {
A = ply, T = targets, V = name
})
DarkRP.storeRPName(target, name)
target:setDarkRPVar("rpname", name)
end)
end)
:End()
end)

654
lua/sam/modules/fun.lua Normal file
View File

@@ -0,0 +1,654 @@
--[[
| 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 SAM_LOADED then return end
local sam, command, language = sam, sam.command, sam.language
command.set_category("Fun")
do
local sounds = {}
for i = 1, 6 do
sounds[i] = "physics/body/body_medium_impact_hard" .. i .. ".wav"
end
local slap = function(ply, damage, admin)
if not ply:Alive() or ply:sam_get_nwvar("frozen") then return end
ply:ExitVehicle()
ply:SetVelocity(Vector(math.random(-100, 100), math.random(-100, 100), math.random(200, 400)))
ply:EmitSound(sounds[math.random(1, 6)], 60, math.random(80, 120))
if damage > 0 then
ply:TakeDamage(damage, admin, DMG_GENERIC)
end
end
command.new("slap")
:SetPermission("slap", "admin")
:AddArg("player")
:AddArg("number", {hint = "damage", round = true, optional = true, min = 0, default = 0})
:Help("slap_help")
:OnExecute(function(ply, targets, damage)
for i = 1, #targets do
slap(targets[i], damage, ply)
end
if damage > 0 then
sam.player.send_message(nil, "slap_damage", {
A = ply, T = targets, V = damage
})
else
sam.player.send_message(nil, "slap", {
A = ply, T = targets
})
end
end)
:End()
end
command.new("slay")
:SetPermission("slay", "admin")
:AddArg("player")
:Help("slay_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
local v = targets[i]
if not v:sam_get_exclusive(ply) then
v:Kill()
end
end
sam.player.send_message(nil, "slay", {
A = ply, T = targets
})
end)
:End()
command.new("hp")
:Aliases("sethp", "health", "sethealth")
:SetPermission("hp", "admin")
:AddArg("player")
:AddArg("number", {hint = "amount", min = 1, max = 2147483647, round = true, optional = true, default = 100})
:Help("hp_help")
:OnExecute(function(ply, targets, amount)
for i = 1, #targets do
targets[i]:SetHealth(amount)
end
sam.player.send_message(nil, "set_hp", {
A = ply, T = targets, V = amount
})
end)
:End()
command.new("armor")
:Aliases("setarmor")
:SetPermission("armor", "admin")
:AddArg("player")
:AddArg("number", {hint = "amount", min = 1, max = 2147483647, round = true, optional = true, default = 100})
:Help("armor_help")
:OnExecute(function(ply, targets, amount)
for i = 1, #targets do
targets[i]:SetArmor(amount)
end
sam.player.send_message(nil, "set_armor", {
A = ply, T = targets, V = amount
})
end)
:End()
command.new("ignite")
:SetPermission("ignite", "admin")
:AddArg("player")
:AddArg("number", {hint = "seconds", optional = true, default = 60, round = true})
:Help("ignite_help")
:OnExecute(function(ply, targets, length)
for i = 1, #targets do
local target = targets[i]
if target:IsOnFire() then
target:Extinguish()
end
target:Ignite(length)
end
sam.player.send_message(nil, "ignite", {
A = ply, T = targets, V = length
})
end)
:End()
command.new("unignite")
:Aliases("extinguish")
:SetPermission("ignite", "admin")
:AddArg("player", {optional = true})
:Help("unignite_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
targets[i]:Extinguish()
end
sam.player.send_message(nil, "unignite", {
A = ply, T = targets
})
end)
:End()
command.new("god")
:Aliases("invincible")
:SetPermission("god", "admin")
:AddArg("player", {optional = true})
:Help("god_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
local target = targets[i]
target:GodEnable()
target.sam_has_god_mode = true
end
sam.player.send_message(nil, "god", {
A = ply, T = targets
})
end)
:End()
command.new("ungod")
:Aliases("uninvincible")
:SetPermission("ungod", "admin")
:AddArg("player", {optional = true})
:Help("ungod_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
local target = targets[i]
target:GodDisable()
target.sam_has_god_mode = nil
end
sam.player.send_message(nil, "ungod", {
A = ply, T = targets
})
end)
:End()
do
command.new("freeze")
:SetPermission("freeze", "admin")
:AddArg("player")
:Help("freeze_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
local v = targets[i]
v:ExitVehicle()
if v:sam_get_nwvar("frozen") then
v:UnLock()
end
v:Lock()
v:sam_set_nwvar("frozen", true)
v:sam_set_exclusive("frozen")
end
sam.player.send_message(nil, "freeze", {
A = ply, T = targets
})
end)
:End()
command.new("unfreeze")
:SetPermission("unfreeze", "admin")
:AddArg("player", {optional = true})
:Help("unfreeze_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
local v = targets[i]
v:UnLock()
v:sam_set_nwvar("frozen", false)
v:sam_set_exclusive(nil)
end
sam.player.send_message(nil, "unfreeze", {
A = ply, T = targets
})
end)
:End()
local disallow = function(ply)
if ply:sam_get_nwvar("frozen") then
return false
end
end
for _, v in ipairs({"SAM.CanPlayerSpawn", "CanPlayerSuicide", "CanTool"}) do
hook.Add(v, "SAM.FreezePlayer." .. v, disallow)
end
end
command.new("cloak")
:SetPermission("cloak", "admin")
:AddArg("player", {optional = true})
:Help("cloak_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
targets[i]:sam_cloak()
end
sam.player.send_message(nil, "cloak", {
A = ply, T = targets
})
end)
:End()
command.new("uncloak")
:SetPermission("uncloak", "admin")
:AddArg("player", {optional = true})
:Help("uncloak_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
targets[i]:sam_uncloak()
end
sam.player.send_message(nil, "uncloak", {
A = ply, T = targets
})
end)
:End()
do
local jail_props = {
Vector(0, 0, -5), Angle(90, 0, 0);
Vector(0, 0, 97), Angle(90, 0, 0);
Vector(21, 31, 46), Angle(0, 90, 0);
Vector(21, -31, 46), Angle(0, 90, 0);
Vector(-21, 31, 46), Angle(0, 90, 0);
Vector(-21, -31, 46), Angle(0, 90, 0);
Vector(-52, 0, 46), Angle(0, 0, 0);
Vector(52, 0, 46), Angle(0, 0, 0)
}
local remove_jail = function(ply_jail_props)
for _, jail_prop in ipairs(ply_jail_props) do
if IsValid(jail_prop) then
jail_prop:Remove()
end
end
end
local unjail = function(ply)
if not IsValid(ply) then return end
if not ply:sam_get_nwvar("jailed") then return end
remove_jail(ply.sam_jail_props)
ply.sam_jail_props = nil
ply.sam_jail_pos = nil
ply:sam_set_nwvar("jailed", nil)
ply:sam_set_exclusive(nil)
timer.Remove("SAM.Unjail." .. ply:SteamID())
timer.Remove("SAM.Jail.Watch." .. ply:SteamID())
end
local return_false = function()
return false
end
local function jail(ply, time)
if not IsValid(ply) then return end
if not isnumber(time) or time < 0 then
time = 0
end
if ply:sam_get_nwvar("frozen") then
RunConsoleCommand("sam", "unfreeze", "#" .. ply:EntIndex())
end
if not ply:sam_get_nwvar("jailed") or (not ply.sam_jail_props or not IsValid(ply.sam_jail_props[1])) then
ply:ExitVehicle()
ply:SetMoveType(MOVETYPE_WALK)
ply.sam_jail_pos = ply:GetPos()
ply:sam_set_nwvar("jailed", true)
ply:sam_set_exclusive("in jail")
if ply.sam_jail_props then
for k, v in ipairs(ply.sam_jail_props) do
if IsValid(v) then
v:Remove()
end
end
end
local ply_jail_props = {}
for i = 1, #jail_props, 2 do
local jail_prop = ents.Create("prop_physics")
jail_prop:SetModel("models/props_building_details/Storefront_Template001a_Bars.mdl")
jail_prop:SetPos(ply.sam_jail_pos + jail_props[i])
jail_prop:SetAngles(jail_props[i + 1])
jail_prop:SetMoveType(MOVETYPE_NONE)
jail_prop:Spawn()
jail_prop:GetPhysicsObject():EnableMotion(false)
jail_prop.CanTool = return_false
jail_prop.PhysgunPickup = return_false
jail_prop.jailWall = true
table.insert(ply_jail_props, jail_prop)
end
ply.sam_jail_props = ply_jail_props
end
local steamid = ply:SteamID()
if time == 0 then
timer.Remove("SAM.Unjail." .. steamid)
else
timer.Create("SAM.Unjail." .. steamid, time, 1, function()
if IsValid(ply) then
unjail(ply)
end
end)
end
timer.Create("SAM.Jail.Watch." .. steamid, 0.5, 0, function()
if not IsValid(ply) then
return timer.Remove("SAM.Jail.Watch." .. steamid)
end
if ply:GetPos():DistToSqr(ply.sam_jail_pos) > 4900 then
ply:SetPos(ply.sam_jail_pos)
end
if not IsValid(ply.sam_jail_props[1]) then
jail(ply, timer.TimeLeft("SAM.Unjail." .. steamid) or 0)
end
end)
end
command.new("jail")
:SetPermission("jail", "admin")
:AddArg("player")
:AddArg("length", {optional = true, default = 0, min = 0})
:AddArg("text", {hint = "reason", optional = true, default = sam.language.get("default_reason")})
:GetRestArgs()
:Help("jail_help")
:OnExecute(function(ply, targets, length, reason)
for i = 1, #targets do
jail(targets[i], length * 60)
end
sam.player.send_message(nil, "jail", {
A = ply, T = targets, V = sam.format_length(length), V_2 = reason
})
end)
:End()
command.new("unjail")
:SetPermission("unjail", "admin")
:AddArg("player", {optional = true})
:Help("unjail_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
unjail(targets[i])
end
sam.player.send_message(nil, "unjail", {
A = ply, T = targets
})
end)
:End()
sam.hook_first("CanProperty", "SAM.Jail", function(_, property, ent)
if ent.jailWall and property == "remover" then
return false
end
end)
if SERVER then
hook.Add("PlayerSpawn", "SAM.Jail", function(ply)
if ply:sam_get_nwvar("jailed") or ply:sam_get_pdata("jailed") then
if ply.sam_jail_pos then
ply:SetPos(ply.sam_jail_pos)
else
ply:SetPos(ply:sam_get_pdata("jail_pos"))
jail(ply, ply:sam_get_pdata("jail_time_left"))
ply:sam_set_pdata("jailed", nil)
ply:sam_set_pdata("jail_pos", nil)
ply:sam_set_pdata("jail_time_left", nil)
end
end
end)
hook.Add("PlayerEnteredVehicle", "SAM.Jail", function(ply)
if ply:sam_get_nwvar("jailed") then
ply:ExitVehicle()
end
end)
hook.Add("PlayerDisconnected", "SAM.Jail", function(ply)
if ply:sam_get_nwvar("jailed") then
remove_jail(ply.sam_jail_props)
ply:sam_set_pdata("jailed", true)
ply:sam_set_pdata("jail_pos", ply.sam_jail_pos)
ply:sam_set_pdata("jail_time_left", timer.TimeLeft("SAM.Unjail." .. ply:SteamID()) or 0)
timer.Remove("SAM.Unjail." .. ply:SteamID())
timer.Remove("SAM.Jail.Watch." .. ply:SteamID())
end
end)
end
local disallow = function(ply)
if ply:sam_get_nwvar("jailed") then
return false
end
end
for _, v in ipairs({"PlayerNoClip", "SAM.CanPlayerSpawn", "CanPlayerEnterVehicle", "CanPlayerSuicide", "CanTool"}) do
hook.Add(v, "SAM.Jail", disallow)
end
end
command.new("strip")
:SetPermission("strip", "admin")
:AddArg("player")
:Help("strip_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
targets[i]:StripWeapons()
end
sam.player.send_message(nil, "strip", {
A = ply, T = targets
})
end)
:End()
command.new("respawn")
:SetPermission("respawn", "admin")
:AddArg("player", {optional = true})
:Help("respawn_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
targets[i]:Spawn()
end
sam.player.send_message(nil, "respawn", {
A = ply, T = targets
})
end)
:End()
command.new("setmodel")
:SetPermission("setmodel", "superadmin")
:AddArg("player")
:AddArg("text", {hint = "model"})
:Help("setmodel_help")
:OnExecute(function(ply, targets, model)
for i = 1, #targets do
targets[i]:SetModel(model)
end
sam.player.send_message(nil, "setmodel", {
A = ply, T = targets, V = model
})
end)
:End()
command.new("giveammo")
:Aliases("ammo")
:SetPermission("giveammo", "superadmin")
:AddArg("player")
:AddArg("number", {hint = "amount", min = 0, max = 99999})
:Help("giveammo_help")
:OnExecute(function(ply, targets, amount)
if amount == 0 then
amount = 99999
end
for i = 1, #targets do
local target = targets[i]
for _, wep in ipairs(target:GetWeapons()) do
if wep:GetPrimaryAmmoType() ~= -1 then
target:GiveAmmo(amount, wep:GetPrimaryAmmoType(), true)
end
if wep:GetSecondaryAmmoType() ~= -1 then
target:GiveAmmo(amount, wep:GetSecondaryAmmoType(), true)
end
end
end
sam.player.send_message(nil, "giveammo", {
A = ply, T = targets, V = amount
})
end)
:End()
do
command.new("scale")
:SetPermission("scale", "superadmin")
:AddArg("player")
:AddArg("number", {hint = "amount", optional = true, min = 0, max = 2.5, default = 1})
:Help("scale_help")
:OnExecute(function(ply, targets, amount)
for i = 1, #targets do
local v = targets[i]
v:SetModelScale(amount)
-- https://github.com/carz1175/More-ULX-Commands/blob/9b142ee4247a84f16e2dc2ec71c879ab76e145d4/lua/ulx/modules/sh/extended.lua#L313
v:SetViewOffset(Vector(0, 0, 64 * amount))
v:SetViewOffsetDucked(Vector(0, 0, 28 * amount))
v.sam_scaled = true
end
sam.player.send_message(nil, "scale", {
A = ply, T = targets, V = amount
})
end)
:End()
hook.Add("PlayerSpawn", "SAM.Scale", function(ply)
if ply.sam_scaled then
ply.sam_scaled = nil
ply:SetViewOffset(Vector(0, 0, 64))
ply:SetViewOffsetDucked(Vector(0, 0, 28))
end
end)
end
sam.command.new("freezeprops")
:SetPermission("freezeprops", "admin")
:Help("freezeprops_help")
:OnExecute(function(ply)
for _, prop in ipairs(ents.FindByClass("prop_physics")) do
local physics_obj = prop:GetPhysicsObject()
if IsValid(physics_obj) then
physics_obj:EnableMotion(false)
end
end
sam.player.send_message(nil, "freezeprops", {
A = ply
})
end)
:End()

224
lua/sam/modules/murder.lua Normal file
View File

@@ -0,0 +1,224 @@
--[[
| 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 SAM_LOADED then return end
local add = not GAMEMODE and hook.Add or function(_, _, fn)
fn()
end
-- Thanks to https://github.com/boxama/addons/blob/master/addons/ULX_Murder/lua/ulx/modules/sh/murder.lua
add("PostGamemodeLoaded", "SAM.Murder", function()
if GAMEMODE.Author ~= "MechanicalMind" then return end
if not isstring(GAMEMODE.Version) or GAMEMODE.Version < "28" then return end
local sam, command = sam, sam.command
command.set_category("Murder")
local autoslain_players = {}
command.new("slaynr")
:SetPermission("slaynr", "admin")
:AddArg("player")
:AddArg("number", {hint = "rounds", optional = true, default = 1, min = 1, max = 100, round = true})
:Help("Slays the target(s) at the beggining of the next round.")
:OnExecute(function(ply, targets, rounds)
for i = 1, #targets do
local v = targets[i]
v.MurdererChance = 0
if not v:IsBot() then
autoslain_players[v:AccountID()] = rounds
end
end
sam.player.send_message(nil, "{A} set {T} to be autoslain for {V} round(s)", {
A = ply, T = targets, V = rounds
})
end)
:End()
command.new("unslaynr")
:SetPermission("unslaynr", "admin")
:AddArg("player")
:Help("Remove target(s) autoslays.")
:OnExecute(function(ply, targets)
for i = 1, #targets do
local v = targets[i]
v.MurdererChance = 1
if not v:IsBot() then
autoslain_players[v:AccountID()] = nil
end
end
sam.player.send_message(nil, "Removed all autoslays for {T} ", {
A = ply, T = targets
})
end)
:End()
hook.Add("OnStartRound", "SAM.Murder", function()
timer.Simple(3, function()
local players = team.GetPlayers(2)
local targets = {admin = sam.console}
for i = 1, #players do
local v = players[i]
if not v:IsBot() then continue end
local slays = autoslain_players[v:AccountID()]
if not slays then continue end
v:Kill()
slays = slays - 1
targets[1] = v
sam.player.send_message(nil, "{A} autoslayed {T}, autoslays left: {V}.", {
A = sam.console, T = targets, V = slays
})
autoslain_players[v:AccountID()] = slays > 0 and slays or nil
end
end)
end)
hook.Add("PlayerInitialSpawn", "SAM.Murder", function(ply)
if autoslain_players[ply:AccountID()] then
ply.MurdererChance = 0
end
end)
command.new("respawn")
:SetPermission("respawn", "admin")
:AddArg("player", {single_target = true})
:Help("Respawn a target.")
:OnExecute(function(ply, targets)
local target = targets[1]
if target:Team() ~= 2 then
return ply:sam_add_text("You cannot respawn a spectator!")
end
target:Spectate(OBS_MODE_NONE)
target:Spawn()
sam.player.send_message(nil, "respawn", {
A = ply, T = targets
})
end)
:End()
local get_admins = function()
local admins = {}
local players = player.GetHumans()
for i = 1, #players do
local v = players[i]
if v:IsAdmin() then
table.insert(admins, v)
end
end
return admins
end
command.new("givemagnum")
:SetPermission("givemagnum", "superadmin")
:AddArg("player", {single_target = true, optional = true})
:Help("Give the target a magnum.")
:OnExecute(function(ply, targets)
local target = targets[1]
if target:Team() ~= 2 then
return ply:sam_add_text("You cannot give spectator a magnum!")
end
target:Give("weapon_mu_magnum")
sam.player.send_message(get_admins(), "{A} gave {T} a {V}", {
A = ply, T = targets, V = "magnum"
})
end)
:End()
command.new("giveknife")
:SetPermission("giveknife", "superadmin")
:AddArg("player", {single_target = true, optional = true})
:Help("Give the target a knife.")
:OnExecute(function(ply, targets)
local target = targets[1]
if target:Team() ~= 2 then
return ply:sam_add_text("You cannot give spectator a knife!")
end
target:Give("weapon_mu_knife")
sam.player.send_message(get_admins(), "{A} gave {T} a {V}", {
A = ply, T = targets, V = "knife"
})
end)
:End()
command.new("forcemurderer")
:SetPermission("forcemurderer", "admin")
:AddArg("player", {single_target = true, optional = true})
:Help("Force the target to me a murderer next round.")
:OnExecute(function(ply, targets)
GAMEMODE.ForceNextMurderer = targets[1]
sam.player.send_message(get_admins(), "{A} set {T} to be the Murderer next round!", {
A = ply, T = targets
})
end)
:End()
command.new("getmurderers")
:SetPermission("getmurderers", "admin")
:Help("Print all murderers in chat.")
:OnExecute(function(ply)
local murderers = {admin = ply}
local players = team.GetPlayers(2)
for i = 1, #players do
local v = players[i]
if v:GetMurderer() then
table.insert(murderers, v)
end
end
sam.player.send_message(ply, "Murderers are: {T}", {
T = murderers
})
end)
:End()
end)

View File

@@ -0,0 +1,136 @@
--[[
| 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 SAM_LOADED then return end
local ranks_loaded
if SERVER then
ranks_loaded = sam.ranks.ranks_loaded()
else
ranks_loaded = sam.ranks.get_ranks() ~= nil
end
do
local load_ranks = function()
for name, rank in pairs(sam.ranks.get_ranks()) do
if not sam.ranks.is_default_rank(name) then
CAMI.RegisterUsergroup({Name = name, Inherits = rank.inherit}, "SAM")
end
end
end
if ranks_loaded then
load_ranks()
else
hook.Add("SAM.LoadedRanks", "SAM.CAMI.LoadRanksToCAMI", load_ranks)
end
end
hook.Add("SAM.AddedRank", "SAM.CAMI.AddedRank", function(name, rank)
if not sam.ranks.is_default_rank(name) then
CAMI.RegisterUsergroup({Name = name, Inherits = rank.inherit}, "SAM")
end
end)
hook.Add("SAM.RemovedRank", "SAM.CAMI.RemovedRank", function(name)
CAMI.UnregisterUsergroup(name, "SAM")
end)
hook.Add("SAM.RankNameChanged", "SAM.CAMI.RankNameChanged", function(old, new)
CAMI.UnregisterUsergroup(old, "SAM")
CAMI.RegisterUsergroup({Name = new, Inherits = sam.ranks.get_rank(new).inherit}, "SAM")
end)
hook.Add("SAM.ChangedPlayerRank", "SAM.CAMI.ChangedPlayerRank", function(ply, new_rank, old_rank)
CAMI.SignalUserGroupChanged(ply, old_rank, new_rank, "SAM")
end)
hook.Add("SAM.ChangedSteamIDRank", "SAM.CAMI.ChangedSteamIDRank", function(steamid, new_rank, old_rank)
CAMI.SignalSteamIDUserGroupChanged(steamid, old_rank, new_rank, "SAM")
end)
----------------------------------------------------------------------------------------------------------------------------------------------------------
if SERVER then
do
local on_user_group_registered = function(rank, source)
if source ~= "SAM" then
sam.ranks.add_rank(rank.Name, sam.ranks.is_rank(rank.Inherits) and rank.Inherits or "user")
end
end
local load_ranks = function()
for _, rank in pairs(CAMI.GetUsergroups()) do
on_user_group_registered(rank, "CAMI")
end
hook.Add("CAMI.OnUsergroupRegistered", "SAM.CAMI.OnUsergroupRegistered", on_user_group_registered)
end
if ranks_loaded then
load_ranks()
else
hook.Add("SAM.LoadedRanks", "SAM.CAMI.LoadRanksFromCAMI", load_ranks)
end
end
hook.Add("CAMI.OnUsergroupUnregistered", "SAM.CAMI.OnUsergroupUnregistered", function(rank, source)
if source ~= "SAM" then
sam.ranks.remove_rank(rank.Name)
end
end)
hook.Add("CAMI.PlayerUsergroupChanged", "SAM.CAMI.PlayerUsergroupChanged", function(ply, _, new_rank, source)
if ply and IsValid(ply) and source ~= "SAM" then
sam.player.set_rank(ply, new_rank)
end
end)
hook.Add("CAMI.SteamIDUsergroupChanged", "SAM.CAMI.SteamIDUsergroupChanged", function(steamid, _, new_rank, source)
if sam.is_steamid(steamid) and source ~= "SAM" then
sam.player.set_rank_id(steamid, new_rank)
end
end)
end
do
local on_privilege_registered = function(privilege)
sam.permissions.add(privilege.Name, "CAMI", privilege.MinAccess)
end
local load_privileges = function()
for _, privilege in pairs(CAMI.GetPrivileges()) do
on_privilege_registered(privilege)
end
hook.Add("CAMI.OnPrivilegeRegistered", "SAM.CAMI.OnPrivilegeRegistered", on_privilege_registered)
end
if ranks_loaded then
load_privileges()
else
hook.Add("SAM.LoadedRanks", "SAM.CAMI.LoadPrivileges", load_privileges)
end
end
hook.Add("CAMI.OnPrivilegeUnregistered", "SAM.CAMI.OnPrivilegeUnregistered", function(privilege)
sam.permissions.remove(privilege.Name)
end)
hook.Add("CAMI.PlayerHasAccess", "SAM.CAMI.PlayerHasAccess", function(ply, privilege, callback, target)
if sam.type(ply) ~= "Player" then return end
local has_permission = ply:HasPermission(privilege)
if sam.type(target) == "Player" then
callback(has_permission and ply:CanTarget(target))
else
callback(has_permission)
end
return true
end)

View File

@@ -0,0 +1,61 @@
--[[
| 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/
--]]
--
-- This checks if a player joined your server using a lent account that is banned
-- eg. player got banned so he decided to make an alt account and used https://store.steampowered.com/promotion/familysharing
--
--
-- Whitelisted players from checking if they have family sharing or not
-- You can have steamid/steamid64 here
--
local Whitelisted_SteamIDs = {
}
local BanMessage = "Bypassing a ban using an alt. (alt: %s)"
--
-- Do you want to kick players using family shared accounts?
--
local BlockFamilySharing = false
local BlockFamilySharingMessage = "This server blocked using shared accounts."
--
--
-- DO NOT TOUCH --
--
--
for k, v in pairs(Whitelisted_SteamIDs) do
Whitelisted_SteamIDs[v] = true
Whitelisted_SteamIDs[k] = nil
end
hook.Add("SAM.AuthedPlayer", "CheckSteamFamily", function(ply)
local ply_steamid = ply:SteamID()
local ply_steamid64 = ply:SteamID64()
if Whitelisted_SteamIDs[ply_steamid] or Whitelisted_SteamIDs[ply_steamid64] then return end
local lender = ply:OwnerSteamID64()
if (ply_steamid64 == lender) then return end
if BlockFamilySharing then
ply:Kick(BlockFamilySharingMessage)
else
lender = util.SteamIDFrom64(lender)
sam.player.is_banned(lender, function(banned)
if banned then
RunConsoleCommand("sam", "banid", ply_steamid, "0", BanMessage:format(lender))
end
end)
end
end)

View File

@@ -0,0 +1,198 @@
--[[
| 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 SAM_LOADED then return end
local sam, command, language = sam, sam.command, sam.language
command.set_category("Teleport")
local find_empty_pos -- https://github.com/FPtje/DarkRP/blob/b147d6fa32799136665a9fd52d35c2fe87cf7f78/gamemode/modules/base/sv_util.lua#L149
do
local is_empty = function(vector, ignore)
local point = util.PointContents(vector)
local a = point ~= CONTENTS_SOLID
and point ~= CONTENTS_MOVEABLE
and point ~= CONTENTS_LADDER
and point ~= CONTENTS_PLAYERCLIP
and point ~= CONTENTS_MONSTERCLIP
if not a then return false end
local ents_found = ents.FindInSphere(vector, 35)
for i = 1, #ents_found do
local v = ents_found[i]
if (v:IsNPC() or v:IsPlayer() or v:GetClass() == "prop_physics" or v.NotEmptyPos) and v ~= ignore then
return false
end
end
return true
end
local distance, step, area = 600, 30, Vector(16, 16, 64)
local north_vec, east_vec, up_vec = Vector(0, 0, 0), Vector(0, 0, 0), Vector(0, 0, 0)
find_empty_pos = function(pos, ignore)
if is_empty(pos, ignore) and is_empty(pos + area, ignore) then
return pos
end
for j = step, distance, step do
for i = -1, 1, 2 do
local k = j * i
-- North/South
north_vec.x = k
if is_empty(pos + north_vec, ignore) and is_empty(pos + north_vec + area, ignore) then
return pos + north_vec
end
-- East/West
east_vec.y = k
if is_empty(pos + east_vec, ignore) and is_empty(pos + east_vec + area, ignore) then
return pos + east_vec
end
-- Up/Down
up_vec.z = k
if is_empty(pos + up_vec, ignore) and is_empty(pos + up_vec + area, ignore) then
return pos + up_vec
end
end
end
return pos
end
end
command.new("bring")
:DisallowConsole()
:SetPermission("bring", "admin")
:AddArg("player", {cant_target_self = true})
:Help("bring_help")
:OnExecute(function(ply, targets)
if not ply:Alive() then
return ply:sam_send_message("dead")
end
if ply:InVehicle() then
return ply:sam_send_message("leave_car")
end
if ply:sam_get_exclusive(ply) then
return ply:sam_send_message(ply:sam_get_exclusive(ply))
end
local teleported = {admin = ply}
local all = targets.input == "*"
for i = 1, #targets do
local target = targets[i]
if target:sam_get_exclusive(ply) then
if not all then
ply:sam_send_message(target:sam_get_exclusive(ply))
end
continue
end
if not target:Alive() then
target:Spawn()
end
target.sam_tele_pos, target.sam_tele_ang = target:GetPos(), target:EyeAngles()
target:ExitVehicle()
target:SetVelocity(Vector(0, 0, 0))
target:SetPos(find_empty_pos(ply:GetPos(), target))
target:SetEyeAngles((ply:EyePos() - target:EyePos()):Angle())
table.insert(teleported, target)
end
if #teleported > 0 then
sam.player.send_message(nil, "bring", {
A = ply, T = teleported
})
end
end)
:End()
command.new("goto")
:DisallowConsole()
:SetPermission("goto", "admin")
:AddArg("player", {single_target = true, allow_higher_target = true, cant_target_self = true})
:Help("goto_help")
:OnExecute(function(ply, targets)
if ply:sam_get_exclusive(ply) then
return ply:sam_send_message(ply:sam_get_exclusive(ply))
end
if not ply:Alive() then
ply:Spawn()
end
local target = targets[1]
ply.sam_tele_pos, ply.sam_tele_ang = ply:GetPos(), ply:EyeAngles()
ply:ExitVehicle()
ply:SetVelocity(Vector(0, 0, 0))
ply:SetPos(find_empty_pos(target:GetPos(), ply))
ply:SetEyeAngles((target:EyePos() - ply:EyePos()):Angle())
sam.player.send_message(nil, "goto", {
A = ply, T = targets
})
end)
:End()
command.new("return")
:SetPermission("return", "admin")
:AddArg("player", {single_target = true, optional = true})
:Help("return_help")
:OnExecute(function(ply, targets)
local target = targets[1]
local last_pos, last_ang = target.sam_tele_pos, target.sam_tele_ang
if not last_pos then
return sam.player.send_message(ply, "no_location", {
T = targets
})
end
if target:sam_get_exclusive(ply) then
return ply:sam_send_message(target:sam_get_exclusive(ply))
end
if not target:Alive() then
return ply:sam_send_message(target:Name() .. " is dead!")
end
target:ExitVehicle()
target:SetVelocity(Vector(0, 0, 0))
target:SetPos(last_pos)
target:SetEyeAngles(last_ang)
target.sam_tele_pos, target.sam_tele_ang = nil, nil
sam.player.send_message(nil, "returned", {
A = ply, T = targets
})
end)
:End()

102
lua/sam/modules/ttt.lua Normal file
View File

@@ -0,0 +1,102 @@
--[[
| 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 SAM_LOADED then return end
local run = function(fn)
if not GAMEMODE then
timer.Simple(0, fn)
else
fn()
end
end
run(function()
if engine.ActiveGamemode() ~= "terrortown" then return end
local sam, command, language = sam, sam.command, sam.language
command.set_category("TTT")
command.new("setslays")
:SetPermission("setslays", "admin")
:AddArg("player", {single_target = true})
:AddArg("number", {hint = "amount", optional = true, min = 1, default = 1, round = true})
:Help("setslays_help")
:OnExecute(function(ply, targets, amount)
targets[1]:sam_set_pdata("slays_amount", amount)
sam.player.send_message(nil, "setslays", {
A = ply, T = targets, V = amount
})
end)
:End()
command.new("removeslays")
:SetPermission("removeslays", "admin")
:AddArg("player", {single_target = true})
:Help("removeslays_help")
:OnExecute(function(ply, targets, amount)
local target = targets[1]
target:sam_set_pdata("slays_amount", nil)
target:SetForceSpec(false)
sam.player.send_message(nil, "removeslays", {
A = ply, T = targets
})
end)
:End()
OldBeginRound = OldBeginRound or BeginRound
function BeginRound(...)
local players = player.GetAll()
for i = 1, #players do
local ply = players[i]
local slays = ply:sam_get_pdata("slays_amount")
if not slays then continue end
if not ply:IsSpec() then
ply:Kill()
end
GAMEMODE:PlayerSpawnAsSpectator(ply)
ply:SetTeam(TEAM_SPEC)
ply:SetForceSpec(true)
ply:Spawn()
ply:SetRagdollSpec(false) -- dying will enable this, we don't want it here
slays = slays - 1
if slays == 0 then
timer.Simple(0, function()
ply:SetForceSpec(false)
end)
ply:sam_set_pdata("slays_amount", nil)
else
ply:sam_set_pdata("slays_amount", slays)
end
sam.player.send_message(nil, "setslays_slayed", {
T = {ply}, V = slays
})
end
return OldBeginRound(...)
end
end)

273
lua/sam/modules/user.lua Normal file
View File

@@ -0,0 +1,273 @@
--[[
| 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 SAM_LOADED then return end
local sam, command, language = sam, sam.command, sam.language
command.set_category("User Management")
command.new("setrank")
:Aliases("adduser", "changerank", "giverank")
:SetPermission("setrank")
:AddArg("player", {single_target = true})
:AddArg("rank", {check = function(rank, ply)
return ply:CanTargetRank(rank)
end})
:AddArg("length", {optional = true, default = 0})
:Help("setrank_help")
:OnExecute(function(ply, targets, rank, length)
targets[1]:sam_set_rank(rank, length)
sam.player.send_message(nil, "setrank", {
A = ply, T = targets, V = rank, V_2 = sam.format_length(length)
})
end)
:End()
command.new("setrankid")
:Aliases("adduserid", "changerankid", "giverankid")
:SetPermission("setrankid")
:AddArg("steamid")
:AddArg("rank", {check = function(rank, ply)
return ply:CanTargetRank(rank)
end})
:AddArg("length", {optional = true, default = 0})
:Help("setrankid_help")
:OnExecute(function(ply, promise, rank, length)
local a_name = ply:Name()
promise:done(function(data)
local steamid, target = data[1], data[2]
if target then
target:sam_set_rank(rank, length)
sam.player.send_message(nil, "setrank", {
A = ply, T = {target, admin = ply}, V = rank, V_2 = sam.format_length(length)
})
else
sam.player.set_rank_id(steamid, rank, length)
sam.player.send_message(nil, "setrank", {
A = a_name, T = steamid, V = rank, V_2 = sam.format_length(length)
})
end
end)
end)
:End()
command.new("addrank")
:SetPermission("manage_ranks")
:AddArg("text", {hint = "rank name", check = function(rank)
return not sam.ranks.is_rank(rank)
end})
:AddArg("rank", {hint = "inherit from"})
:AddArg("number", {hint = "immunity", min = 2, max = 99, optional = true})
:AddArg("length", {hint = "ban limit", optional = true})
:Help("addrank_help")
:MenuHide()
:OnExecute(function(ply, rank, inherit, immunity, ban_limit)
sam.ranks.add_rank(rank, inherit, immunity, ban_limit)
sam.player.send_message(nil, "addrank", {
A = ply, V = rank
})
end)
:End()
command.new("removerank")
:SetPermission("manage_ranks")
:AddArg("rank", {check = function(rank)
return not sam.ranks.is_default_rank(rank)
end})
:Help("removerank_help")
:MenuHide()
:OnExecute(function(ply, rank)
sam.ranks.remove_rank(rank)
sam.player.send_message(nil, "removerank", {
A = ply, V = rank
})
end)
:End()
command.new("renamerank")
:SetPermission("manage_ranks")
:AddArg("rank", {check = function(rank)
return not sam.ranks.is_default_rank(rank)
end})
:AddArg("text", {hint = "new name", check = function(rank)
return not sam.ranks.is_rank(rank)
end})
:Help("renamerank_help")
:MenuHide()
:OnExecute(function(ply, rank, new_name)
sam.ranks.rename_rank(rank, new_name)
sam.player.send_message(nil, "renamerank", {
A = ply, T = rank, V = new_name
})
end)
:End()
command.new("changeinherit")
:SetPermission("manage_ranks")
:AddArg("rank", {check = function(rank)
return rank ~= "user" and rank ~= "superadmin"
end})
:AddArg("rank", {hint = "inherits from"})
:Help("changeinherit_help")
:MenuHide()
:OnExecute(function(ply, rank, inherit)
if rank == inherit then return end
sam.ranks.change_inherit(rank, inherit)
sam.player.send_message(nil, "changeinherit", {
A = ply, T = rank, V = inherit
})
end)
:End()
command.new("changerankimmunity")
:SetPermission("manage_ranks")
:AddArg("rank", {check = function(rank)
return rank ~= "user" and rank ~= "superadmin"
end})
:AddArg("number", {hint = "new immunity", min = 2, max = 99})
:Help("changerankimmunity_help")
:MenuHide()
:OnExecute(function(ply, rank, new_immunity)
sam.ranks.change_immunity(rank, new_immunity)
sam.player.send_message(nil, "rank_immunity", {
A = ply, T = rank, V = new_immunity
})
end)
:End()
command.new("changerankbanlimit")
:SetPermission("manage_ranks")
:AddArg("rank", {check = function(rank)
return rank ~= "superadmin"
end})
:AddArg("length")
:Help("changerankbanlimit_help")
:MenuHide()
:OnExecute(function(ply, rank, new_limit)
sam.ranks.change_ban_limit(rank, new_limit)
sam.player.send_message(nil, "rank_ban_limit", {
A = ply, T = rank, V = sam.format_length(new_limit)
})
end)
:End()
command.new("givepermission")
:SetPermission("manage_ranks")
:AddArg("rank")
:AddArg("text", {hint = "permission"})
:Help("givepermission_help")
:MenuHide()
:OnExecute(function(ply, rank, permission)
if rank == "superadmin" then
return ply:sam_send_message("super_admin_access")
end
sam.ranks.give_permission(rank, permission)
sam.player.send_message(nil, "giveaccess", {
A = ply, V = permission, T = rank
})
end)
:End()
command.new("takepermission")
:SetPermission("manage_ranks")
:AddArg("rank")
:AddArg("text", {hint = "permission"})
:Help("takepermission_help")
:MenuHide()
:OnExecute(function(ply, rank, permission)
if rank == "superadmin" then
return ply:sam_send_message("super_admin_access")
end
sam.ranks.take_permission(rank, permission)
sam.player.send_message(nil, "takeaccess", {
A = ply, V = permission, T = rank
})
end)
:End()
command.new("changeranklimit")
:SetPermission("manage_ranks")
:AddArg("rank")
:AddArg("text", {hint = "limit"})
:AddArg("number", {hint = "value"})
:Help("changeranklimit_help")
:MenuHide()
:OnExecute(function(ply, rank, limit, value)
if rank == "superadmin" then
return ply:sam_send_message("super_admin_access")
end
sam.ranks.set_limit(rank, limit, value)
sam.player.send_message(nil, "changeranklimit", {
A = ply, T = rank, V = limit, V_2 = value
})
end)
:End()

579
lua/sam/modules/util.lua Normal file
View File

@@ -0,0 +1,579 @@
--[[
| 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 SAM_LOADED then return end
local sam, command, language = sam, sam.command, sam.language
command.set_category("Utility")
command.new("map")
:SetPermission("map", "admin")
:AddArg("map")
:AddArg("text", {hint = "gamemode", optional = true, check = sam.is_valid_gamemode})
:Help("map_help")
:OnExecute(function(ply, map, gamemode)
if not gamemode then
sam.player.send_message(nil, "map_change", {
A = ply, V = map
})
else
sam.player.send_message(nil, "map_change2", {
A = ply, V = map, V_2 = gamemode
})
RunConsoleCommand("gamemode", gamemode)
end
if #player.GetHumans() == 0 then
RunConsoleCommand("changelevel", map)
else
timer.Create("SAM.Command.Map", 10, 1, function()
RunConsoleCommand("changelevel", map)
end)
end
end)
:End()
command.new("maprestart")
:SetPermission("maprestart", "admin")
:Help("map_restart_help")
:OnExecute(function(ply)
if #player.GetHumans() == 0 then
RunConsoleCommand("changelevel", game.GetMap())
else
timer.Create("SAM.Command.MapRestart", 10, 1, function()
RunConsoleCommand("changelevel", game.GetMap())
end)
sam.player.send_message(nil, "map_restart", {
A = ply
})
end
end)
:End()
command.new("mapreset")
:SetPermission("mapreset", "admin")
:Help("mapreset_help")
:OnExecute(function(ply)
game.CleanUpMap()
sam.player.send_message(nil, "mapreset", {
A = ply
})
end)
:End()
command.new("kick")
:SetPermission("kick", "admin")
:AddArg("player", {single_target = true})
:AddArg("text", {hint = "reason", optional = true, default = sam.language.get("default_reason")})
:GetRestArgs()
:Help("kick_help")
:OnExecute(function(ply, targets, reason)
local target = targets[1]
target:Kick(reason)
sam.player.send_message(nil, "kick", {
A = ply, T = target:Name(), V = reason
})
end)
:End()
command.new("ban")
:SetPermission("ban", "admin")
:AddArg("player", {single_target = true})
:AddArg("length", {optional = true, default = 0})
:AddArg("text", {hint = "reason", optional = true, default = sam.language.get("default_reason")})
:GetRestArgs()
:Help("ban_help")
:OnExecute(function(ply, targets, length, reason)
local target = targets[1]
if ply:GetBanLimit() ~= 0 then
if length == 0 then
length = ply:GetBanLimit()
else
length = math.Clamp(length, 1, ply:GetBanLimit())
end
end
target:sam_ban(length, reason, ply:SteamID())
sam.player.send_message(nil, "ban", {
A = ply, T = target:Name(), V = sam.format_length(length), V_2 = reason
})
end)
:End()
command.new("banid")
:SetPermission("banid", "admin")
:AddArg("steamid")
:AddArg("length", {optional = true, default = 0})
:AddArg("text", {hint = "reason", optional = true, default = sam.language.get("default_reason")})
:GetRestArgs()
:Help("banid_help")
:OnExecute(function(ply, promise, length, reason)
local a_steamid, a_name, a_ban_limit = ply:SteamID(), ply:Name(), ply:GetBanLimit()
promise:done(function(data)
local steamid, target = data[1], data[2]
if a_ban_limit ~= 0 then
if length == 0 then
length = a_ban_limit
else
length = math.Clamp(length, 1, a_ban_limit)
end
end
if target then
target:sam_ban(length, reason, a_steamid)
sam.player.send_message(nil, "ban", {
A = a_name, T = target:Name(), V = sam.format_length(length), V_2 = reason
})
else
sam.player.ban_id(steamid, length, reason, a_steamid)
sam.player.send_message(nil, "banid", {
A = a_name, T = steamid, V = sam.format_length(length), V_2 = reason
})
end
end)
end)
:End()
command.new("unban")
:SetPermission("unban", "admin")
:AddArg("steamid", {allow_higher_target = true})
:Help("unban_help")
:OnExecute(function(ply, steamid, reason)
sam.player.unban(steamid, ply:SteamID())
sam.player.send_message(nil, "unban", {
A = ply, T = steamid
})
end)
:End()
do
command.new("noclip")
:SetPermission("noclip", "admin")
:AddArg("player", {optional = true})
:Help("noclip_help")
:OnExecute(function(ply, targets)
local id
for i = 1, #targets do
local v = targets[i]
v:SetMoveType(v:GetMoveType() == MOVETYPE_WALK and MOVETYPE_NOCLIP or MOVETYPE_WALK)
if v == ply then
id = i
end
end
if id then
table.remove(targets, id)
if #targets == 0 then return end
end
sam.player.send_message(nil, "noclip", {
A = ply, T = targets
})
end)
:End()
sam.permissions.add("can_noclip", nil, "admin")
hook.Add("PlayerNoClip", "SAM.CanNoClip", function(ply)
if ply:HasPermission("can_noclip") then
return true
end
end)
end
do
local config = sam.config
sam.permissions.add("can_physgun_players", nil, "admin")
if CLIENT then
local add_setting = function(body, title, key)
local setting = body:Add("SAM.LabelPanel")
setting:Dock(TOP)
setting:SetLabel(title)
local enable = setting:Add("SAM.ToggleButton")
enable:SetConfig(key, true)
return setting
end
config.add_menu_setting("Physgun", function(body)
local setting_body
do
local p = add_setting(body, "Physgun (Enable/Disable all physgun features except picking up players)", "Physgun.Enabled")
p:DockMargin(8, 6, 8, 0)
end
setting_body = body:Add("Panel")
setting_body:Dock(TOP)
setting_body:DockMargin(8, 6, 8, 0)
setting_body:DockPadding(8, 0, 8, 0)
add_setting(setting_body, "No fall damage on drop", "Physgun.NoFallDamageOnDrop")
add_setting(setting_body, "Right click to freeze players", "Physgun.RightClickToFreeze")
add_setting(setting_body, "Reset Velocity to fix some issues when players fall", "Physgun.ResetVelocity")
function setting_body:PerformLayout()
setting_body:SizeToChildren(false, true)
end
end)
end
local freeze_player = function(ply)
if SERVER then
ply:Lock()
end
ply:SetMoveType(MOVETYPE_NONE)
ply:SetCollisionGroup(COLLISION_GROUP_WORLD)
end
sam.hook_first("PhysgunPickup", "SAM.CanPhysgunPlayer", function(ply, target)
if sam.type(target) == "Player" and ply:HasPermission("can_physgun_players") and ply:CanTarget(target) then
freeze_player(target)
return true
end
end)
local load_phygun_settings = function()
hook.Remove("PhysgunDrop", "SAM.PhysgunDrop")
hook.Remove("OnPlayerHitGround", "SAM.PhysgunDropOnPlayerHitGround")
if config.get("Physgun.Enabled", true) == false then
return
end
local right_click_to_freeze = config.get("Physgun.RightClickToFreeze", true)
local reset_velocity = config.get("Physgun.ResetVelocity", true)
hook.Add("PhysgunDrop", "SAM.PhysgunDrop", function(ply, target)
if sam.type(target) ~= "Player" then return end
if right_click_to_freeze and ply:KeyPressed(IN_ATTACK2) then
freeze_player(target)
if SERVER then
target:sam_set_nwvar("frozen", true)
target:sam_set_exclusive("frozen")
end
else
if reset_velocity then
target:SetLocalVelocity(Vector(0, 0, 0))
end
if SERVER then
target:UnLock()
target:sam_set_nwvar("frozen", false)
target:sam_set_exclusive(nil)
if target.sam_has_god_mode then
target:GodEnable()
end
target.sam_physgun_drop_was_frozen = not target:IsOnGround()
end
target:SetMoveType(MOVETYPE_WALK)
target:SetCollisionGroup(COLLISION_GROUP_PLAYER)
end
end)
if config.get("Physgun.NoFallDamageOnDrop", true) then
hook.Add("OnPlayerHitGround", "SAM.PhysgunDropOnPlayerHitGround", function(ply)
if ply.sam_physgun_drop_was_frozen then
ply.sam_physgun_drop_was_frozen = false
return true
end
end)
end
end
config.hook({"Physgun.Enabled", "Physgun.RightClickToFreeze", "Physgun.ResetVelocity", "Physgun.NoFallDamageOnDrop"}, load_phygun_settings)
end
do
command.new("cleardecals")
:SetPermission("cleardecals", "admin")
:Help("cleardecals_help")
:OnExecute(function(ply)
sam.netstream.Start(nil, "cleardecals")
sam.player.send_message(nil, "cleardecals", {
A = ply
})
end)
:End()
if CLIENT then
sam.netstream.Hook("cleardecals", function()
game.RemoveRagdolls()
RunConsoleCommand("r_cleardecals")
end)
end
end
do
command.new("stopsound")
:SetPermission("stopsound", "admin")
:Help("stopsound_help")
:OnExecute(function(ply)
sam.netstream.Start(nil, "stopsound")
sam.player.send_message(nil, "stopsound", {
A = ply
})
end)
:End()
if CLIENT then
sam.netstream.Hook("stopsound", function()
RunConsoleCommand("stopsound")
end)
end
end
command.new("exit")
:SetPermission("exit_vehicle", "admin")
:AddArg("player", {single_target = true})
:Help("exit_vehicle_help")
:OnExecute(function(ply, targets)
local target = targets[1]
if not target:InVehicle() then
if ply == target then
return ply:sam_send_message("not_in_vehicle")
else
return ply:sam_send_message("not_in_vehicle2", {
S = target:Name()
})
end
end
target:ExitVehicle()
sam.player.send_message(nil, "exit_vehicle", {
A = ply, T = targets
})
end)
:End()
command.new("time")
:SetPermission("time", "user")
:AddArg("player", {single_target = true, optional = true})
:Help("time_help")
:OnExecute(function(ply, targets)
if ply == targets[1] then
sam.player.send_message(ply, "time_your", {
V = sam.reverse_parse_length(targets[1]:sam_get_play_time() / 60)
})
else
sam.player.send_message(ply, "time_player", {
T = targets, V = sam.reverse_parse_length(targets[1]:sam_get_play_time() / 60)
})
end
end)
:End()
command.new("admin")
:SetPermission("admin_mode", "admin")
:Help("admin_help")
:OnExecute(function(ply)
ply:sam_cloak()
ply:GodEnable()
ply:SetMoveType(MOVETYPE_NOCLIP)
end)
:End()
command.new("unadmin")
:SetPermission("admin_mode", "admin")
:Help("unadmin_help")
:OnExecute(function(ply)
ply:sam_uncloak()
ply:GodDisable()
ply:SetMoveType(MOVETYPE_WALK)
end)
:End()
do
command.new("buddha")
:SetPermission("buddha", "admin")
:AddArg("player", {optional = true})
:Help("buddha_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
targets[i].sam_buddha = true
end
sam.player.send_message(nil, "buddha", {
A = ply, T = targets
})
end)
:End()
command.new("unbuddha")
:SetPermission("buddha", "admin")
:AddArg("player", {optional = true})
:Help("unbuddha_help")
:OnExecute(function(ply, targets)
for i = 1, #targets do
targets[i].sam_buddha = nil
end
sam.player.send_message(nil, "unbuddha", {
A = ply, T = targets
})
end)
:End()
if SERVER then
hook.Add("EntityTakeDamage", "SAM.BuddhaMode", function(ply, info)
if ply.sam_buddha and ply:Health() - info:GetDamage() <= 0 then
ply:SetHealth(1)
return true
end
end)
end
end
command.new("give")
:SetPermission("give", "superadmin")
:AddArg("player")
:AddArg("text", {hint = "weapon/entity"})
:Help("give_help")
:OnExecute(function(ply, targets, weapon)
for i = 1, #targets do
targets[i]:Give(weapon)
end
sam.player.send_message(nil, "give", {
A = ply, T = targets, V = weapon
})
end)
:End()
-- do
-- if CLIENT then
-- sam.netstream.Hook("GetFriends", function()
-- local friends = {}
-- local humans = player.GetHumans()
-- for i = 1, #humans do
-- local human = humans[i]
-- if human:GetFriendStatus() == "friend" then
-- table.insert(friends, human)
-- end
-- end
-- netstream.Start("GetFriends", friends)
-- end)
-- else
-- hook.Add("SAM.AuthedPlayer", "GetPlayerFriends", function(ply)
-- timer.Simple(0, function()
-- ply.sam_requesting_friends = true
-- netstream.Start(ply, "GetFriends")
-- end)
-- end)
-- local invalid_friends = function(ply, friends, new_list)
-- if not sam.istable(friends) then return true end
-- local count = #friends
-- local max_players = game.MaxPlayers()
-- for k, friend in pairs(friends) do
-- if not sam.isnumber(k) then return true end
-- if not sam.isentity(friend) then return true end
-- if k > max_players then return true end
-- if k > count then return true end
-- if IsValid(friend) then
-- table.insert(new_list, friend)
-- end
-- end
-- end
-- sam.netstream.Hook("GetFriends", function(ply, friends)
-- local new_list = {}
-- if invalid_friends(ply, friends, new_list) then
-- ply.sam_friends_invalid = true
-- return
-- end
-- ply.sam_friends = new_list
-- end, function()
-- return ply.sam_requesting_friends
-- end)
-- end
-- command.new("friends")
-- :SetPermission("friends", "superadmin")
-- :AddArg("player", {single_target = true})
-- :Help(language.get("friends_help"))
-- :OnExecute(function(ply, targets)
-- local target = targets[1]
-- target.sam_friends_requests = target.sam_friends_requests or {}
-- target.sam_friends_requests[ply] = true
-- end)
-- :End()
-- end

44
lua/sam/modules/utime.lua Normal file
View File

@@ -0,0 +1,44 @@
--[[
| 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 SAM_LOADED then return end
local PLAYER = FindMetaTable("Player")
function PLAYER:GetUTime()
return self:sam_get_nwvar("TotalUTime")
end
function PLAYER:SetUTime(time)
self:sam_set_nwvar("TotalUTime", time)
end
function PLAYER:GetUTimeStart()
return self:sam_get_nwvar("UTimeStart")
end
function PLAYER:SetUTimeStart(time)
self:sam_set_nwvar("UTimeStart", time)
end
function PLAYER:GetUTimeSessionTime()
return CurTime() - self:GetUTimeStart()
end
function PLAYER:GetUTimeTotalTime()
return self:GetUTime() + CurTime() - self:GetUTimeStart()
end
if SERVER then
hook.Add("SAM.AuthedPlayer", "SAM.UTime", function(ply)
ply:SetUTime(ply:sam_get_play_time())
ply:SetUTimeStart(CurTime())
end)
end

486
lua/sam/modules/vote.lua Normal file
View File

@@ -0,0 +1,486 @@
--[[
| 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 SAM_LOADED then return end
-- DONT EVER TALK TO ME ABOUT THIS CODE
local sam, command = sam, sam.command
command.set_category("Voting")
local start_vote, end_vote
if SERVER then
local vote_on = false
local options, players_voted
local shuffle = function(tbl) -- https://gist.github.com/Uradamus/10323382
for i = #tbl, 2, -1 do
local j = math.random(i)
tbl[i], tbl[j] = tbl[j], tbl[i]
end
return tbl
end
end_vote = function(ply, callback)
if not vote_on then
return ply:sam_add_text(color_white, "There is no vote to end.")
end
vote_on = false
sam.set_global("Vote", nil)
if callback then
local tbl = {}
local total_count = 0
for i = 1, #options do
local count = sam.get_global("Votings" .. i)
total_count = total_count + count
table.insert(tbl, {i, count})
sam.set_global("Votings" .. i, nil)
end
if total_count == 0 then
return sam.player.add_text(nil, color_white, "The vote have been canceled because nobody voted.")
end
table.sort(shuffle(tbl), function(a,b) return a[2] > b[2] end)
local v = tbl[1]
local winner, count = v[1], v[2]
callback(winner, options[winner], count, total_count)
else
for i = 1, #options do
sam.set_global("Votings" .. i, nil)
end
end
options, players_voted = nil, nil
timer.Remove("SAM.Vote")
end
start_vote = function(ply, callback, title, ...)
if vote_on then
return ply:sam_add_text(color_white, "There is an active vote, wait for it to finish.")
end
vote_on = true
options, players_voted = {}, {}
local args, n = {...}, select("#", ...)
for i = 1, n do
local v = args[i]
if v then
table.insert(options, v)
end
end
sam.set_global("Vote", {title, options, CurTime()})
for k in ipairs(options) do
sam.set_global("Votings" .. k, 0)
end
timer.Create("SAM.Vote", 25, 1, function()
end_vote(ply, callback)
end)
return true
end
sam.netstream.Hook("Vote", function(ply, index)
if not sam.isnumber(index) or index > 5 then return end
local votings = sam.get_global("Votings" .. index)
if not votings then return end
local old_index = players_voted[ply:AccountID()]
if old_index == index then return end
if old_index then
sam.set_global("Votings" .. old_index, sam.get_global("Votings" .. old_index) - 1)
end
sam.set_global("Votings" .. index, votings + 1)
players_voted[ply:AccountID()] = index
end)
end
if CLIENT then
local SUI = sam.SUI
-- https://github.com/Facepunch/garrysmod/blob/master/garrysmod/lua/includes/extensions/client/player.lua
local VOTING_TITLE = SUI.CreateFont("VotingTitle", "Roboto Bold", 15)
local VOTING_OPTION = SUI.CreateFont("VotingTitle", "Roboto Medium", 14)
local bind_translation = {}
for i = 0, 9 do
bind_translation["slot" .. i] = i
end
local voting_frame
end_vote = function()
if IsValid(voting_frame) then
voting_frame:Remove()
end
hook.Remove("PlayerBindPress", "SAM.Voting")
hook.Remove("SAM.ChangedGlobalVar", "SAM.VotingCount")
end
hook.Add("SAM.ChangedGlobalVar", "Voting", function(key, value)
if key ~= "Vote" then return end
if not value then
end_vote()
return
end
local title, options, start_time = value[1], value[2], value[3]
sui.TDLib.Start()
voting_frame = vgui.Create("EditablePanel")
voting_frame:SetSize(SUI.Scale(165), SUI.Scale(230))
voting_frame:SetPos(5, ScrH() * 0.25)
voting_frame:DockPadding(4, 4, 4, 4)
voting_frame:Blur()
:Background(Color(30, 30, 30, 240))
local voting_title = voting_frame:Add("SAM.Label")
voting_title:Dock(TOP)
voting_title:SetFont(VOTING_TITLE)
voting_title:TextColor(Color(220, 220, 220))
voting_title:SetText(title)
voting_title:SetWrap(true)
voting_title:SetAutoStretchVertical(true)
local line = voting_frame:Add("SAM.Label")
line:Dock(TOP)
line:TextColor(Color(220, 220, 220))
line:SetText("-")
local options_added = {}
for i, v in ipairs(options) do
local option = voting_frame:Add("SAM.Label")
option:Dock(TOP)
option:SetFont(VOTING_OPTION)
option:TextColor(Color(220, 220, 220), true)
option:SetText(i .. ". " .. v .. " (0)")
option:SetWrap(true)
option:SetAutoStretchVertical(true)
options_added[i] = option
end
function voting_frame:Think() -- fucking gmod
self:SizeToChildren(false, true)
local time_left = math.floor(25 - (CurTime() - start_time))
if time_left <= 0 then
end_vote()
voting_frame.Think = nil
return
end
voting_title:SetText(title .. " (" .. time_left .. ")")
end
line = voting_frame:Add("SAM.Label")
line:Dock(TOP)
line:TextColor(Color(220, 220, 220))
line:SetText("-")
local option = voting_frame:Add("SAM.Label")
option:Dock(TOP)
option:SetFont(VOTING_OPTION)
option:TextColor(Color(220, 220, 220), true)
option:SetText("0. Close")
option:SetWrap(true)
option:SetAutoStretchVertical(true)
sui.TDLib.End()
local current_index
hook.Add("PlayerBindPress", "SAM.Voting", function(_, bind, down)
if not down then return end
local index = bind_translation[bind]
if not index then return end
if index == 0 then
end_vote()
return true
end
if not options[index] then return true end
if current_index then
options_added[current_index]:TextColor(Color(220, 220, 220), true)
end
options_added[index]:TextColor(Color(65, 185, 255), true)
current_index = index
sam.netstream.Start("Vote", index)
return true
end)
hook.Add("SAM.ChangedGlobalVar", "SAM.VotingCount", function(key2, count)
if key2:sub(1, 7) ~= "Votings" then return end
if not count then return end
local index = tonumber(key2:sub(8))
options_added[index]:SetText(index .. ". " .. options[index] .. " (" .. count .. ")")
end)
end)
end
local vote_check = function(str)
return str:match("%S") ~= nil
end
command.new("vote")
:SetPermission("vote", "admin")
:AddArg("text", {hint = "title", check = vote_check})
:AddArg("text", {hint = "option", check = vote_check})
:AddArg("text", {hint = "option", check = vote_check})
:AddArg("text", {hint = "option", optional = true, check = vote_check})
:AddArg("text", {hint = "option", optional = true, check = vote_check})
:AddArg("text", {hint = "option", optional = true, check = vote_check})
:Help("Start a vote!")
:OnExecute(function(ply, title, ...)
local callback = function(_, option, count, total_count)
sam.player.send_message(nil, "Vote {V} for {V_2} has been passed. ({V_3}/{V_4})", {
V = title, V_2 = option, V_3 = count, V_4 = total_count
})
end
if start_vote(ply, callback, title, ...) then
sam.player.send_message(nil, "{A} started a vote with title {V}.", {
A = ply, V = title
})
end
end)
:End()
command.new("endvote")
:SetPermission("endvote", "admin")
:Help("End current vote.")
:OnExecute(function(ply)
end_vote(ply)
end)
:End()
command.new("votekick")
:SetPermission("votekick", "admin")
:AddArg("player", {single_target = true})
:AddArg("text", {hint = "reason", optional = true})
:GetRestArgs()
:Help("Start a vote to kick a player.")
:OnExecute(function(ply, targets, reason)
local target = targets[1]
local target_name = target:Name()
local callback = function(index, option, count, total_count)
if not IsValid(ply) then
sam.player.send_message(nil, "Vote was canceled because {T} left.", {
T = target_name
})
return
end
if index == 1 then
target:Kick("Vote was successful (" .. (reason or "none") .. ")")
sam.player.send_message(nil, "Vote was successful, {T} has been kicked. ({V})", {
T = targets, V = reason
})
else
sam.player.send_message(nil, "Vote was unsuccessful, {T} won't be kicked.", {
T = target_name
})
end
end
local title = "Kick " .. target_name .. "?"
if reason then
title = title .. " (" .. reason .. ")"
end
if start_vote(ply, callback, title, "Yes", "No") then
if reason then
sam.player.send_message(nil, "{A} started a votekick against {T} ({V})", {
A = ply, T = targets, V = reason
})
else
sam.player.send_message(nil, "{A} started a votekick against {T}", {
A = ply, T = targets
})
end
end
end)
:End()
command.new("voteban")
:SetPermission("voteban", "admin")
:AddArg("player", {single_target = true})
:AddArg("length", {optional = true, default = 60, min = 30, max = 120})
:AddArg("text", {hint = "reason", optional = true})
:GetRestArgs()
:Help("Start a vote to ban a player.")
:OnExecute(function(ply, targets, length, reason)
local target = targets[1]
local target_steamid, target_name = target:SteamID(), target:Name()
local ply_steamid = ply:SteamID()
local callback = function(index, option, count, total_count)
if index == 1 then
sam.player.ban_id(target_steamid, length, "Vote was successful (" .. (reason or "none") .. ")", ply_steamid)
sam.player.send_message(nil, "Vote was successful, {T} has been banned. ({V})", {
T = target_name, V = reason
})
else
sam.player.send_message(nil, "Vote was unsuccessful, {T} won't be banned.", {
T = target_name
})
end
end
local title = "Ban " .. target_name .. "?"
if reason then
title = title .. " (" .. reason .. ")"
end
if start_vote(ply, callback, title, "Yes", "No") then
if reason then
sam.player.send_message(nil, "{A} started a voteban against {T} for {V} ({V_2})", {
A = ply, T = targets, V = sam.format_length(length), V_2 = reason
})
else
sam.player.send_message(nil, "{A} started a voteban against {T} for {V}", {
A = ply, T = targets, V = sam.format_length(length)
})
end
end
end)
:End()
command.new("votemute")
:SetPermission("votemute", "admin")
:AddArg("player", {single_target = true})
:AddArg("text", {hint = "reason", optional = true})
:GetRestArgs()
:Help("Start a vote to mute and gag a player.")
:OnExecute(function(ply, targets, reason)
local _reason = reason and (" (" .. reason .. ")") or ""
local target = targets[1]
local target_name = target:Name()
local callback = function(index, option, count, total_count)
if not IsValid(target) then
sam.player.send_message(nil, "Vote was canceled because {T} left.", {
T = target_name
})
return
end
if index == 1 then
RunConsoleCommand("sam", "mute", "#" .. target:EntIndex(), 0, "votemute" .. _reason)
RunConsoleCommand("sam", "gag", "#" .. target:EntIndex(), 0, "votemute" .. _reason)
sam.player.send_message(nil, "Vote was successful, {T} has been muted. ({V})", {
T = target_name, V = reason
})
else
sam.player.send_message(nil, "Vote was unsuccessful, {T} won't be muted.", {
T = target_name
})
end
end
local title = "Mute " .. target_name .. "?" .. _reason
if start_vote(ply, callback, title, "Yes", "No") then
if reason then
sam.player.send_message(nil, "{A} started a votemute against {T} ({V}).", {
A = ply, T = targets, V = reason
})
else
sam.player.send_message(nil, "{A} started a votemute against {T}.", {
A = ply, T = targets
})
end
end
end)
:End()
command.new("votemap")
:SetPermission("votemap", "admin")
:AddArg("map", {exclude_current = true})
:AddArg("map", {optional = true, exclude_current = true})
:AddArg("map", {optional = true, exclude_current = true})
:GetRestArgs()
:Help("Start a vote to change map.")
:OnExecute(function(ply, ...)
local callback = function(_, option, count, total_count)
sam.player.send_message(nil, "Map vote for {V} has been passed. ({V_2}/{V_3})", {
V = option, V_2 = count, V_3 = total_count
})
if sam.is_valid_map(option) then
RunConsoleCommand("sam", "map", option)
end
end
local args = {...}
for i = select("#", ...), 1, -1 do
if args[i] == "None" then
args[i] = nil
end
end
table.insert(args, "Extend Current Map")
if start_vote(ply, callback, "Vote for the next map!", unpack(args)) then
sam.player.send_message(nil, "{A} started a map change vote.", {
A = ply
})
end
end)
:End()

View File

@@ -0,0 +1,18 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local netstream = sam.netstream
netstream.Hook("PlaySound", function(sound)
surface.PlaySound(sound)
end)

View File

@@ -0,0 +1,79 @@
--[[
| 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 SAM_LOADED then return end
local netstream = sam.netstream
local nwvars = {}
if SERVER then
function sam.player.set_nwvar(ply, key, value, force)
local id = ply:EntIndex()
if force or nwvars[id][key] ~= value then
nwvars[id][key] = value
netstream.Start(nil, "SetNWVar", id, key, value)
end
end
end
if CLIENT then
function sam.player.set_nwvar(ply, key, value)
local id_vars = nwvars[ply:EntIndex()]
id_vars[key] = value
end
netstream.Hook("SetNWVar", function(id, key, value)
local id_vars = nwvars[id]
if id_vars == nil then
nwvars[id] = {
[key] = value
}
else
id_vars[key] = value
end
end)
netstream.Hook("SendNWVars", function(vars)
nwvars = vars
end)
netstream.Hook("RemoveNWVar", function(id)
nwvars[id] = nil
end)
end
function sam.player.get_nwvar(ply, key, default)
local value = nwvars[ply:EntIndex()]
if value then
value = value[key]
if value ~= nil then
return value
end
end
return default
end
if SERVER then
hook.Add("OnEntityCreated", "SAM.NWVars", function(ent)
if ent:IsPlayer() and ent:IsValid() then
nwvars[ent:EntIndex()] = {}
netstream.Start(ent, "SendNWVars", nwvars)
end
end)
hook.Add("EntityRemoved", "SAM.NWVars", function(ent)
if ent:IsPlayer() then
local id = ent:EntIndex()
nwvars[id] = nil
netstream.Start(nil, "RemoveNWVar", id)
end
end)
end

View File

@@ -0,0 +1,183 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local config = sam.config
do
local _player = {}
sam.player = setmetatable(sam.player, {
__index = _player,
__newindex = function(_, k, v)
_player[k] = v
if sam.isfunction(v) and debug.getlocal(v, 1) == "ply" then
FindMetaTable("Player")["sam_" .. k] = v
sam.console["sam_" .. k] = v
end
end
})
end
function sam.player.find_by_name(name)
name = name:lower()
local current, players = nil, player.GetAll()
for i = 1, #players do
local ply = players[i]
local found = ply:Name():lower():find(name, 1, true)
if found then
if current then
if not sam.istable(current) then
current = {current, ply}
else
table.insert(current, ply)
end
else
current = ply
end
end
end
return current
end
do
if CLIENT then
config.add_menu_setting("Chat Prefix (Leave empty for no prefix)", function()
local entry = vgui.Create("SAM.TextEntry")
entry:SetPlaceholder("")
entry:SetNoBar(true)
entry:SetConfig("ChatPrefix", "")
return entry
end)
end
function sam.player.send_message(ply, msg, tbl)
if SERVER then
if sam.isconsole(ply) then
local result = sam.format_message(msg, tbl)
sam.print(unpack(result, 1, result.__cnt))
else
return sam.netstream.Start(ply, "send_message", msg, tbl)
end
else
local prefix_result = sam.format_message(config.get("ChatPrefix", ""))
local prefix_n = #prefix_result
local result = sam.format_message(msg, tbl, prefix_result, prefix_n)
chat.AddText(unpack(result, 1, result.__cnt))
end
end
if SERVER then
function sam.player.add_text(ply, ...)
if sam.isconsole(ply) then
sam.print(...)
else
sam.netstream.Start(ply, "add_text", ...)
end
end
end
if CLIENT then
sam.netstream.Hook("send_message", function(msg, tbl)
sam.player.send_message(nil, msg, tbl)
end)
sam.netstream.Hook("add_text", function(...)
chat.AddText(...)
end)
end
end
do
local PLAYER = FindMetaTable("Player")
function PLAYER:IsAdmin()
return self:CheckGroup("admin")
end
function PLAYER:IsSuperAdmin()
return self:CheckGroup("superadmin")
end
local inherits_from = sam.ranks.inherits_from
function PLAYER:CheckGroup(name)
return inherits_from(self:sam_getrank(), name)
end
local has_permission = sam.ranks.has_permission
function PLAYER:HasPermission(perm)
return has_permission(self:sam_getrank(), perm)
end
local can_target = sam.ranks.can_target
function PLAYER:CanTarget(ply)
return can_target(self:sam_getrank(), ply:sam_getrank())
end
function PLAYER:CanTargetRank(rank)
return can_target(self:sam_getrank(), rank)
end
local get_ban_limit = sam.ranks.get_ban_limit
function PLAYER:GetBanLimit(ply)
return get_ban_limit(self:sam_getrank())
end
function PLAYER:sam_get_play_time()
return self:sam_get_nwvar("play_time", 0) + self:sam_get_session_time()
end
function PLAYER:sam_get_session_time()
return os.time() - self:sam_get_nwvar("join_time", 0)
end
function PLAYER:sam_getrank()
return self:sam_get_nwvar("rank", "user")
end
function PLAYER:sam_setrank(name)
return self:sam_set_nwvar("rank", name)
end
if SERVER then
function PLAYER:Ban(length)
self:sam_ban(length)
end
hook.Remove("PlayerInitialSpawn", "PlayerAuthSpawn")
end
end
do
local set_cooldown = function(ply, name, time)
if not ply.sam_cool_downs then
ply.sam_cool_downs = {}
end
ply.sam_cool_downs[name] = SysTime() + time
return true
end
function sam.player.check_cooldown(ply, name, time)
if not ply.sam_cool_downs or not ply.sam_cool_downs[name] then
return set_cooldown(ply, name, time)
end
local current_time = SysTime()
local cool_down = ply.sam_cool_downs[name]
if cool_down > current_time then
return false, cool_down - current_time
else
return set_cooldown(ply, name, time)
end
end
end

139
lua/sam/player/sv_auth.lua Normal file
View File

@@ -0,0 +1,139 @@
--[[
| 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 SAM_LOADED then return end
local sam, SQL = sam, sam.SQL
local auth_player = function(data, ply)
if not ply:IsValid() then return end
if ply:sam_get_nwvar("is_authed") then return end
local steamid = ply:SteamID()
local rank, expiry_date, play_time
local first_join = false
if data then
rank, expiry_date, play_time = data.rank, tonumber(data.expiry_date), tonumber(data.play_time)
SQL.FQuery([[
UPDATE
`sam_players`
SET
`name` = {1},
`last_join` = {2}
WHERE
`steamid` = {3}
]], {ply:Name(), os.time(), steamid})
else
rank, expiry_date, play_time = "user", 0, 0
first_join = true
local time = os.time()
SQL.FQuery([[
INSERT INTO
`sam_players`(
`steamid`,
`name`,
`rank`,
`expiry_date`,
`first_join`,
`last_join`,
`play_time`
)
VALUES
({1}, {2}, {3}, {4}, {5}, {6}, {7})
]], {steamid, ply:Name(), rank, 0, time, time, 0})
end
ply:SetUserGroup(rank)
ply:sam_setrank(rank)
ply:sam_start_rank_timer(expiry_date)
ply:sam_set_nwvar("join_time", os.time())
ply:sam_set_nwvar("play_time", play_time)
ply:sam_set_nwvar("is_authed", true)
hook.Call("SAM.AuthedPlayer", nil, ply, steamid, first_join)
timer.Simple(0, function()
if IsValid(ply) then
sam.client_hook_call("SAM.AuthedPlayer", ply, steamid, first_join)
end
end)
end
hook.Add("PlayerInitialSpawn", "SAM.AuthPlayer", function(ply)
SQL.FQuery([[
SELECT
`rank`,
`expiry_date`,
`play_time`
FROM
`sam_players`
WHERE
`steamid` = {1}
]], {ply:SteamID()}, auth_player, true, ply)
end)
sam.player.auth = auth_player
hook.Add("SAM.AuthedPlayer", "SetSuperadminToListenServer", function(ply)
if game.SinglePlayer() or ply:IsListenServerHost() then
ply:sam_set_rank("superadmin")
end
end)
hook.Add("SAM.AuthedPlayer", "CheckIfFullyAuthenticated", function(ply)
timer.Simple(0, function()
if not IsValid(ply) then return end
if ply:IsBot() then return end
if not ply.IsFullyAuthenticated or ply:IsFullyAuthenticated() then return end
if game.SinglePlayer() or ply:IsListenServerHost() then return end
ply:Kick("Your SteamID wasn't fully authenticated, try restarting steam.")
end)
end)
do
local format = string.format
local floor = math.floor
local SysTime = SysTime
local last_save = SysTime()
local save_play_time = function(ply)
if not ply:sam_get_nwvar("is_authed") then return end
local query = format([[
UPDATE
`sam_players`
SET
`play_time` = %u
WHERE
`steamid` = '%s'
]], floor(ply:sam_get_play_time()), ply:SteamID())
SQL.Query(query)
end
hook.Add("Think", "SAM.Player.SaveTimes", function()
if SysTime() - last_save < 60 then return end
SQL.Begin()
local players = player.GetHumans()
for i = 1, #players do
save_play_time(players[i])
end
SQL.Commit()
sam.hook_call("SAM.UpdatedPlayTimes")
last_save = SysTime()
end)
hook.Add("PlayerDisconnected", "SAM.Player.SaveTime", save_play_time)
end

291
lua/sam/player/sv_bans.lua Normal file
View File

@@ -0,0 +1,291 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local SQL = sam.SQL
local DEFAULT_REASON = sam.language.get("default_reason")
local set_cached, get_cached do
local cached_bans = {}
function set_cached(k, v)
cached_bans[k] = v
end
function get_cached(k)
return cached_bans[k]
end
timer.Create("SAM.ClearCachedBans", 60 * 2.5, 0, function()
table.Empty(cached_bans)
end)
end
function sam.format_ban_message(admin_name, admin_steamid, reason, unban_date)
unban_date = unban_date == 0 and "never" or sam.format_length((unban_date - os.time()) / 60)
local message_tbl
if admin_name == "" then
message_tbl = sam.format_message("ban_message", {
S = admin_steamid, S_2 = reason, S_3 = unban_date
})
else
message_tbl = sam.format_message("ban_message_2", {
S = admin_name, S_2 = admin_steamid, S_3 = reason, S_4 = unban_date
})
end
local message = ""
for i = 1, #message_tbl do
local v = message_tbl[i]
if sam.isstring(v) then
message = message .. v
end
end
return message
end
function sam.player.ban(ply, length, reason, admin_steamid)
if sam.type(ply) ~= "Player" or not ply:IsValid() then
error("invalid player")
end
if ply.sam_is_banned then return end
local unban_date
if not sam.isnumber(length) or length <= 0 then
unban_date = 0
else
unban_date = (math.min(length, 31536000) * 60) + os.time()
end
if not sam.isstring(reason) then
reason = DEFAULT_REASON
end
if ply:IsBot() then -- you can't ban bots!
return ply:Kick(reason)
end
if not sam.is_steamid(admin_steamid) then -- 4958fc64d974ee5657060aff5c15da1f4f4c5a3de14720e33e3dfbd4622d384a
admin_steamid = "Console"
end
local steamid = ply:SteamID()
SQL.FQuery([[
INSERT INTO
`sam_bans`(
`steamid`,
`reason`,
`admin`,
`unban_date`
)
VALUES
({1}, {2}, {3}, {4})
]], {steamid, reason, admin_steamid, unban_date})
local admin_name = ""
do
local admin = player.GetBySteamID(admin_steamid)
if admin then
admin_name = admin:Name()
end
end
ply.sam_is_banned = true
set_cached(steamid, nil)
sam.hook_call("SAM.BannedPlayer", ply, unban_date, reason, admin_steamid)
ply:Kick(sam.format_ban_message(admin_name, admin_steamid, reason, unban_date))
end
function sam.player.ban_id(steamid, length, reason, admin_steamid)
sam.is_steamid(steamid, true)
do
local ply = player.GetBySteamID(steamid)
if ply then
return ply:sam_ban(length, reason, admin_steamid)
end
end
local unban_date
if not sam.isnumber(length) or length <= 0 then
unban_date = 0
else
unban_date = (math.min(length, 31536000) * 60) + os.time()
end
if not sam.isstring(reason) then
reason = DEFAULT_REASON
end
local query
if SQL.IsMySQL() then
query = [[
INSERT INTO
`sam_bans`(
`steamid`,
`reason`,
`admin`,
`unban_date`
)
VALUES
({1}, {2}, {3}, {4}) ON DUPLICATE KEY
UPDATE
`reason` = VALUES(`reason`),
`admin` = VALUES(`admin`),
`unban_date` = VALUES(`unban_date`)
]]
else
query = [[
INSERT INTO
`sam_bans`(
`steamid`,
`reason`,
`admin`,
`unban_date`
)
VALUES
({1}, {2}, {3}, {4}) ON CONFLICT(`steamid`) DO
UPDATE SET
`reason` = excluded.`reason`,
`admin` = excluded.`admin`,
`unban_date` = excluded.`unban_date`
]]
end
SQL.FQuery(query, {steamid, reason, admin_steamid, unban_date})
local admin_name = ""
if sam.is_steamid(admin_steamid) then
local admin = player.GetBySteamID(admin_steamid)
if admin then
admin_name = admin:Name()
end
else
admin_steamid = "Console"
end
set_cached(steamid, nil)
sam.hook_call("SAM.BannedSteamID", steamid, unban_date, reason, admin_steamid)
sam.player.kick_id(steamid, sam.format_ban_message(admin_name, admin_steamid, reason, unban_date))
end
function sam.player.unban(steamid, admin)
sam.is_steamid(steamid, true)
if not sam.is_steamid(admin) then
admin = "Console"
end
SQL.FQuery([[
DELETE FROM
`sam_bans`
WHERE
`steamid` = {1}
]], {steamid})
set_cached(steamid, false)
sam.hook_call("SAM.UnbannedSteamID", steamid, admin)
end
local check_for_unban = function(steamid, ban_data, callback)
local to_return = ban_data
local unban_date = tonumber(ban_data.unban_date)
if unban_date ~= 0 and os.time() >= unban_date then
to_return = false
sam.player.unban(steamid)
end
if callback then
callback(to_return, steamid)
else
return to_return
end
end
do
local query_callback = function(data, arguments)
local steamid, callback = arguments[1], arguments[2]
if data then
set_cached(steamid, data)
check_for_unban(steamid, data, callback)
else
set_cached(steamid, false)
callback(false, steamid)
end
end
function sam.player.is_banned(steamid, callback)
sam.is_steamid(steamid, true)
local ban_data = get_cached(steamid)
if ban_data then
check_for_unban(steamid, ban_data, callback)
elseif ban_data == false then
callback(false, steamid)
else
local query = [[
SELECT
`sam_bans`.`steamid`,
`sam_bans`.`reason`,
`sam_bans`.`admin`,
`sam_bans`.`unban_date`,
IFNULL(`sam_players`.`name`, '') AS `admin_name`
FROM
`sam_bans`
LEFT OUTER JOIN
`sam_players`
ON
`sam_bans`.`admin` = `sam_players`.`steamid`
WHERE
`sam_bans`.`steamid` = ]] .. SQL.Escape(steamid)
SQL.Query(query, query_callback, true, {steamid, callback})
end
end
end
do
local steamids = {}
local query_callback = function(ban_data, steamid)
steamids[steamid] = nil
if ban_data then
sam.player.kick_id(steamid, sam.format_ban_message(ban_data.admin_name, ban_data.admin, ban_data.reason, tonumber(ban_data.unban_date)))
end
end
gameevent.Listen("player_connect")
hook.Add("player_connect", "SAM.CheckIfBanned", function(data)
local steamid = data.networkid
if data.bot == 0 and not steamids[steamid] then
steamids[steamid] = true
sam.player.is_banned(steamid, query_callback)
end
end)
hook.Add("CheckPassword", "SAM.CheckIfBanned", function(steamid64)
local steamid = util.SteamIDFrom64(steamid64)
local ban_data = get_cached(steamid)
if not ban_data then return end
ban_data = check_for_unban(steamid, ban_data)
if not ban_data then return end
return false, sam.format_ban_message(ban_data.admin_name, ban_data.admin, ban_data.reason, tonumber(ban_data.unban_date))
end)
end
sam.player.ban_set_cached, sam.player.ban_get_cached = set_cached, get_cached

View File

@@ -0,0 +1,157 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local netstream = sam.netstream
do
local connected_players = {}
function sam.player.kick_id(id, reason)
reason = sam.isstring(reason) and reason or sam.language.get("default_reason")
reason = reason:sub(1, 400)
game.KickID(id, reason)
end
function sam.player.is_connected(steamid)
return connected_players[steamid] and true or false
end
function sam.get_connected_players()
return connected_players
end
gameevent.Listen("player_connect")
hook.Add("player_connect", "SAM.ConnectedPlayers", function(data)
connected_players[data.networkid] = true
end)
gameevent.Listen("player_disconnect")
hook.Add("player_disconnect", "SAM.ConnectedPlayers", function(data)
connected_players[data.networkid] = nil
end)
end
function sam.player.set_exclusive(ply, reason)
ply.sam_exclusive_reason = reason
end
function sam.player.get_exclusive(ply, admin)
local reason = ply.sam_exclusive_reason
if reason then
if ply == admin then
return "You are " .. reason .. "!"
else
return ply:Name() .. " is " .. reason .. "!"
end
end
end
do
local hide_weapons = function(ply, should_hide)
for _, v in pairs(ply:GetWeapons()) do
v:SetNoDraw(should_hide)
end
local physgun_beams = ents.FindByClassAndParent("physgun_beam", ply)
if physgun_beams then
for i = 1, #physgun_beams do
physgun_beams[i]:SetNoDraw(should_hide)
end
end
end
local cloak = function(ply, should_cloak)
ply:SetNoDraw(should_cloak)
ply:DrawWorldModel(not should_cloak)
ply:SetRenderMode(should_cloak and RENDERMODE_TRANSALPHA or RENDERMODE_NORMAL)
ply:Fire("alpha", should_cloak and 0 or 255, 0)
ply:sam_set_nwvar("cloaked", should_cloak)
hide_weapons(ply, should_cloak)
end
function sam.player.cloak(ply)
cloak(ply, true)
end
function sam.player.uncloak(ply)
cloak(ply, false)
end
hook.Add("PlayerSpawn", "SAM.CloakPlayer", function(ply)
if ply:sam_get_nwvar("cloaked") then
cloak(ply, true)
end
end)
hook.Add("PlayerSwitchWeapon", "SAM.CloakPlayer", function(ply)
if ply:sam_get_nwvar("cloaked") then
timer.Create("SAM.HideWeapons" .. ply:SteamID(), 0, 1, function()
if IsValid(ply) and ply:sam_get_nwvar("cloaked") then
hide_weapons(ply, true)
end
end)
end
end)
end
do
local call_hook = function(ply)
local can_spawn = hook.Call("SAM.CanPlayerSpawn", nil, ply)
if can_spawn ~= nil then
return can_spawn
end
end
local spawn_hooks = {
"Effect", "NPC",
"Object", "Prop",
"Ragdoll", "SENT",
"SWEP", "Vehicle"
}
for k, v in ipairs(spawn_hooks) do
hook.Add("PlayerSpawn" .. v, "SAM.CanPlayerSpawn", call_hook)
end
end
do
local persistent_data = {}
function sam.player.set_pdata(ply, key, value)
local ply_pdata = persistent_data[ply:AccountID()]
if ply_pdata then
ply_pdata[key] = value
else
persistent_data[ply:AccountID()] = {
[key] = value
}
end
end
function sam.player.get_pdata(ply, key, default)
local ply_pdata = persistent_data[ply:AccountID()]
if ply_pdata then
local value = ply_pdata[key]
if value ~= nil then
return value
end
end
return default
end
end
function sam.player.play_sound(ply, sound)
netstream.Start(ply, "PlaySound", sound)
end
--[[/*953ed60e46bbecc33df4049c215b53951c0dfcb21fbe82bbeb63c201211c72dc*/]]

215
lua/sam/player/sv_ranks.lua Normal file
View File

@@ -0,0 +1,215 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local SQL, Promise = sam.SQL, sam.Promise
function sam.player.set_rank(ply, rank, length)
if sam.type(ply) ~= "Player" or not ply:IsValid() then
error("invalid player")
elseif not sam.ranks.is_rank(rank) then
error("invalid rank")
end
if not sam.isnumber(length) or length < 0 then
length = 0
end
local expiry_date = 0
if length ~= 0 then
if rank == "user" then
expiry_date = 0
else
expiry_date = (math.min(length, 31536000) * 60) + os.time()
end
end
ply:sam_start_rank_timer(expiry_date)
SQL.FQuery([[
UPDATE
`sam_players`
SET
`rank` = {1},
`expiry_date` = {2}
WHERE
`steamid` = {3}
]], {rank, expiry_date, ply:SteamID()})
local old_rank = ply:sam_getrank()
ply:SetUserGroup(rank)
ply:sam_setrank(rank)
sam.hook_call("SAM.ChangedPlayerRank", ply, rank, old_rank, expiry_date)
end
do
local set_rank_id = function(player_data, arguments)
local old_rank = player_data and player_data.rank or false
local promise, steamid, rank, length = unpack(arguments, 1, 4)
local expiry_date = 0
if length ~= 0 then
if rank == "user" then
expiry_date = 0
else
expiry_date = (math.min(length, 31536000) * 60) + os.time()
end
end
local exists = true
if old_rank == false then
exists, old_rank = false, "user"
local time = os.time()
SQL.FQuery([[
INSERT INTO
`sam_players`(
`steamid`,
`name`,
`rank`,
`expiry_date`,
`first_join`,
`last_join`,
`play_time`
)
VALUES
({1}, {2}, {3}, {4}, {5}, {6}, {7})
]], {steamid, "", rank, 0, time, time, 0})
else
SQL.FQuery([[
UPDATE
`sam_players`
SET
`rank` = {1},
`expiry_date` = {2}
WHERE
`steamid` = {3}
]], {rank, expiry_date, steamid})
end
promise:resolve()
sam.hook_call("SAM.ChangedSteamIDRank", steamid, rank, old_rank, expiry_date, exists)
end
function sam.player.set_rank_id(steamid, rank, length)
sam.is_steamid(steamid, true)
if not sam.ranks.is_rank(rank) then
error("invalid rank")
end
local promise = Promise.new()
do
local ply = player.GetBySteamID(steamid)
if ply then
promise:resolve(ply:sam_set_rank(rank, length))
return promise
end
end
if not sam.isnumber(length) or length < 0 then
length = 0
end
SQL.FQuery([[
SELECT
`rank`
FROM
`sam_players`
WHERE
`steamid` = {1}
]], {steamid}, set_rank_id, true, {promise, steamid, rank, length})
return promise
end
end
do
local get_rank = function(data, callback)
if not data then
callback(false)
else
callback(data.rank)
end
end
function sam.player.get_rank(steamid, callback)
sam.is_steamid(steamid, true)
SQL.FQuery([[
SELECT
`rank`
FROM
`sam_players`
WHERE
`steamid` = {1}
]], {steamid}, get_rank, true, callback)
end
end
do
local remove_rank_timer = function(ply)
timer.Remove("SAM.RankTimer." .. ply:SteamID())
end
function sam.player.start_rank_timer(ply, expiry_date)
ply.sam_rank_expirydate = expiry_date
if expiry_date == 0 then -- permanent rank
return remove_rank_timer(ply)
end
expiry_date = expiry_date - os.time()
timer.Create("SAM.RankTimer." .. ply:SteamID(), expiry_date, 1, function()
sam.player.send_message(nil, "rank_expired", {
T = {ply, admin = sam.console}, V = ply:sam_getrank()
})
ply:sam_set_rank("user")
end)
end
hook.Add("PlayerDisconnected", "SAM.RemoveRankTimer", remove_rank_timer)
end
hook.Add("SAM.OnRankRemove", "ResetPlayerRank", function(name)
for _, ply in ipairs(player.GetAll()) do
if ply:sam_getrank() == name then
ply:sam_set_rank("user")
end
end
SQL.FQuery([[
UPDATE
`sam_players`
SET
`rank` = 'user',
`expiry_date` = 0
WHERE
`rank` = {1}
]], {name})
end)
hook.Add("SAM.RankNameChanged", "ChangePlayerRankName", function(old, new)
for _, ply in ipairs(player.GetAll()) do
if ply:sam_getrank() == old then
ply:sam_set_rank(new)
end
end
SQL.FQuery([[
UPDATE
`sam_players`
SET
`rank` = {1}
WHERE
`rank` = {2}
]], {new, old})
end)

124
lua/sam/ranks/sh_ranks.lua Normal file
View File

@@ -0,0 +1,124 @@
--[[
| 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 SAM_LOADED then return end
SAM_IMMUNITY_SUPERADMIN = 100
SAM_IMMUNITY_ADMIN = 50
SAM_IMMUNITY_USER = 1
function sam.ranks.get_ranks()
return sam.get_global("Ranks") or {}
end
function sam.ranks.get_rank(rank)
local ranks = sam.ranks.get_ranks()
return ranks[rank]
end
function sam.ranks.is_rank(rank)
if sam.ranks.get_rank(rank) then
return true
else
return false
end
end
function sam.ranks.is_default_rank(rank)
return rank == "superadmin" or rank == "admin" or rank == "user"
end
function sam.ranks.inherits_from(rank, rank_2)
if rank == rank_2 then
return true
end
while true do
rank = sam.ranks.get_rank(rank)
if rank then
local inherits_from = rank.inherit
if inherits_from == rank_2 then
return true
end
rank = rank.inherit
else
return false
end
end
end
function sam.ranks.has_permission(rank, permission)
while true do
if rank == "superadmin" then
return true
end
rank = sam.ranks.get_rank(rank)
if rank then
local rank_permission = rank.data.permissions[permission]
if rank_permission ~= nil then
return rank_permission
end
rank = rank.inherit
else
return false
end
end
end
function sam.ranks.get_limit(rank, limit_type)
while true do
if rank == "superadmin" then return -1 end
rank = sam.ranks.get_rank(rank)
if rank then
local limit = rank.data.limits[limit_type]
if limit ~= nil then
return limit
end
rank = rank.inherit
else
return cvars.Number("sbox_max" .. limit_type, 0)
end
end
end
function sam.ranks.get_immunity(rank)
rank = sam.ranks.get_rank(rank)
return rank and rank.immunity or false
end
function sam.ranks.can_target(rank_1, rank_2)
rank_1, rank_2 = sam.ranks.get_rank(rank_1), sam.ranks.get_rank(rank_2)
if not rank_1 or not rank_2 then
return false
end
return rank_1.immunity >= rank_2.immunity
end
function sam.ranks.get_ban_limit(rank)
rank = sam.ranks.get_rank(rank)
return rank and rank.ban_limit or false
end
if CLIENT then
hook.Add("SAM.ChangedGlobalVar", "SAM.Ranks.CheckLoadedRanks", function(key, value)
if key == "Ranks" then
hook.Call("SAM.LoadedRanks", nil, value)
hook.Remove("SAM.ChangedGlobalVar", "SAM.Ranks.CheckLoadedRanks")
end
end)
end

448
lua/sam/ranks/sv_ranks.lua Normal file
View File

@@ -0,0 +1,448 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local SQL, util = sam.SQL, util
local ranks = {}
local ranks_loaded = false
function sam.ranks.sync()
sam.set_global("Ranks", ranks, true)
end
function sam.ranks.add_rank(name, inherit, immunity, ban_limit)
if not sam.isstring(name) then
error("invalid rank name")
end
if not ranks_loaded then
error("loading ranks")
end
if ranks[name] then
return "rank_exists"
end
if not sam.isstring(inherit) and name ~= "user" then
inherit = "user"
end
local data = {
permissions = {},
limits = {}
}
if name ~= "user" then
local inherit_rank = ranks[inherit]
if not inherit_rank then
error("invalid rank to inherit from")
end
if not sam.isnumber(immunity) then
immunity = inherit_rank.immunity
end
if not sam.isnumber(ban_limit) then
ban_limit = inherit_rank.ban_limit
end
end
if name == "superadmin" then
immunity = 100
elseif name == "user" then
immunity = 1
else
immunity = math.Clamp(immunity, 2, 99)
end
ban_limit = math.Clamp(ban_limit, 0, 31536000)
SQL.FQuery([[
INSERT INTO
`sam_ranks`(
`name`,
`inherit`,
`immunity`,
`ban_limit`,
`data`
)
VALUES
({1}, {2}, {3}, {4}, {5})
]], {name, inherit or "NULL", immunity, ban_limit, util.TableToJSON(data)})
local rank = {
name = name,
inherit = inherit,
immunity = immunity,
ban_limit = ban_limit,
data = data
}
ranks[name] = rank
sam.ranks.sync()
sam.hook_call("SAM.AddedRank", name, rank)
end
function sam.ranks.remove_rank(name)
if not sam.isstring(name) then
error("invalid rank")
end
if not ranks_loaded then
error("loading ranks")
end
-- can't just remove default ranks!!
if sam.ranks.is_default_rank(name) then
return "default_rank"
end
// 13271fc0ed1d2e4e36589c40f54f685d96ba64e69b7c5bc70aa14d4bb236ab18
local rank = ranks[name]
if not rank then
error("invalid rank")
end
SQL.FQuery([[
DELETE FROM
`sam_ranks`
WHERE
`name` = {1}
]], {name})
sam.hook_call("SAM.OnRankRemove", name, rank)
ranks[name] = nil
sam.ranks.sync()
sam.hook_call("SAM.RemovedRank", name)
end
function sam.ranks.rename_rank(old, new)
if not sam.isstring(old) then
error("invalid old name")
end
if not sam.isstring(new) then
error("invalid new name")
end
if not ranks_loaded then
error("loading ranks")
end
local old_rank = ranks[old]
if not old_rank then
error("invalid rank")
end
if sam.ranks.is_default_rank(old) then
return "default_rank"
end
if ranks[new] then
error("new rank name exists")
end
old_rank.name = new
ranks[new], ranks[old] = ranks[old], nil
SQL.FQuery([[
UPDATE
`sam_ranks`
SET
`name` = {1}
WHERE
`name` = {2}
]], {new, old})
sam.ranks.sync()
sam.hook_call("SAM.RankNameChanged", old, new)
end
function sam.ranks.change_inherit(name, inherit)
if not sam.isstring(name) then
error("invalid rank")
end
if not sam.isstring(inherit) then
error("invalid rank to inherit from")
end
if not ranks_loaded then
error("loading ranks")
end
local rank = ranks[name]
if not rank then
error("invalid rank")
end
if name == "user" or name == "superadmin" then return end
if not ranks[inherit] then
error("invalid rank to inherit from")
end
if name == inherit then
error("you can't inherit from the same rank")
end
if rank.inherit == inherit then return end
local old_inherit = rank.inherit
-- e3a33576750e51364148739225303ca04e922554b5b1b0197a7475aa52cc2634!!!
rank.inherit = inherit
SQL.FQuery([[
UPDATE
`sam_ranks`
SET
`inherit` = {1}
WHERE
`name` = {2}
]], {inherit, name})
sam.ranks.sync()
sam.hook_call("SAM.ChangedInheritRank", name, inherit, old_inherit)
end
function sam.ranks.change_immunity(name, new_immunity)
if not sam.isstring(name) then
error("invalid rank")
end
if not sam.isnumber(new_immunity) then
error("invalid immunity")
end
if not ranks_loaded then
error("loading ranks")
end
local rank = ranks[name]
if not rank then
error("invalid rank")
end
if name == "user" or name == "superadmin" then return end
new_immunity = math.Clamp(new_immunity, 2, 99) // 8bc01ecde3d91528be5a061c416d9334bd200f6f3f76a0083468334f18f03063!!!
local old_immunity = rank.immunity
rank.immunity = new_immunity
SQL.FQuery([[
UPDATE
`sam_ranks`
SET
`immunity` = {1}
WHERE
`name` = {2}
]], {new_immunity, name})
sam.ranks.sync()
sam.hook_call("SAM.RankImmunityChanged", name, new_immunity, old_immunity)
end
function sam.ranks.change_ban_limit(name, new_limit)
if not sam.isstring(name) then
error("invalid rank")
end
if not sam.isnumber(new_limit) then
error("invalid ban limit")
end
if not ranks_loaded then
error("loading ranks")
end
local rank = ranks[name]
if not rank then
error("invalid rank")
end
if name == "superadmin" then return end
new_limit = math.Clamp(new_limit, 0, 31536000)
local old_limit = rank.ban_limit
rank.ban_limit = new_limit
SQL.FQuery([[
UPDATE
`sam_ranks`
SET
`ban_limit` = {1}
WHERE
`name` = {2}
]], {new_limit, name})
sam.ranks.sync()
sam.hook_call("SAM.RankBanLimitChanged", name, new_limit, old_limit)
end
function sam.ranks.give_permission(name, permission)
if not sam.isstring(name) then
error("invalid rank")
end
if not sam.isstring(permission) then
error("invalid permission")
end
if not ranks_loaded then
error("loading ranks")
end
if name == "superadmin" then return end
local rank = ranks[name]
if not rank then
error("invalid rank")
end
local permissions = rank.data.permissions
if permissions[permission] then return end
-- 3db898ddbc4a12ba9980d00541151133f8e09c55bcb7650487608c39edd6c162!!!
permissions[permission] = true
SQL.FQuery([[
UPDATE
`sam_ranks`
SET
`data` = {1}
WHERE
`name` = {2}
]], {util.TableToJSON(rank.data), name})
sam.ranks.sync()
sam.hook_call("SAM.RankPermissionGiven", name, permission)
end
function sam.ranks.take_permission(name, permission)
if not sam.isstring(name) then
error("invalid rank")
end
if not sam.isstring(permission) then
error("invalid permission")
end
if not ranks_loaded then
error("loading ranks")
end
if name == "superadmin" then return end
local rank = ranks[name]
if not rank then
error("invalid rank")
end
local permissions = rank.data.permissions
if permissions[permission] == false then return end
permissions[permission] = false
SQL.FQuery([[
UPDATE
`sam_ranks`
SET
`data` = {1}
WHERE
`name` = {2}
]], {util.TableToJSON(rank.data), name})
sam.ranks.sync()
sam.hook_call("SAM.RankPermissionTaken", name, permission)
end
function sam.ranks.set_limit(name, type, limit)
if not sam.isstring(name) then
error("invalid rank")
end
if not sam.isstring(type) then
error("invalid limit type")
end
if not sam.isnumber(limit) then
error("invalid limit value")
end
if not ranks_loaded then
error("loading ranks")
end
if name == "superadmin" then return end
local rank = ranks[name]
if not rank then
error("invalid rank")
end
limit = math.Clamp(limit, -1, 1000)
local limits = rank.data.limits
if limits[type] == limit then return end
limits[type] = limit
SQL.FQuery([[
UPDATE
`sam_ranks`
SET
`data` = {1}
WHERE
`name` = {2}
]], {util.TableToJSON(rank.data), name})
sam.ranks.sync()
sam.hook_call("SAM.RankChangedLimit", name, type, limit)
end
function sam.ranks.ranks_loaded()
return ranks_loaded
end
sam.ranks.sync()
hook.Add("SAM.DatabaseLoaded", "LoadRanks", function()
SQL.Query([[
SELECT
*
FROM
`sam_ranks`
]], function(sam_ranks)
for _, v in ipairs(sam_ranks) do
local name = v.name
ranks[name] = {
name = name,
inherit = name ~= "user" and v.inherit,
immunity = tonumber(v.immunity),
ban_limit = tonumber(v.ban_limit),
data = util.JSONToTable(v.data)
}
end
ranks_loaded = true
if #ranks < 3 then
sam.ranks.add_rank("user", nil, nil, 0)
sam.ranks.add_rank("admin", "user", SAM_IMMUNITY_ADMIN, 20160 --[[2 weeks]])
sam.ranks.add_rank("superadmin", "admin", SAM_IMMUNITY_SUPERADMIN, 0)
/*a5a6b7626ac8542cad9e421879f6138c874bc3609653dc032c142493caff4203*/
end
sam.ranks.sync()
hook.Call("SAM.LoadedRanks", nil, ranks)
end):wait()
end)

View File

@@ -0,0 +1,480 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local netstream = sam.netstream
local SUI = sam.SUI
local config = sam.config
local Trim = string.Trim
local muted_var = CreateClientConVar("sam_mute_reports", "0", false, false, "", 0, 1)
local position = config.get_updated("Reports.Position", "Left")
local max_reports = config.get_updated("Reports.MaxReports", 4)
local always_show = config.get_updated("Reports.AlwaysShow", true)
local pad_x = config.get_updated("Reports.XPadding", 5)
local pad_y = config.get_updated("Reports.YPadding", 5)
local duty_jobs = {}
config.hook({"Reports.DutyJobs"}, function()
local jobs = config.get("Reports.DutyJobs", ""):Split(",")
for i = #jobs, 1, -1 do
local v = Trim(jobs[i])
if v ~= "" then
jobs[v] = true
end
jobs[i] = nil
end
duty_jobs = jobs
end)
local commands = {}
config.hook({"Reports.Commands"}, function()
local cmds = config.get("Reports.Commands", ""):Split(",")
for i = 1, #cmds do
local v = Trim(cmds[i])
if v ~= "" then
cmds[i] = {
name = v,
func = function(_, ply)
if IsValid(ply) then
RunConsoleCommand("sam", v, "#" .. ply:EntIndex())
end
end
}
end
end
commands = cmds
end)
local reports = {}
local queued_reports = {}
local new_report, remove_report, check_queued, get_report, append_report
get_report = function(ply, index)
for i = 1, #reports do
local v = reports[i]
local _ply = index and v.index or v.ply
if _ply == ply then return v end
end
for i = 1, #queued_reports do
local v = queued_reports[i]
local _ply = index and v.index or v.ply
if _ply == ply then return v, i end
end
end
remove_report = function(ply)
local report, delayed_i = get_report(ply)
if delayed_i then
return table.remove(queued_reports, delayed_i)
end
local panel = report.panel
panel:MoveToNewX(position.value == "Right" and ScrW() or -panel:GetWide(), function()
for i = report.pos + 1, #reports do
local v = reports[i]
v.pos = v.pos - 1
v.panel:MoveToNewY(v.panel:GetY())
end
panel:Remove()
table.remove(reports, report.pos)
check_queued()
end)
end
check_queued = function()
while (max_reports.value - #reports > 0 and #queued_reports > 0) do
new_report(table.remove(queued_reports, 1))
end
end
append_report = function(ply, text)
local report, delayed = get_report(ply)
if delayed then
table.insert(report.comments, text)
else
report.panel:AddComment(text)
end
end
new_report = function(report)
if #reports >= max_reports.value then
return table.insert(queued_reports, report)
end
report.pos = table.insert(reports, report)
local panel = vgui.Create("SAM.Report")
panel:SetReport(report)
for k, v in ipairs(commands) do
panel:AddButton(v.name:gsub("^%l", string.upper), v.func)
end
local claim = panel:AddButton("Claim", function(self, ply)
if panel:HasReport() then
return LocalPlayer():sam_send_message("You have an active case, close it first.")
end
self.DoClick = function()
end
local claim_query = netstream.async.Start("ClaimReport", nil, ply)
claim_query:done(function(claimed)
if not IsValid(panel) then return end
if claimed then
panel:SetHasReport(ply)
self:SetText("Close")
self.background = Color(231, 76, 60, 200)
self.hover = Color(255, 255, 255, 25)
panel:FixWide()
for k, v in ipairs(panel:GetChildren()[3]:GetChildren()) do
v:SetDisabled(false)
v:SetCursor("hand")
end
self.DoClick = function()
panel:Close()
end
else
panel:SetClaimed()
end
end)
end)
local dismiss = panel:AddButton("Dismiss", function(self, ply)
self.DoClick = function()
end
panel:Close()
netstream.Start("DismissReport", ply)
end)
panel.claim = claim
claim:SetCursor("hand")
claim:SetDisabled(false)
claim.background = Color(39, 174, 96, 200)
claim.hover = Color(255, 255, 255, 25)
panel.dismiss = dismiss
dismiss:SetCursor("hand")
dismiss:SetDisabled(false)
dismiss.background = Color(231, 76, 60, 200)
dismiss.hover = Color(255, 255, 255, 25)
panel:FixWide()
local x = pad_x.value
if position.value == "Right" then
x = (ScrW() - panel:GetWide()) - x
end
panel:MoveToNewX(x)
panel:MoveToNewY(panel:GetY())
panel.new = true
for k, v in ipairs(report.comments) do
panel:AddComment(v)
end
panel.new = nil
end
netstream.Hook("Report", function(ply, comment)
if not IsValid(ply) then return end
if muted_var:GetBool() then return end
local report = get_report(ply)
if not report then
report = {
ply = ply,
index = ply:EntIndex(),
comments = {comment}
}
system.FlashWindow()
if not always_show.value and not duty_jobs[team.GetName(LocalPlayer():Team())] then
LocalPlayer():sam_send_message("({S Blue}) {S_2 Red}: {S_3}", {
S = "Report", S_2 = ply:Name(), S_3 = comment
})
else
new_report(report)
end
else
append_report(ply, comment)
end
end)
netstream.Hook("ReportClaimed", function(ply)
local report, delayed = get_report(ply)
if not report then return end
if delayed then
table.remove(queued_reports, delayed)
else
report.panel:SetClaimed()
end
end)
netstream.Hook("ReportClosed", function(index)
local report, delayed = get_report(index, true)
if not report then return end
if delayed then
table.remove(queued_reports, delayed)
else
report.panel:SetClosed()
end
end)
do
local REPORTS_HEADER = SUI.CreateFont("ReportHeader", "Roboto", 14, 540)
local REPORT_COMMENT = SUI.CreateFont("ReportComment", "Roboto", 13, 540)
local REPORT_BUTTONS = SUI.CreateFont("ReportButtons", "Roboto", 13, 550)
local Panel = {}
function Panel:Init()
sui.TDLib.Start()
self:Blur()
:Background(Color(30, 30, 30, 240))
local p_w, p_h = SUI.Scale(350), SUI.Scale(145)
self:SetSize(p_w, p_h)
local x = p_w * 2
if position.value == "Right" then
x = ScrW() + x
else
x = -x
end
self:SetPos(x, -p_h)
local top_panel = self:Add("Panel")
top_panel:Dock(TOP)
top_panel:SetTall(SUI.Scale(24))
top_panel:Background(Color(60, 60, 60, 200))
local ply_name = top_panel:Add("DLabel")
ply_name:Dock(LEFT)
ply_name:DockMargin(5, 0, 0, 0)
ply_name:SetTextColor(Color(200, 200, 200))
ply_name:SetFont(REPORTS_HEADER)
self.ply_name = ply_name
local scroll = self:Add("SAM.ScrollPanel")
scroll:Dock(FILL)
scroll:DockMargin(5, 5, 5, 5)
scroll.Paint = nil
self.scroll = scroll
local comment = scroll:Add("DLabel")
comment:Dock(TOP)
comment:SetText("")
comment:SetTextColor(Color(200, 200, 200))
comment:SetFont(REPORT_COMMENT)
comment:SetMultiline(true)
comment:SetWrap(true)
comment:SetAutoStretchVertical(true)
self.comment = comment
local bottom = self:Add("Panel")
bottom:Dock(BOTTOM)
bottom:SetTall(SUI.Scale(24))
self.bottom = bottom
sui.TDLib.End()
end
function Panel:GetY()
return (self:GetTall() + 5) * (self.report.pos - 1) + pad_y.value
end
function Panel:Close()
remove_report(self.report.ply)
end
local change_state = function(self, text)
self.claim:SetText(text)
self.claim.DoClick = function() end
self.claim:SUI_TDLib()
:Background(Color(41, 128, 185, 200))
timer.Simple(5, function()
if IsValid(self) then
self:Close()
end
end)
if self:HasReport() == self.report.ply then
self:SetHasReport()
end
self:FixWide()
end
function Panel:SetClaimed()
change_state(self, "Case clamied!")
end
function Panel:SetClosed()
change_state(self, "Case closed!")
end
function Panel:SetReport(report)
surface.PlaySound("garrysmod/balloon_pop_cute.wav")
report.panel = self
self.report = report
self.ply_name:SetText(report.ply:Name() .. " (" .. report.ply:SteamName() .. ")")
self.ply_name:SetWide(self:GetWide())
end
local disabled = Color(60, 60, 60, 200)
local click = Color(255, 255, 255, 30)
local button_paint = function(self, w, h)
draw.RoundedBox(0, 0, 0, w, h, self.background)
if self:GetDisabled() then
draw.RoundedBox(0, 0, 0, w, h, disabled)
else
if self:IsHovered() then
draw.RoundedBox(0, 0, 0, w, h, self.hover)
end
if self.Depressed then
draw.RoundedBox(0, 0, 0, w, h, click)
end
end
end
local button_click = function(self)
self.cb(self, self.report.ply)
end
local background = Color(60, 60, 60, 200)
local hover = Color(14, 134, 204, 100)
function Panel:AddButton(text, cb)
local button = self.bottom:Add("DButton")
button:Dock(LEFT)
button:SetText(text)
button:SetTextColor(Color(200, 200, 200))
button:SetFont(REPORT_BUTTONS)
button:SetDisabled(true)
button:SetCursor("arrow")
button.Paint = button_paint
button.DoClick = button_click
button.background = background
button.hover = hover
button.cb = cb
button.report = self.report
return button
end
function Panel:FixWide()
local wide = 0
for _, v in ipairs(self.bottom:GetChildren()) do
v:SizeToContents()
v:SetWide(v:GetWide() + 6)
wide = wide + v:GetWide()
end
self:SetWide(wide)
return wide
end
function Panel:OnRemove()
local reporter = self:HasReport()
if reporter then
netstream.Start("CloseReport", reporter)
self:SetHasReport()
end
end
function Panel:AddComment(text)
local comment = self.comment
local old_text = comment:GetText()
if old_text ~= "" then
old_text = old_text .. "\n"
end
if not self.new then
surface.PlaySound("ambient/water/drip4.wav")
end
comment:SetText(old_text .. "- " .. text)
comment:SizeToContents()
self.scroll:ScrollToBottom()
end
function Panel:HasReport()
return LocalPlayer().sam_has_report
end
function Panel:SetHasReport(v)
LocalPlayer().sam_has_report = v
end
local new_animation = function(panel, name)
local new_name = "anim_" .. name
panel["MoveToNew" .. name:upper()] = function(self, new, cb)
if self[new_name] then
table.RemoveByValue(self.m_AnimList, self[new_name])
end
self[new_name] = self:NewAnimation(0.2, 0, -1, function()
self[new_name] = nil
if cb then cb() end
end)
self[new_name].Think = function(_, _, frac)
self[name] = Lerp(frac, self[name], new)
end
end
end
new_animation(Panel, "x")
new_animation(Panel, "y")
vgui.Register("SAM.Report", Panel, "EditablePanel")
end

View File

@@ -0,0 +1,202 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local netstream = sam.netstream
local config = sam.config
local auto_close
config.hook({"Reports.AutoCloseTime"}, function()
auto_close = sam.parse_length(config.get("Reports.AutoCloseTime", "10m")) * 60
end)
hook.Add("SAM.LoadedConfig", "SAM.ReportsMain", function(c)
if not c["Reports.Commands"] then
sam.config.set("Reports.Commands", "goto, bring, return")
end
if not c["Reports.DutyJobs"] then
sam.config.set("Reports.DutyJobs", "Admin On Duty, Hobo, Medic")
end
end)
local get_admins = function(ply)
local admins = {}
local players = player.GetHumans()
for i = 1, #players do
local v = players[i]
if v:HasPermission("see_admin_chat") and v ~= ply then
table.insert(admins, v)
end
end
return admins
end
local remove_report_info = function(ply)
local admin = ply.sam_report_admin
if IsValid(ply) then
ply.sam_has_report = nil
ply.sam_report_admin = nil
netstream.Start(get_admins(), "ReportClosed", ply:EntIndex())
end
if IsValid(admin) then
admin.sam_claimed_report = nil
end
timer.Remove("SAM.Reports." .. ply:EntIndex())
end
function sam.player.report(ply, comment)
if not IsValid(ply) then
error("invalid player")
end
local can_use, time = ply:sam_check_cooldown("NewReport", 4)
if can_use == false then return false, time < 1 and 1 or math.Round(time) end
if not sam.isstring(comment) then
error("invalid comment")
end
comment = comment:sub(1, 120)
local admin = ply.sam_report_admin
if admin then
if IsValid(admin) then
return netstream.Start(admin, "Report", ply, comment)
else
remove_report_info(ply)
end
end
ply.sam_has_report = true
netstream.Start(get_admins(), "Report", ply, comment)
end
netstream.async.Hook("ClaimReport", function(res, ply, reporter)
if sam.type(reporter) ~= "Player" or not IsValid(reporter) or not reporter.sam_has_report then
return res(false)
end
local admin = reporter.sam_report_admin
if not IsValid(admin) then
reporter.sam_report_admin, admin = nil, nil
end
if admin and admin.sam_claimed_report then
return res(false)
end
res(true)
reporter.sam_report_admin = ply
ply.sam_claimed_report = true
local admins = get_admins(ply)
netstream.Start(admins, "ReportClaimed", reporter)
table.insert(admins, ply)
sam.player.send_message(admins, "report_claimed", {
A = ply, T = {reporter, admin = ply}
})
reporter:sam_send_message("Your report has been {S Green} by {S_2 Blue}.", {
S = "claimed", S_2 = ply:SteamName()
})
ix.log.Add(ply, "samReportClaimed", reporter)
--[[
timer.Create("SAM.Reports." .. reporter:EntIndex(), auto_close, 1, function()
remove_report_info(reporter)
if IsValid(reporter) then
sam.player.send_message(reporter, "report_aclosed")
end
end)
--]]
end, function(ply)
return ply:HasPermission("see_admin_chat")
end)
netstream.Hook("DismissReport", function(ply, reporter)
if sam.type(reporter) ~= "Player" or not IsValid(reporter) then return end
remove_report_info(reporter)
if IsValid(reporter) then
sam.player.send_message(get_admins(), "report_dismissed", {
A = ply, T = {reporter, admin = ply}
})
reporter:sam_send_message("Your report has been {S Red} by {S_2 Blue}. This may be because it was considered out of staff hands, nonsensical, self-explanatory, not an actual question/issue, or the issue was already dealt with.", {
S = "dismissed", S_2 = ply:SteamName()
})
end
end, function(ply)
return ply:HasPermission("see_admin_chat")
end)
netstream.Hook("CloseReport", function(ply, reporter)
if sam.type(reporter) ~= "Player" or not IsValid(reporter) then return end
if ply == reporter.sam_report_admin then
remove_report_info(reporter)
if IsValid(reporter) then
sam.player.send_message(get_admins(), "report_closed", {
A = ply, T = {reporter, admin = ply}
})
reporter:sam_send_message("Your report has been {S Red} by {S_2 Blue}.", {
S = "closed", S_2 = ply:SteamName()
})
end
end
end, function(ply)
return ply:HasPermission("see_admin_chat")
end)
hook.Add("PlayerDisconnected", "SAM.Reports", function(ply)
if ply.sam_has_report then
remove_report_info(ply)
end
end)
local msgs = {
"Hey there I need some help",
"TP TO ME NOW",
"I JUST GOT RDM'D"
}
concommand.Add("sam_test_reports", function(ply)
if IsValid(ply) and not ply:IsSuperAdmin() then return end
local bots = player.GetBots()
if #bots < 2 then
for i = 1, 2 - #bots do
RunConsoleCommand("bot")
end
end
timer.Simple(1, function()
for k, v in ipairs(player.GetBots()) do
timer.Create("SAM.TestReports" .. k, k, 3, function()
if not IsValid(v) then return end
v:sam_set_rank("user")
v:Say("!asay srlion " .. table.Random(msgs))
end)
end
end)
end)

29
lua/sam/sh_colors.lua Normal file
View File

@@ -0,0 +1,29 @@
--[[
| 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 SAM_LOADED then return end
local colors = {
Red = Color(244, 67, 54),
Blue = Color(13, 130, 223),
Green = Color(0, 230, 64),
White = Color(236, 240, 241),
Black = Color(10, 10, 10)
}
function sam.get_color(name)
return colors[name]
end
function sam.add_color(name, color)
if isstring(name) and IsColor(color) then
colors[name] = color
end
end

184
lua/sam/sh_lang.lua Normal file
View File

@@ -0,0 +1,184 @@
--[[
| 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 SAM_LOADED then return end
local lang = sam.load_file("sam_language.lua", "sh")
local original = lang
if not isstring(lang) then
lang = "english"
end
local lang_path = "sam_languages/" .. lang .. ".lua"
if not file.Exists(lang_path, "LUA") then
lang_path = "sam_languages/english.lua"
if not file.Exists(lang_path, "LUA") then
-- maybe they deleted english lang????
sam.print("SAM is broken!")
sam.print("Language '" .. tostring(original) .. "' doesn't exist and 'english' language file doesn't exist")
return false
else
sam.print("Language '" .. tostring(original) .. "' doesn't exist falling back to english")
end
end
local Language = sam.load_file(lang_path, "sh_")
local sub, find = string.sub, string.find
local white_color = Color(236, 240, 241)
do
local args = {}
function sam.add_message_argument(arg, func)
if isstring(arg) and isfunction(func) then
args[arg] = func
end
end
local insert = function(t, v)
t.__cnt = t.__cnt + 1
t[t.__cnt] = v
end
function sam.format_message(msg, tbl, result, result_n)
msg = Language[msg] or msg
result = result or {}
result.__cnt = result_n or 0
local pos = 0
local start, _end, arg, arg2 = nil, 0, nil, nil
while true do
start, _end, arg, arg2 = find(msg, "%{ *([%w_%#]+)([^%{}]-) *%}", _end)
if not start then break end
if pos ~= start then
local txt = sub(msg, pos, start - 1)
if txt ~= "" then
insert(result, white_color)
insert(result, txt)
end
end
local ma = args[sub(arg, 1, 1)]
if not ma then
insert(result, "{" .. arg .. " " .. arg2 .. "}")
else
ma(result, tbl and tbl[arg], arg, unpack(arg2:Trim():Split(" ")))
end
pos = _end + 1
end
if pos <= #msg then
insert(result, white_color)
insert(result, sub(msg, pos))
end
return result
end
/*
Admin
*/
sam.add_message_argument("A", function(result, admin)
if sam.isconsole(admin) then
-- we need to show that it's the real console!!!!!
insert(result, Color(236, 240, 241))
insert(result, "*")
insert(result, Color(13, 130, 223))
insert(result, "Console")
else
if sam.type(admin) == "Player" then
if CLIENT and LocalPlayer() == admin then
insert(result, Color(255, 215, 0))
insert(result, sam.language.get("You"))
else
insert(result, Color(13, 130, 223))
insert(result, admin:Name())
end
else
insert(result, Color(13, 130, 223))
insert(result, admin)
end
end
end)
/*
Target(s)
*/
sam.add_message_argument("T", function(result, targets)
for k, v in ipairs(sam.get_targets_list(targets)) do
insert(result, v)
end
end)
/*
Value(s)
*/
sam.add_message_argument("V", function(result, value)
insert(result, Color(0, 230, 64))
insert(result, tostring(value))
end)
/*
Text(s)
*/
sam.add_message_argument("S", function(result, text, _, color)
insert(result, sam.get_color(color) or white_color)
insert(result, tostring(text))
end)
-- https://gist.github.com/fernandohenriques/12661bf250c8c2d8047188222cab7e28
local hex_rgb = function(hex)
local r, g, b
if #hex == 4 then
r, g, b = tonumber(hex:sub(2, 2), 16) * 17, tonumber(hex:sub(3, 3), 16) * 17, tonumber(hex:sub(4, 4), 16) * 17
else
r, g, b = tonumber(hex:sub(2, 3), 16), tonumber(hex:sub(4, 5), 16), tonumber(hex:sub(6, 7), 16)
end
if not r or not g or not b then
return color_white
end
return Color(r, g, b)
end
/*
Colored Text(s)
*/
sam.add_message_argument("#", function(result, _, color, ...)
local text = table.concat({...}, " ")
insert(result, hex_rgb(color))
insert(result, text)
end)
end
function sam.get_message(msg)
msg = Language[msg]
if not msg then
return false
else
return {Color(236, 240, 241), msg}
end
end
function sam.language.get(key)
return Language[key]
end
function sam.language.Add(key, value)
Language[key] = value
end

80
lua/sam/sh_motd.lua Normal file
View File

@@ -0,0 +1,80 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
local config = sam.config
local command = sam.command
if CLIENT then
config.add_menu_setting("MOTD URL (Leave empty for no MOTD)", function()
local entry = vgui.Create("SAM.TextEntry")
entry:SetPlaceholder("")
entry:SetNoBar(true)
entry:SetConfig("MOTDURL", "")
return entry
end)
end
local motd
local load_motd = function()
local url = config.get("MOTDURL", "")
if url == "" then
command.remove_command("motd")
hook.Remove("HUDPaint", "SAM.OpenMOTD")
return
end
if IsValid(motd) then
motd:Remove()
end
command.set_category("Menus")
command.new("motd")
:Help("Open MOTD menu")
:OnExecute(function(ply)
sam.netstream.Start(ply, "OpenMOTD")
end)
:End()
if CLIENT then
function sam.menu.open_motd()
if IsValid(motd) then
motd:Remove()
end
motd = vgui.Create("SAM.Frame")
motd:Dock(FILL)
motd:DockMargin(40, 40, 40, 40)
motd:MakePopup()
function motd.close.DoClick()
motd:Remove()
end
local html = motd:Add("DHTML")
html:Dock(FILL)
html:OpenURL(url)
end
sam.netstream.Hook("OpenMOTD", function()
sam.menu.open_motd()
end)
hook.Add("HUDPaint", "SAM.OpenMOTD", function()
sam.menu.open_motd()
hook.Remove("HUDPaint", "SAM.OpenMOTD")
end)
end
end
config.hook({"MOTDURL"}, load_motd)

View File

@@ -0,0 +1,98 @@
--[[
| 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 SAM_LOADED then return end
local permissions = {}
local give_permission
if SERVER then
local permissions_to_add = {}
give_permission = function(name, permission)
if sam.ranks.ranks_loaded() then
local rank = sam.ranks.get_rank(name)
if rank and rank.data.permissions[permission] == nil then
sam.ranks.give_permission(name, permission)
end
else
table.insert(permissions_to_add, {name, permission})
end
end
hook.Add("SAM.LoadedRanks", "SAM.Command.GivePermissions", function()
for k, v in ipairs(permissions_to_add) do
give_permission(v[1], v[2])
end
end)
end
local get_next_Other = function()
for i, v in ipairs(permissions) do
if v.category == "Other" then
return i
end
end
return #permissions + 1
end
function sam.permissions.add(permission, category, rank)
if not sam.isstring(category) then
category = "Other"
end
local permission_data = {
name = permission,
category = category,
rank = rank,
value = value
}
local index = sam.permissions.get_index(permission)
if not index then
if category ~= "Other" then
table.insert(permissions, get_next_Other(), permission_data)
else
table.insert(permissions, permission_data)
end
hook.Call("SAM.AddedPermission", nil, permission, category, rank, value)
else
permissions[index] = permission_data
hook.Call("SAM.PermissionModified", nil, permission, category, rank, value)
end
if SERVER and rank then
give_permission(rank, permission)
end
end
function sam.permissions.get_index(permission)
for i, v in ipairs(permissions) do
if v.name == permission then
return i
end
end
end
function sam.permissions.remove(permission)
local index = sam.permissions.get_index(permission)
if index then
table.remove(permissions, index)
hook.Call("SAM.RemovedPermission", nil, permission)
end
end
function sam.permissions.exists(permission)
return sam.permissions.get_index(permission) and true or false
end
function sam.permissions.get()
return permissions
end

251
lua/sam/sh_restrictions.lua Normal file
View File

@@ -0,0 +1,251 @@
--[[
| 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 SAM_LOADED then return end
local loaded = false
local load_restrictions = function()
local sam = sam
local config = sam.config
local hook = hook
local SERVER = SERVER
if CLIENT then
local add_setting = function(body, title, key)
local setting = body:Add("SAM.LabelPanel")
setting:Dock(TOP)
setting:SetLabel(title)
local enable = setting:Add("SAM.ToggleButton")
enable:SetConfig(key, true)
return setting
end
config.add_menu_setting("Restrictions", function(body)
local setting = body:Add("SAM.LabelPanel")
setting:Dock(TOP)
setting:DockMargin(8, 6, 8, 0)
setting:SetLabel("Restrictions (Check these settings in ranks' permissions)")
local setting_body = body:Add("Panel")
setting_body:Dock(TOP)
setting_body:DockMargin(8, 6, 8, 0)
setting_body:DockPadding(8, 0, 8, 0)
add_setting(setting_body, "Tool (Eg. using button tool)", "Restrictions.Tool")
add_setting(setting_body, "Spawning (Eg. spawning props)", "Restrictions.Spawning")
add_setting(setting_body, "Limits (Eg. how many props can you spawn)", "Restrictions.Limits")
function setting_body:PerformLayout()
setting_body:SizeToChildren(false, true)
end
end)
end
local tools = weapons.GetStored("gmod_tool")
if sam.istable(tools) then
if config.get("Restrictions.Tool", true) then
for k, v in pairs(tools.Tool) do
sam.permissions.add(v.Mode, "Tools - " .. (v.Category or "Other"), "user")
end
hook.Add("CanTool", "SAM.Module.Restrictions", function(ply, _, tool)
if not ply:HasPermission(tool) then
if CLIENT and sam.player.check_cooldown(ply, "ToolNoPermission", 0.1) then
ply:sam_send_message("You don't have permission to use this tool.")
end
return false
end
end)
else
for k, v in pairs(tools.Tool) do
sam.permissions.remove(v.Mode)
end
hook.Remove("CanTool", "SAM.Module.Restrictions")
end
end
sam.permissions.add("admin_weapons", "Spawning", "superadmin")
local function no_permission(ply, name)
ply:sam_play_sound("buttons/button10.wav")
ply:sam_send_message("You don't have permission to spawn {S Blue}.", {
S = name
})
end
local spawning = {
PlayerSpawnProp = {
name = "props",
permission = "user",
call_gm = true,
},
PlayerGiveSWEP = {
name = "give_weapons",
cb = function(_, ply, _, wep)
if wep.sam_AdminOnly and not ply:HasPermission("admin_weapons") then
no_permission(ply, "admin weapons")
return false
end
return true
end,
hook = sam.hook_first,
},
PlayerSpawnSWEP = {
name = "spawn_weapons",
cb = function(_, ply, _, wep)
if wep.sam_AdminOnly and not ply:HasPermission("admin_weapons") then
no_permission(ply, "admin weapons")
return false
end
return true
end,
hook = sam.hook_first,
},
-- PlayerSpawnSENT = {
-- name = "entities",
-- check_limit = "sents"
-- },
PlayerSpawnNPC = {
name = "npcs",
check_limit = "npcs",
},
PlayerSpawnVehicle = {
name = "vehicles",
check_limit = "vehicles",
},
PlayerSpawnRagdoll = {
name = "ragdolls",
permission = "user",
}
}
local override_lists = {
"Weapon",
-- "SpawnableEntities"
}
local function LimitReachedProcess(ply, str)
if not IsValid(ply) then return true end
return ply:CheckLimit(str)
end
local GAMEMODE = GAMEMODE
if config.get("Restrictions.Spawning", true) then
for k, v in pairs(spawning) do
local name = v
local permission = "superadmin"
local check
local check_limit
local hook = sam.hook_last
if istable(v) then
name = v.name
permission = v.permission or permission
if v.call_gm then
check = GAMEMODE[k]
elseif v.cb then
check = v.cb
end
hook = v.hook or hook
check_limit = v.check_limit
end
sam.permissions.add(name, "Spawning", permission)
if SERVER then
hook(k, "SAM.Spawning." .. k .. name, function(ply, ...)
if not ply:HasPermission(name) then
no_permission(ply, name)
return false
end
if check_limit then
return LimitReachedProcess(ply, check_limit)
end
if check then
return check(GAMEMODE, ply, ...)
end
return true
end)
end
end
for i = 1, #override_lists do
for k, v in pairs(list.GetForEdit(override_lists[i])) do
v.sam_AdminOnly = v.sam_AdminOnly or v.AdminOnly
v.AdminOnly = false
end
end
else
sam.permissions.add("admin_weapons")
for k, v in pairs(spawning) do
sam.permissions.remove(istable(v) and v.name or v)
if SERVER then
hook.Remove(k, "SAM.Spawning." .. k)
end
end
for i = 1, #override_lists do
for k, v in pairs(list.GetForEdit(override_lists[i])) do
if v.sam_AdminOnly then
v.AdminOnly = v.sam_AdminOnly
end
end
end
end
local PLAYER = FindMetaTable("Player")
if config.get("Restrictions.Limits", true) then
local get_limit = sam.ranks.get_limit
function PLAYER:GetLimit(limit_type)
return get_limit(self:sam_getrank(), limit_type)
end
sam.hook_first("PlayerCheckLimit", "SAM.PlayerCheckLimit", function(ply, limit_type, count)
local ply_limit = ply:GetLimit(limit_type)
if ply_limit < 0 then return true end
if count > ply_limit - 1 then
return false
end
return true
end)
sam.limit_types = {}
for _, limit_type in SortedPairs(cleanup.GetTable(), true) do
local cvar = GetConVar("sbox_max" .. limit_type)
if cvar then
table.insert(sam.limit_types, limit_type)
end
end
else
sam.limit_types = nil
PLAYER.GetLimit = nil
hook.Remove("PlayerCheckLimit", "SAM.PlayerCheckLimit")
end
if not loaded then
loaded = true
hook.Call("SAM.LoadedRestrictions")
end
end
timer.Simple(5, function()
if GAMEMODE.IsSandboxDerived then
sam.config.hook({"Restrictions.Tool", "Restrictions.Spawning", "Restrictions.Limits"}, load_restrictions)
end
end)

350
lua/sam/sh_util.lua Normal file
View File

@@ -0,0 +1,350 @@
--[[
| 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 SAM_LOADED then return end
local sam = sam
function sam.parse_args(str)
local args = {}
local tmp, in_quotes = "", false
for i = 1, #str do
local char = str:sub(i, i)
if char == "\"" then
-- i could use string.find to find the next double quotes but thats gonna be overkill
in_quotes = not in_quotes
if tmp ~= "" or not in_quotes then
args[#args + 1], tmp = tmp, ""
end
elseif char ~= " " or in_quotes then
tmp = tmp .. char
elseif tmp ~= "" then
args[#args + 1], tmp = tmp, ""
end
end
if tmp ~= "" then
args[#args + 1] = tmp
end
return args, #args
end
function sam.get_targets_list(targets)
if sam.isstring(targets) then
return {Color(244, 67, 54), targets}
end
local len = #targets
if len == player.GetCount() and len > 1 then
return {Color(244, 67, 54), sam.language.get("Everyone")}
end
local admin = targets.admin
local result = {}
local white = Color(236, 240, 241)
for i = 1, len do
local target = targets[i]
if CLIENT and LocalPlayer() == target then
table.insert(result, Color(255, 215, 0))
if admin ~= LocalPlayer() then
table.insert(result, sam.language.get("You"))
else
table.insert(result, sam.language.get("Yourself"))
end
elseif admin ~= target then
local name
if sam.isentity(target) and target.Name then
name = target:Name()
else
name = "Unknown"
table.insert(result, white)
table.insert(result, "*")
end
table.insert(result, Color(244, 67, 54))
table.insert(result, name)
else
table.insert(result, Color(255, 215, 0))
table.insert(result, sam.language.get("Themself"))
end
if i ~= len then
table.insert(result, white)
table.insert(result, ",")
end
end
return result
end
function sam.is_steamid(id, err) -- https://stackoverflow.com/questions/6724268/check-if-input-matches-steam-id-format
if sam.isstring(id) and id:match("^STEAM_[0-5]:[0-1]:[0-9]+$") ~= nil then
return true
else
return err and error("invalid steamid", 2) or false
end
end
function sam.is_steamid64(id, err)
if sam.isstring(id)
and tonumber(id)
and id:sub(1, 7) == "7656119"
and (#id == 17 or #id == 18) then
return true
else
return err and error("invalid steamid64", 2) or false
end
end
do
local console = {}
do
local return_console = function()
return "Console"
end
for _, v in ipairs({"SteamID", "SteamID64", "Name", "Nick", "Name"}) do
console[v] = return_console
end
setmetatable(console, {
__tostring = return_console,
MetaName = "console"
})
end
function console.IsAdmin()
return true
end
function console.IsSuperAdmin()
return true
end
function console:IsUserGroup(name)
return name == "superadmin"
end
function console.GetUserGroup()
return "superadmin"
end
function console.sam_getrank()
return "superadmin"
end
function console.HasPermission()
return true
end
function console.CanTarget()
return true
end
function console.CanTargetRank()
return true
end
function console.GetBanLimit()
return 0
end
function console.SetUserGroup()
end
function sam.isconsole(v)
return v == console
end
sam.console = console
end
do
local times = {
"year"; 525600,
"month"; 43800,
"week"; 10080,
"day"; 1440,
"hour"; 60,
"minute"; 1
}
for i = 1, #times, 2 do
times[i] = " " .. times[i]
end
local floor = math.floor
function sam.format_length(mins) -- Thanks to this guide https://stackoverflow.com/a/21323783
if mins <= 0 then
return "Indefinitely"
elseif mins <= 1 then
return "1 minute"
end
local str = ""
for i = 1, #times, 2 do
local n1, n2 = times[i + 1]
n2, mins = floor(mins / n1), mins % n1
if n2 > 0 then
if str ~= "" then
if mins == 0 then
str = str .. " and "
else
str = str .. ", "
end
end
str = str .. n2 .. times[i]
if n2 > 1 then
str = str .. "s"
end
end
if mins == 0 then
break
end
end
return str
end
end
do
local times = {
m = 1,
h = 60,
d = 1440,
w = 10080,
mo = 43800,
y = 525600
}
function sam.parse_length(length)
local time, found = tonumber(length), false
if sam.isnumber(length) then
time, found = length, true
elseif time then
found = true
else
time = 0
for t, u in length:gmatch("(%d+)(%a+)") do
u = times[u]
if u then
time = time + (u * t)
found = true
end
end
end
if not found then return false end
return math.Clamp(time, 0, 31536000)
end
local times2 = {}
for k, v in SortedPairsByValue(times, true) do
table.insert(times2, k)
table.insert(times2, v)
end
local floor = math.floor
function sam.reverse_parse_length(mins) -- Thanks to this guide https://stackoverflow.com/a/21323783
if mins <= 0 then
return "0"
elseif mins <= 1 then
return "1m"
end
local str = ""
for i = 1, #times2, 2 do
local n1, n2 = times2[i + 1]
n2, mins = floor(mins / n1), mins % n1
if n2 > 0 then
if str ~= "" then
str = str .. " "
end
str = str .. n2 .. times2[i]
end
if mins == 0 then
break
end
end
return str
end
end
do
if SERVER then
function sam.hook_call(event, ...)
hook.Call(event, nil, ...)
sam.netstream.Start(nil, "HookCall", event, ...)
end
function sam.client_hook_call(event, ...)
sam.netstream.Start(nil, "HookCall", event, ...)
end
else
local function hook_call(event, ...)
hook.Call(event, nil, ...)
end
sam.netstream.Hook("HookCall", hook_call)
end
end
if SERVER then
local maps = {}
for k, v in ipairs(file.Find("maps/*.bsp", "GAME")) do
maps[k] = v:sub(1, -5):lower()
end
sam.set_global("Maps", maps)
end
function sam.is_valid_map(name)
local maps = sam.get_global("Maps")
if name:sub(-4) == ".bsp" then
name = name:sub(1, -5)
end
name = name:lower()
for i = 1, #maps do
if maps[i] == name then
return name
end
end
return false
end
function sam.is_valid_gamemode(name)
name = name:lower()
local gamemodes = engine.GetGamemodes()
for i = 1, #gamemodes do
local gamemode = gamemodes[i]
if sam.isstring(gamemode.name) and gamemode.name:lower() == name then
return true
end
end
return false
end
function sam.hook_first(event, name, func)
if HOOK_HIGH then
return hook.Add(event, name, func, HOOK_HIGH)
end
return hook.Add(event, name, func)
end
function sam.hook_last(event, name, func)
if HOOK_LOW then
return hook.Add(event, name, func, HOOK_LOW)
end
return hook.Add(event, name, func)
end

425
lua/sam/sv_sql.lua Normal file
View File

@@ -0,0 +1,425 @@
--[[
| 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 SAM_LOADED then return end
local SQL = sam.SQL
SQL.SetAddonName("SAM")
if file.Exists("sam_sql_config.lua", "LUA") then
local config = sam.load_file("sam_sql_config.lua", "sv_")
if sam.istable(config) then
SQL.SetConfig(config)
end
end
local current_version = 0
local versions = {}
local add_version = function(version, fn)
table.insert(versions, {version, fn})
end
local update = function()
for _, v in ipairs(versions) do
local version = v[1]
if version > current_version then
v[2]()
end
end
end
local check_updates = function()
-- SQL.Query([[DROP TABLE IF EXISTS `sam_players`]])
-- SQL.Query([[DROP TABLE IF EXISTS `sam_bans`]])
-- SQL.Query([[DROP TABLE IF EXISTS `sam_ranks`]])
-- SQL.Query([[DROP TABLE IF EXISTS `sam_version`]])
-- SQL.Query([[DROP TABLE IF EXISTS `sam_config`]])
SQL.TableExists("sam_version", function(exists)
if exists then
SQL.Query([[
SELECT
`version`
FROM
`sam_version`
]], function(version_data)
current_version = tonumber(version_data.version)
if sam.version > current_version then
update()
SQL.FQuery([[
UPDATE
`sam_version`
SET
`version` = {1}
]], {sam.version}):wait()
end
hook.Call("SAM.DatabaseLoaded")
end, true):wait()
else
update()
SQL.Query([[
CREATE TABLE `sam_version`(
`version` SMALLINT(255)
)
]])
SQL.FQuery([[
INSERT INTO
`sam_version`(`version`)
VALUES
({1})
]], {sam.version}):wait()
hook.Call("SAM.DatabaseLoaded")
end
end):wait()
end
add_version(100, function()
local auto_increment = SQL.IsMySQL() and "AUTO_INCREMENT" or ""
SQL.FQuery([[
CREATE TABLE `sam_players`(
`id` INT PRIMARY KEY {1f},
`steamid` VARCHAR(32),
`name` VARCHAR(255),
`rank` VARCHAR(30),
`expiry_date` INT UNSIGNED,
`first_join` INT UNSIGNED,
`last_join` INT UNSIGNED,
`play_time` MEDIUMINT UNSIGNED
)
]], {auto_increment})
SQL.FQuery([[
CREATE TABLE `sam_bans`(
`id` INT PRIMARY KEY {1f},
`steamid` VARCHAR(32),
`name` VARCHAR(255),
`reason` VARCHAR(255),
`admin` VARCHAR(32),
`unban_date` INT UNSIGNED
)
]], {auto_increment})
SQL.Query([[
CREATE TABLE `sam_ranks`(
`name` VARCHAR(30),
`immunity` TINYINT UNSIGNED,
`ban_limit` INT UNSIGNED,
`data` TEXT
)
]])
end)
add_version(110, function()
SQL.Query([[
ALTER TABLE `sam_ranks`
ADD `inherit` VARCHAR(30)
]])
SQL.Query([[
UPDATE
`sam_ranks`
SET
`inherit` = 'user'
WHERE
`name` != 'user'
]])
if not SQL.IsMySQL() then
SQL.Query([[
ALTER TABLE
`sam_players` RENAME TO `tmp_sam_players`
]])
SQL.Query([[
CREATE TABLE `sam_players`(
`id` INT PRIMARY KEY,
`steamid` VARCHAR(32),
`name` VARCHAR(255),
`rank` VARCHAR(30),
`expiry_date` INT UNSIGNED,
`first_join` INT UNSIGNED,
`last_join` INT UNSIGNED,
`play_time` INT UNSIGNED
)
]])
SQL.Query([[
INSERT INTO
`sam_players`(
`id`,
`steamid`,
`name`,
`rank`,
`expiry_date`,
`first_join`,
`last_join`,
`play_time`
)
SELECT
`id`,
`steamid`,
`name`,
`rank`,
`expiry_date`,
`first_join`,
`last_join`,
`play_time` * 60
FROM
`tmp_sam_players`
]])
SQL.Query([[DROP TABLE `tmp_sam_players`]])
else
SQL.Query([[
ALTER TABLE
`sam_players`
MODIFY
`play_time` INT UNSIGNED
]])
SQL.Query([[
UPDATE `sam_players`
SET `play_time` = `play_time` * 60
]])
end
end)
add_version(112, function()
if not SQL.IsMySQL() then
SQL.Query([[
ALTER TABLE
`sam_players` RENAME TO `tmp_sam_players`
]])
SQL.Query([[
ALTER TABLE
`sam_bans` RENAME TO `tmp_sam_bans`
]])
SQL.Query([[
CREATE TABLE `sam_players`(
`id` INTEGER PRIMARY KEY,
`steamid` VARCHAR(32),
`name` VARCHAR(255),
`rank` VARCHAR(30),
`expiry_date` INT UNSIGNED,
`first_join` INT UNSIGNED,
`last_join` INT UNSIGNED,
`play_time` INT UNSIGNED
)
]])
SQL.Query([[
CREATE TABLE `sam_bans`(
`id` INTEGER PRIMARY KEY,
`steamid` VARCHAR(32),
`name` VARCHAR(255),
`reason` VARCHAR(255),
`admin` VARCHAR(32),
`unban_date` INT UNSIGNED
)
]])
SQL.Query([[
INSERT INTO
`sam_players`(
`id`,
`steamid`,
`name`,
`rank`,
`expiry_date`,
`first_join`,
`last_join`,
`play_time`
)
SELECT
*
FROM
`tmp_sam_players`
]])
SQL.Query([[
INSERT INTO
`sam_bans`(
`id`,
`steamid`,
`name`,
`reason`,
`admin`,
`unban_date`
)
SELECT
*
FROM
`tmp_sam_bans`
]])
SQL.Query([[DROP TABLE `tmp_sam_players`]])
SQL.Query([[DROP TABLE `tmp_sam_bans`]])
end
end)
add_version(114, function()
SQL.Query([[
SELECT
`name`,
`data`
FROM
`sam_ranks`
]], function(ranks)
for k, v in ipairs(ranks) do
local name, permissions = v.name, v.data
SQL.FQuery([[
UPDATE
`sam_ranks`
SET
`data` = {1}
WHERE
`name` == {2}
]], {sam.pon.encode({permissions = sam.pon.decode(permissions), limits = {}}), name})
end
end):wait()
end)
add_version(120, function()
if SQL.IsMySQL() then
SQL.Query([[ALTER TABLE `sam_bans` ADD UNIQUE (`steamid`)]])
SQL.Query([[ALTER TABLE `sam_bans` DROP `name`]])
else
SQL.Query([[
ALTER TABLE
`sam_bans` RENAME TO `tmp_sam_bans`
]])
SQL.Query([[
CREATE TABLE `sam_bans`(
`id` INTEGER PRIMARY KEY,
`steamid` VARCHAR(32) UNIQUE,
`reason` VARCHAR(255),
`admin` VARCHAR(32),
`unban_date` INT UNSIGNED
)
]])
SQL.Query([[
INSERT INTO
`sam_bans`(
`id`,
`steamid`,
`reason`,
`admin`,
`unban_date`
)
SELECT
`id`,
`steamid`,
`reason`,
`admin`,
`unban_date`
FROM
`tmp_sam_bans`
]])
SQL.Query([[DROP TABLE `tmp_sam_bans`]])
end
SQL.Query([[
SELECT
`name`,
`data`
FROM
`sam_ranks`
]], function(ranks)
for k, v in ipairs(ranks) do
SQL.FQuery([[
UPDATE
`sam_ranks`
SET
`data` = {1}
WHERE
`name` = {2}
]], {util.TableToJSON(sam.pon.decode(v.data)), v.name})
end
end
):wait()
end)
add_version(135, function()
if not SQL.IsMySQL() then return end
SQL.Query([[ALTER TABLE `sam_ranks` MODIFY `name` BLOB;]])
end)
add_version(138, function()
SQL.Query([[
CREATE TABLE `sam_config`(
`key` VARCHAR(40) UNIQUE,
`value` TEXT
)
]])
end)
add_version(141, function()
if not SQL.IsMySQL() then return end
SQL.Query([[ALTER TABLE `sam_config` MODIFY `value` BLOB;]])
end)
add_version(143, function()
SQL.Query([[DROP TABLE IF EXISTS `sam_config`]])
SQL.Query([[
CREATE TABLE `sam_config`(
`key` VARCHAR(40) UNIQUE,
`value` BLOB
)
]])
end)
add_version(147, function()
SQL.Query([[CREATE UNIQUE INDEX sam_players_steamid_index ON `sam_players` (`steamid`);]])
SQL.Query([[CREATE INDEX sam_bans_steamid_index ON `sam_bans` (`steamid`);]])
end)
hook.Add("SAM.DatabaseConnected", "CheckUpdates", check_updates)
hook.Add("SAM.DatabaseLoaded", "SAM.DatabaseLoaded", function()
sam.DatabaseLoaded = true
sam.print("Connected to database and loaded data successfully.")
end)
SQL.Connect()
timer.Create("SAM.CheckForUpdates", 60 * 20, 0, function()
http.Fetch("https://raw.githubusercontent.com/Srlion/SAM-Docs/master/version.txt", function(version, _, _, code)
if code ~= 200 then return end
version = version:gsub("%s", "")
if sam.version >= (tonumber(version) or 0) then return end
sam.print(Color(255, 0, 0), "New update is available for SAM to download")
local superadmins = {}
for k, v in ipairs(player.GetAll()) do
if v:IsSuperAdmin() then
table.insert(superadmins, v)
end
end
sam.player.add_text(superadmins, Color(255, 0, 0), "New update is available for SAM to download")
end)
end)