zombiesurvival-evolved/gamemodes/zombiesurvival/gamemode/sh_util.lua
William Moodhe e9da54c2f9 ZS updates for 2014-2018
Too many changes to list.
2018-05-01 18:32:59 -04:00

506 lines
12 KiB
Lua

function player.GetAllActive()
local t = {}
for _, pl in pairs(player.GetAll()) do
if not pl:IsSpectator() then
t[#t + 1] = pl
end
end
return t
end
function player.GetAllSpectators()
local t = {}
for _, pl in pairs(player.GetAll()) do
if pl:IsSpectator() then
t[#t + 1] = pl
end
end
return t
end
function FindStartingItem(id)
local item = FindItem(id)
if item and item.WorthShop then return item end
end
function FindItem(id)
return GAMEMODE.Items[id]
end
-- DEPRECATED behavior. CachedInvisibleEntities and filter tables is nonsense. Move to using functions.
local TrueVisibleTrace = {mask = MASK_SHOT}
function TrueVisible(posa, posb, filter)
local filt = ents.FindByClass("projectile_*")
filt = table.Add(filt, SERVER and GAMEMODE.CachedInvisibleEntities or player.GetAll())
if filter then
filt[#filt + 1] = filter
end
TrueVisibleTrace.start = posa
TrueVisibleTrace.endpos = posb
TrueVisibleTrace.filter = filt
TrueVisibleTrace.mask = MASK_SHOT
return not util.TraceLine(TrueVisibleTrace).Hit
end
-- DEPRECATED behavior. CachedInvisibleEntities and filter tables is nonsense. Move to using functions.
function TrueVisibleFilters(posa, posb, ...)
local filt = ents.FindByClass("projectile_*")
filt = table.Add(filt, SERVER and GAMEMODE.CachedInvisibleEntities or player.GetAll())
if ... ~= nil then
filt = table.Add(filt, {...})
end
TrueVisibleTrace.start = posa
TrueVisibleTrace.endpos = posb
TrueVisibleTrace.filter = filt
TrueVisibleTrace.mask = MASK_SHOT
return not util.TraceLine(TrueVisibleTrace).Hit
end
-- Useful macros for the 3 file system
function INC_SERVER()
AddCSLuaFile("shared.lua")
AddCSLuaFile("cl_init.lua")
include("shared.lua")
end
function INC_CLIENT()
include("shared.lua")
end
INC_CLIENT_NO_SHARED = INC_CLIENT
function INC_SERVER_NO_SHARED()
AddCSLuaFile("cl_init.lua")
end
function INC_SERVER_NO_CLIENT()
AddCSLuaFile("shared.lua")
end
-- Just in case you add this by mistake because it does nothing
function INC_SHARED()
end
MASK_SHOT_OPAQUE = bit.bor(MASK_SHOT, CONTENTS_OPAQUE)
-- Literally if photon particles can reach point b from point a.
local LightVisibleTrace = {mask = MASK_SHOT_OPAQUE}
function LightVisible(posa, posb, ...)
local filter
if ... ~= nil then
filter = {...}
end
LightVisibleTrace.start = posa
LightVisibleTrace.endpos = posb
LightVisibleTrace.filter = filter
return not util.TraceLine(LightVisibleTrace).Hit
end
local WorldVisibleTrace = {mask = MASK_SOLID_BRUSHONLY}
function WorldVisible(posa, posb)
WorldVisibleTrace.start = posa
WorldVisibleTrace.endpos = posb
return not util.TraceLine(WorldVisibleTrace).Hit
end
function CosineInterpolation(y1, y2, mu)
local mu2 = (1 - math.cos(mu * math.pi)) / 2
return y1 * (1 - mu2) + y2 * mu2
end
function CubicInterpolate(y0, y1, y2, y3, mu)
local mu2 = mu * mu
local a0 = y3 - y2 - y0 + y1
local a1 = y0 - y1 - a0
local a2 = y2 - y0
return a0 * mu * mu2 + a1 * mu2 + a2 * mu + y1
end
--[[function CatmullInterpolate(y0, y1, y2, y3, mu)
local mu2 = mu * mu
local a0 = -0.5 * y0 + 1.5 * y1 - 1.5 * y2 + 0.5 * y3
local a1 = y0 - 2.5 * y1 + 2 * y2 - 0.5 * y3
local a2 = -0.5 * y0 + 0.5 * y2
return a0 * mu * mu2 + a1 * mu2 + a2 * mu + y1
end]]
function CatmullInterpolate(previous, start, last, nextp, elapsedTime, duration)
local percentComplete = elapsedTime / duration
local percentCompleteSquared = percentComplete * percentComplete
local percentCompleteCubed = percentCompleteSquared * percentComplete
return previous * (-0.5 * percentCompleteCubed +
percentCompleteSquared -
0.5 * percentComplete) +
start * (1.5 * percentCompleteCubed +
-2.5 * percentCompleteSquared + 1.0) +
last * (-1.5 * percentCompleteCubed +
2.0 * percentCompleteSquared +
0.5 * percentComplete) +
nextp * (0.5 * percentCompleteCubed -
0.5 * percentCompleteSquared)
end
function string.AndSeparate(list)
local length = #list
if length <= 0 then return "" end
if length == 1 then return list[1] end
if length == 2 then return list[1].." and "..list[2] end
return table.concat(list, ", ", 1, length - 1)..", and "..list[length]
end
function util.SkewedDistance(a, b, skew)
if a.z > b.z then
return math.sqrt((b.x - a.x) ^ 2 + (b.y - a.y) ^ 2 + ((a.z - b.z) * skew) ^ 2)
end
return a:Distance(b)
end
function util.IsServerOrClient()
return SERVER and "SERVER" or "CLIENT"
end
function util.Blood(pos, amount, dir, force, noprediction)
local effectdata = EffectData()
effectdata:SetOrigin(pos)
effectdata:SetMagnitude(amount)
effectdata:SetNormal(dir)
effectdata:SetScale(math.max(128, force))
util.Effect("bloodstream", effectdata, nil, noprediction)
end
function util.BlastDamagePlayer(inf, att, center, radius, damage, damagetype, taperfactor)
if not att:IsValidPlayer() then ErrorNoHalt("[BlastDamagePlayer] Tried to use a nonplayer") end
util.BlastDamageEx(inf, att, center, radius * (att.ExpDamageRadiusMul or 1), damage * (att.ExplosiveDamageMul or 1), damagetype, taperfactor)
end
-- I had to make this since the default function checks visibility vs. the entitiy's center and not the nearest position.
function util.BlastDamageEx(inflictor, attacker, epicenter, radius, damage, damagetype, taperfactor)
local basedmg = damage
for _, ent in pairs(ents.FindInSphere(epicenter, radius)) do
if ent:IsValid() then
local nearest = ent:NearestPoint(epicenter)
if TrueVisibleFilters(epicenter, nearest, inflictor, attacker, ent)
or TrueVisibleFilters(epicenter, ent:EyePos(), inflictor, attacker, ent)
or TrueVisibleFilters(epicenter, ent:WorldSpaceCenter(), inflictor, attacker, ent) then
ent:TakeSpecialDamage(((radius - nearest:Distance(epicenter)) / radius) * basedmg, damagetype, attacker, inflictor, nearest)
if taperfactor and ent:IsPlayer() then
basedmg = basedmg * taperfactor
end
end
end
end
end
function util.BlastDamageExAlloc(inflictor, attacker, epicenter, radius, damage, damagetype)
local dmg
local t = {}
for _, ent in pairs(ents.FindInSphere(epicenter, radius)) do
if ent:IsValid() then
local nearest = ent:NearestPoint(epicenter)
if TrueVisibleFilters(epicenter, nearest, inflictor, attacker, ent)
or TrueVisibleFilters(epicenter, ent:EyePos(), inflictor, attacker, ent)
or TrueVisibleFilters(epicenter, ent:WorldSpaceCenter(), inflictor, attacker, ent) then
dmg = ((radius - nearest:Distance(epicenter)) / radius) * damage
ent:TakeSpecialDamage(dmg, damagetype, attacker, inflictor, nearest)
t[ent] = dmg
end
end
end
return t
end
function util.BlastAlloc(inflictor, attacker, epicenter, radius)
local t = {}
for _, ent in pairs(ents.FindInSphere(epicenter, radius)) do
if ent:IsValid() then
local nearest = ent:NearestPoint(epicenter)
if TrueVisibleFilters(epicenter, nearest, inflictor, attacker, ent)
or TrueVisibleFilters(epicenter, ent:EyePos(), inflictor, attacker, ent)
or TrueVisibleFilters(epicenter, ent:WorldSpaceCenter(), inflictor, attacker, ent) then
t[#t + 1] = ent
end
end
end
return t
end
function util.FindValidInSphere(pos, radius)
local ret = {}
for _, ent in pairs(util.FindInSphere(pos, radius)) do
if ent:IsValid() then
ret[#ret + 1] = ent
end
end
return ret
end
function util.PoisonBlastDamage(inflictor, attacker, epicenter, radius, damage, noreduce, instant)
for _, ent in pairs(ents.FindInSphere(epicenter, radius)) do
if ent:IsValid() then
local nearest = ent:NearestPoint(epicenter)
if TrueVisibleFilters(epicenter, nearest, inflictor, attacker, ent)
or TrueVisibleFilters(epicenter, ent:EyePos(), inflictor, attacker, ent)
or TrueVisibleFilters(epicenter, ent:WorldSpaceCenter(), inflictor, attacker, ent) then
ent:PoisonDamage(((radius - nearest:Distance(epicenter)) / radius) * damage, attacker, inflictor, nil, noreduce, instant)
end
end
end
end
function util.ToMinutesSeconds(seconds)
local minutes = math.floor(seconds / 60)
seconds = seconds - minutes * 60
return string.format("%02d:%02d", minutes, math.floor(seconds))
end
-- More appropriate for count downs. Timer will display 00:01 if less than a second remains and never display 00:00.
function util.ToMinutesSecondsCD(seconds)
seconds = math.ceil(seconds)
local minutes = math.floor(seconds / 60)
seconds = seconds - minutes * 60
return string.format("%02d:%02d", minutes, seconds)
end
function util.ToMinutesSecondsMilliseconds(seconds)
local minutes = math.floor(seconds / 60)
seconds = seconds - minutes * 60
local milliseconds = math.floor(seconds % 1 * 100)
return string.format("%02d:%02d.%02d", minutes, math.floor(seconds), milliseconds)
end
function util.RemoveAll(class)
for _, ent in pairs(ents.FindByClass(class)) do
ent:Remove()
end
end
-- Takes a table of false/trues with numbers as the keys, compresses to array of chars (8 bits), in other words a string, for storage in files.
-- The net library already uses the smallest number of bits needed so this is only for storing data.
function util.CompressBitTable(t)
local buf = ""
local maxvalue = 0
t = table.ToAssoc(t)
for k in pairs(t) do
if k > maxvalue then maxvalue = k end
end
local num_bytes = math.ceil(maxvalue / 8)
for on_byte = 1, num_bytes do
local byte = 0
for bit_slot = 1, 8 do
if t[bit_slot + 8 * (on_byte - 1)] then
byte = bit.bor(byte, 2 ^ (bit_slot - 1))
end
end
buf = buf..string.char(byte)
end
return buf
end
function util.DecompressBitTable(str, associative)
local t = {}
for on_byte = 1, #str do
local byte = str:sub(on_byte, on_byte):byte()
for bit_slot = 1, 8 do
if bit.band(byte, 1) == 1 then
local v = bit_slot + 8 * (on_byte - 1)
if associative then
t[v] = true
else
t[#t + 1] = v
end
end
byte = bit.arshift(byte, 1)
end
end
return t
end
function table.IsAssoc(t)
for _, v in pairs(t) do
if v == true then
return true
end
return false
end
end
function table.ToAssoc(t)
if not table.IsAssoc(t) then
local t2 = {}
for k, v in pairs(t) do
t2[v] = true
end
return t2
end
return t
end
function table.ToKeyValues(t)
if table.IsAssoc(t) then
local t2 = {}
for k, v in pairs(t) do
if v then
t2[#t2 + 1] = k
end
end
return t2
end
return t
end
local function TooNear(spawn, tab, dist)
dist = dist * dist
local spawnpos = spawn:GetPos()
for _, ent in pairs(tab) do
if ent:GetPos():DistToSqr(spawnpos) <= dist then
return true
end
end
return false
end
function team.GetSpawnPointGrouped(teamid, dist)
dist = dist or 200
local tab = {}
local spawns = team.GetSpawnPoint(teamid)
for _, spawn in pairs(spawns) do
if not TooNear(spawn, tab, dist) then
table.insert(tab, spawn)
end
end
return tab
end
function AccessorFuncDT(tab, membername, type, id)
local emeta = FindMetaTable("Entity")
local setter = emeta["SetDT"..type]
local getter = emeta["GetDT"..type]
tab["Set"..membername] = function(me, val)
setter(me, id, val)
end
tab["Get"..membername] = function(me)
return getter(me, id)
end
end
function team.GetValidSpawnPoint(teamid)
local t = {}
local spawns = team.GetSpawnPoint(teamid)
if spawns then
for _, ent in pairs(spawns) do
if ent:IsValid() and not ent.Disabled then
t[#t + 1] = ent
end
end
end
return t
end
function ents.CreateLimited(class, limit)
if #ents.FindByClass(class) >= (limit or 32) then return NULL end
return ents.Create(class)
end
function string.CommaSeparate(num)
local k
for ___=1, 10000 do
num, k = string.gsub(num, "^(-?%d+)(%d%d%d)", "%1,%2")
if k == 0 then break end
end
return num
end
function tonumbersafe(a)
local n = tonumber(a)
if n then
if n == 0 or n < 0 or n > 0 then
return n
end
-- NaN!
return 0
end
return nil
end
-- y from the top left can be retrieved with quad_h - y
function util.IntersectRayWithQuad(start, dir, quad_bottom_left, quad_angles, quad_w, quad_h, double_sided)
local quad_normal = quad_angles:Forward()
if not double_sided and dir:Dot(quad_normal) > 0 then return end
local hitpos = util.IntersectRayWithPlane(start, dir, quad_bottom_left, quad_normal)
if hitpos then
local lpos, _ = WorldToLocal(hitpos, quad_angles, quad_bottom_left, quad_angles)
local x = lpos.y
local y = lpos.z
if x >= 0 and x <= quad_w and y >= 0 and y <= quad_h then
return hitpos, x, y
end
end
end
local pulseeffect = EffectData()
pulseeffect:SetRadius(8)
pulseeffect:SetMagnitude(1)
pulseeffect:SetScale(1)
function util.CreatePulseImpactEffect(hitpos, hitnormal)
pulseeffect:SetOrigin(hitpos)
pulseeffect:SetNormal(hitnormal)
util.Effect("cball_bounce", pulseeffect)
end