mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 21:53:46 +03:00
Upload
This commit is contained in:
480
lua/sam/reports/cl_reports.lua
Normal file
480
lua/sam/reports/cl_reports.lua
Normal 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
|
||||
202
lua/sam/reports/sv_reports.lua
Normal file
202
lua/sam/reports/sv_reports.lua
Normal 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)
|
||||
Reference in New Issue
Block a user