433 lines
14 KiB
Lua
433 lines
14 KiB
Lua
--[[---------------------------------------------------------------------------
|
|
Utility functions
|
|
---------------------------------------------------------------------------]]
|
|
|
|
local vector = FindMetaTable("Vector")
|
|
local meta = FindMetaTable("Player")
|
|
|
|
--[[---------------------------------------------------------------------------
|
|
Decides whether the vector could be seen by the player if they were to look at it
|
|
---------------------------------------------------------------------------]]
|
|
function vector:isInSight(filter, ply)
|
|
ply = ply or LocalPlayer()
|
|
local trace = {}
|
|
trace.start = ply:EyePos()
|
|
trace.endpos = self
|
|
trace.filter = filter
|
|
trace.mask = -1
|
|
local TheTrace = util.TraceLine(trace)
|
|
|
|
return not TheTrace.Hit, TheTrace.HitPos
|
|
end
|
|
|
|
--[[---------------------------------------------------------------------------
|
|
Turn a money amount into a pretty string
|
|
---------------------------------------------------------------------------]]
|
|
local function attachCurrency(str)
|
|
local config = GAMEMODE.Config
|
|
return config.currencyLeft and config.currency .. str or str .. config.currency
|
|
end
|
|
|
|
function DarkRP.formatMoney(n)
|
|
if not n then return attachCurrency("0") end
|
|
|
|
if n >= 1e14 then return attachCurrency(tostring(n)) end
|
|
if n <= -1e14 then return "-" .. attachCurrency(tostring(math.abs(n))) end
|
|
|
|
local config = GAMEMODE.Config
|
|
|
|
local negative = n < 0
|
|
|
|
n = tostring(math.abs(n))
|
|
|
|
local dp = string.find(n, ".", 1, true) or #n + 1
|
|
|
|
for i = dp - 4, 1, -3 do
|
|
n = n:sub(1, i) .. config.currencyThousandSeparator .. n:sub(i + 1)
|
|
end
|
|
|
|
-- Make sure the amount is padded with zeroes
|
|
if n[#n - 1] == "." then
|
|
n = n .. "0"
|
|
end
|
|
|
|
return (negative and "-" or "") .. attachCurrency(n)
|
|
end
|
|
|
|
--[[---------------------------------------------------------------------------
|
|
Find a player based on given information
|
|
|
|
Note that there is a searching priority:
|
|
* UserID
|
|
* SteamID64
|
|
* SteamID
|
|
* Nick
|
|
* SteamName
|
|
|
|
Note also that there are _separate_ loops. This is to make sure the function
|
|
gives the same result, regardless of the order in which players are iterated
|
|
over.
|
|
---------------------------------------------------------------------------]]
|
|
function DarkRP.findPlayer(info)
|
|
if not info or info == "" then return nil end
|
|
local pls = player.GetAll()
|
|
|
|
local count = #pls
|
|
local numberInfo = tonumber(info)
|
|
|
|
-- First check if the input matches a player by UserID or SteamID64. This is
|
|
-- only necessary if the input can be parsed as a number.
|
|
if numberInfo then
|
|
for k = 1, count do
|
|
local v = pls[k]
|
|
|
|
if numberInfo == v:UserID() then
|
|
return v
|
|
end
|
|
end
|
|
|
|
for k = 1, count do
|
|
local v = pls[k]
|
|
|
|
if info == v:SteamID64() then
|
|
return v
|
|
end
|
|
end
|
|
end
|
|
|
|
local lowerInfo = string.lower(tostring(info))
|
|
if string.StartsWith(lowerInfo, "steam_") then
|
|
for k = 1, count do
|
|
local v = pls[k]
|
|
|
|
if info == v:SteamID() then
|
|
return v
|
|
end
|
|
end
|
|
end
|
|
|
|
for k = 1, count do
|
|
local v = pls[k]
|
|
|
|
if string.find(string.lower(v:Nick()), lowerInfo, 1, true) ~= nil then
|
|
return v
|
|
end
|
|
end
|
|
|
|
for k = 1, count do
|
|
local v = pls[k]
|
|
|
|
if string.find(string.lower(v:SteamName()), lowerInfo, 1, true) ~= nil then
|
|
return v
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
--[[---------------------------------------------------------------------------
|
|
Find multiple players based on a string criterium
|
|
Taken from FAdmin]]
|
|
---------------------------------------------------------------------------*/
|
|
function DarkRP.findPlayers(info)
|
|
if not info then return nil end
|
|
local pls = player.GetAll()
|
|
local found = {}
|
|
local players
|
|
|
|
if string.lower(info) == "*" or string.lower(info) == "<all>" then return pls end
|
|
|
|
local InfoPlayers = {}
|
|
for A in string.gmatch(info .. ";", "([a-zA-Z0-9:_.]*)[;(,%s)%c]") do
|
|
if A ~= "" then
|
|
table.insert(InfoPlayers, A)
|
|
end
|
|
end
|
|
|
|
for _, PlayerInfo in ipairs(InfoPlayers) do
|
|
-- Playerinfo is always to be treated as UserID when it's a number
|
|
-- otherwise people with numbers in their names could get confused with UserID's of other players
|
|
if tonumber(PlayerInfo) then
|
|
local foundPlayer = Player(PlayerInfo)
|
|
if IsValid(foundPlayer) and not found[foundPlayer] then
|
|
found[foundPlayer] = true
|
|
players = players or {}
|
|
table.insert(players, foundPlayer)
|
|
end
|
|
continue
|
|
end
|
|
|
|
local stringPlayerInfo = string.lower(PlayerInfo)
|
|
for _, v in ipairs(pls) do
|
|
-- Prevent duplicates
|
|
if found[v] then continue end
|
|
local steamId = v:SteamID()
|
|
|
|
-- Find by Steam ID
|
|
if (PlayerInfo == steamId or steamId == "UNKNOWN") or
|
|
-- Find by Partial Nick
|
|
string.find(string.lower(v:Nick()), stringPlayerInfo, 1, true) ~= nil or
|
|
-- Find by steam name
|
|
(v.SteamName and string.find(string.lower(v:SteamName()), stringPlayerInfo, 1, true) ~= nil) then
|
|
found[v] = true
|
|
players = players or {}
|
|
table.insert(players, v)
|
|
end
|
|
end
|
|
end
|
|
|
|
return players
|
|
end
|
|
|
|
function meta:getEyeSightHitEntity(searchDistance, hitDistance, filter)
|
|
searchDistance = searchDistance or 100
|
|
hitDistance = (hitDistance or 15) * (hitDistance or 15)
|
|
|
|
filter = filter or function(p) return p:IsPlayer() and p ~= self end
|
|
|
|
self:LagCompensation(true)
|
|
|
|
local shootPos = self:GetShootPos()
|
|
local entities = ents.FindInSphere(shootPos, searchDistance)
|
|
local aimvec = self:GetAimVector()
|
|
|
|
local smallestDistance = math.huge
|
|
local foundEnt
|
|
|
|
for _, ent in ipairs(entities) do
|
|
if not IsValid(ent) or filter(ent) == false then continue end
|
|
|
|
local center = ent:GetPos()
|
|
|
|
-- project the center vector on the aim vector
|
|
local projected = shootPos + (center - shootPos):Dot(aimvec) * aimvec
|
|
|
|
if aimvec:Dot((projected - shootPos):GetNormalized()) < 0 then continue end
|
|
|
|
-- the point on the model that has the smallest distance to your line of sight
|
|
local nearestPoint = ent:NearestPoint(projected)
|
|
local distance = nearestPoint:DistToSqr(projected)
|
|
|
|
if distance < smallestDistance then
|
|
local trace = {
|
|
start = self:GetShootPos(),
|
|
endpos = nearestPoint,
|
|
filter = {self, ent}
|
|
}
|
|
local traceLine = util.TraceLine(trace)
|
|
if traceLine.Hit then continue end
|
|
|
|
smallestDistance = distance
|
|
foundEnt = ent
|
|
end
|
|
end
|
|
|
|
self:LagCompensation(false)
|
|
|
|
if smallestDistance < hitDistance then
|
|
return foundEnt, math.sqrt(smallestDistance)
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
--[[---------------------------------------------------------------------------
|
|
Print the currently available vehicles
|
|
---------------------------------------------------------------------------]]
|
|
local function GetAvailableVehicles(ply)
|
|
if SERVER and IsValid(ply) and not ply:IsAdmin() then return end
|
|
local print = SERVER and ServerLog or Msg
|
|
|
|
print(DarkRP.getPhrase("rp_getvehicles") .. "\n")
|
|
for k in pairs(DarkRP.getAvailableVehicles()) do
|
|
print("\"" .. k .. "\"" .. "\n")
|
|
end
|
|
end
|
|
if SERVER then
|
|
concommand.Add("rp_getvehicles_sv", GetAvailableVehicles)
|
|
else
|
|
concommand.Add("rp_getvehicles", GetAvailableVehicles)
|
|
end
|
|
|
|
--[[---------------------------------------------------------------------------
|
|
Whether a player has a DarkRP privilege
|
|
---------------------------------------------------------------------------]]
|
|
function meta:hasDarkRPPrivilege(priv)
|
|
if FAdmin then
|
|
return FAdmin.Access.PlayerHasPrivilege(self, priv)
|
|
end
|
|
return self:IsAdmin()
|
|
end
|
|
|
|
--[[---------------------------------------------------------------------------
|
|
Convenience function to return the players sorted by name
|
|
---------------------------------------------------------------------------]]
|
|
function DarkRP.nickSortedPlayers()
|
|
local plys = player.GetAll()
|
|
table.sort(plys, function(a,b) return a:Nick() < b:Nick() end)
|
|
return plys
|
|
end
|
|
|
|
--[[---------------------------------------------------------------------------
|
|
Convert a string to a table of arguments
|
|
---------------------------------------------------------------------------]]
|
|
local bitlshift, stringgmatch, stringsub, tableinsert = bit.lshift, string.gmatch, string.sub, table.insert
|
|
function DarkRP.explodeArg(arg)
|
|
local args = {}
|
|
|
|
local from, to, diff = 1, 0, 0
|
|
local inQuotes, wasQuotes = false, false
|
|
|
|
for c in stringgmatch(arg, '.') do
|
|
to = to + 1
|
|
|
|
if c == '"' then
|
|
inQuotes = not inQuotes
|
|
wasQuotes = true
|
|
|
|
continue
|
|
end
|
|
|
|
if c == ' ' and not inQuotes then
|
|
diff = wasQuotes and 1 or 0
|
|
wasQuotes = false
|
|
tableinsert(args, stringsub(arg, from + diff, to - 1 - diff))
|
|
from = to + 1
|
|
end
|
|
end
|
|
diff = wasQuotes and 1 or 0
|
|
|
|
if from ~= to + 1 then tableinsert(args, stringsub(arg, from + diff, to + 1 - bitlshift(diff, 1))) end
|
|
|
|
return args
|
|
end
|
|
|
|
--[[---------------------------------------------------------------------------
|
|
Initialize Physics, throw an error on failure
|
|
---------------------------------------------------------------------------]]
|
|
function DarkRP.ValidatedPhysicsInit(ent, solidType, hint)
|
|
solidType = solidType or SOLID_VPHYSICS
|
|
|
|
if ent:PhysicsInit(solidType) then return true end
|
|
|
|
local class = ent:GetClass()
|
|
|
|
if solidType == SOLID_BSP then
|
|
DarkRP.errorNoHalt(string.format("%s has no physics and will be motionless", class), 2, {
|
|
"Is this a brush model? SOLID_BSP physics cannot initialize on entities that don't have brush models",
|
|
"The physics limit may have been hit",
|
|
hint
|
|
})
|
|
|
|
return false
|
|
end
|
|
|
|
if solidType == SOLID_VPHYSICS then
|
|
local mdl = ent:GetModel()
|
|
|
|
if not mdl or mdl == "" then
|
|
DarkRP.errorNoHalt(string.format("Cannot init physics on entity \"%s\" because it has no model", class), 2, {hint})
|
|
return false
|
|
end
|
|
|
|
mdl = string.lower(mdl)
|
|
|
|
if util.IsValidProp(mdl) then
|
|
-- Has physics, we must have hit the limit
|
|
DarkRP.errorNoHalt(string.format("physics limit hit - %s will be motionless", class), 2, {hint})
|
|
|
|
return false
|
|
end
|
|
|
|
if not file.Exists(mdl, "GAME") then
|
|
DarkRP.errorNoHalt(string.format("%s has missing model \"%s\" and will be invisible and motionless", class, mdl), 2, {
|
|
"Is the model path correct?",
|
|
"Is the model from an addon that is not installed?",
|
|
"Is the model from a game that isn't (properly) mounted? E.g. Counter Strike: Source",
|
|
hint
|
|
})
|
|
|
|
return false
|
|
end
|
|
|
|
DarkRP.errorNoHalt(string.format("%s has model \"%s\" with no physics and will be motionless", class, mdl), 2, {
|
|
"Does this model have an associated physics model (modelname.phy)?",
|
|
"Is this model supposed to have physics? Many models, like effects and view models aren't made to have physics",
|
|
hint
|
|
})
|
|
|
|
return false
|
|
end
|
|
|
|
DarkRP.errorNoHalt(string.format("Unable to initilize physics on entity \"%s\"", class, {hint}), 2)
|
|
|
|
return false
|
|
end
|
|
|
|
--[[---------------------------------------------------------------------------
|
|
Like tonumber, but makes sure it's an integer
|
|
---------------------------------------------------------------------------]]
|
|
function DarkRP.toInt(value)
|
|
value = tonumber(value)
|
|
return value and math.floor(value)
|
|
end
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Check the database for integrity errors. Use in cases when stuff doesn't load
|
|
on restart, or you get corruption errors.
|
|
---------------------------------------------------------------------------]]
|
|
if SERVER then util.AddNetworkString("DarkRP_databaseCheckMessage") end
|
|
if CLIENT then net.Receive("DarkRP_databaseCheckMessage", fc{print, net.ReadString}) end
|
|
|
|
local function checkDatabase(ply)
|
|
local dbFile = SERVER and "sv.db" or "cl.db"
|
|
local display = (CLIENT or not IsValid(ply)) and print or function(msg)
|
|
net.Start("DarkRP_databaseCheckMessage")
|
|
net.WriteString(msg)
|
|
net.Send(ply)
|
|
end
|
|
|
|
if SERVER and IsValid(ply) and not ply:IsSuperAdmin() then
|
|
display("You must be superadmin")
|
|
return
|
|
end
|
|
|
|
if MySQLite and MySQLite.isMySQL() then
|
|
display(string.format([[WARNING: DarkRP is using MySQL. This only
|
|
checks the local SQLite database stored in the %s file in the
|
|
garrysmod/ folder. The check will continue.]], dbFile))
|
|
end
|
|
|
|
local check = sql.QueryValue("PRAGMA INTEGRITY_CHECK")
|
|
if check == false then
|
|
display([[The query to check the database failed. Shit's surely
|
|
fucked, but the cause is unknown.]])
|
|
return
|
|
end
|
|
|
|
if check == "ok" then
|
|
display(string.format("Your %s database file is good.", dbFile))
|
|
return
|
|
end
|
|
|
|
display(string.format([[There are errors in your %s database file. It's corrupt!
|
|
|
|
This can cause the following problems:
|
|
- Data not loading, think of blocked models, doors, players' money and RP names
|
|
- Settings resetting to their default values
|
|
- Lua errors on startup
|
|
|
|
The cause of the problem is that the %s file in your garrysmod/ folder on
|
|
%s is corrupt. How this came to be is unknown, but here's what you can do to solve it:
|
|
|
|
- Delete %s, and run a file integrity check. Warning: You will lose ALL data stored in it!
|
|
- Take the file and try to repair it. This is sadly something that requires some technical knowledge,
|
|
and may not always succeed.
|
|
|
|
The specific error, by the way, is as follows:
|
|
%s
|
|
]], dbFile, dbFile, SERVER and "the server" or "your own computer", dbFile, check))
|
|
|
|
end
|
|
concommand.Add("darkrp_check_db_" .. (SERVER and "sv" or "cl"), checkDatabase)
|