mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
380 lines
9.3 KiB
Lua
380 lines
9.3 KiB
Lua
|
|
--[[
|
||
|
|
| 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/
|
||
|
|
--]]
|
||
|
|
|
||
|
|
-- Random stuff
|
||
|
|
|
||
|
|
if not util then return end
|
||
|
|
|
||
|
|
local math = math
|
||
|
|
local string = string
|
||
|
|
local table = table
|
||
|
|
local pairs = pairs
|
||
|
|
|
||
|
|
-- attempts to get the weapon used from a DamageInfo instance needed because the
|
||
|
|
-- GetAmmoType value is useless and inflictor isn't properly set (yet)
|
||
|
|
function util.WeaponFromDamage(dmg)
|
||
|
|
local inf = dmg:GetInflictor()
|
||
|
|
local wep = nil
|
||
|
|
if IsValid(inf) then
|
||
|
|
if inf:IsWeapon() or inf.Projectile then
|
||
|
|
wep = inf
|
||
|
|
elseif dmg:IsDamageType(DMG_DIRECT) or dmg:IsDamageType(DMG_CRUSH) then
|
||
|
|
-- DMG_DIRECT is the player burning, no weapon involved
|
||
|
|
-- DMG_CRUSH is physics or falling on someone
|
||
|
|
wep = nil
|
||
|
|
elseif inf:IsPlayer() then
|
||
|
|
wep = inf:GetActiveWeapon()
|
||
|
|
if not IsValid(wep) then
|
||
|
|
-- this may have been a dying shot, in which case we need a
|
||
|
|
-- workaround to find the weapon because it was dropped on death
|
||
|
|
wep = IsValid(inf.dying_wep) and inf.dying_wep or nil
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
return wep
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Gets the table for a SWEP or a weapon-SENT (throwing knife), so not
|
||
|
|
-- equivalent to weapons.Get. Do not modify the table returned by this, consider
|
||
|
|
-- as read-only.
|
||
|
|
function util.WeaponForClass(cls)
|
||
|
|
local wep = weapons.GetStored(cls)
|
||
|
|
|
||
|
|
if not wep then
|
||
|
|
wep = scripted_ents.GetStored(cls)
|
||
|
|
if wep then
|
||
|
|
-- don't like to rely on this, but the alternative is
|
||
|
|
-- scripted_ents.Get which does a full table copy, so only do
|
||
|
|
-- that as last resort
|
||
|
|
wep = wep.t or scripted_ents.Get(cls)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
return wep
|
||
|
|
end
|
||
|
|
|
||
|
|
function util.GetAlivePlayers()
|
||
|
|
local alive = {}
|
||
|
|
for k, p in player.Iterator() do
|
||
|
|
if IsValid(p) and p:Alive() and p:IsTerror() then
|
||
|
|
table.insert(alive, p)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
return alive
|
||
|
|
end
|
||
|
|
|
||
|
|
function util.GetNextAlivePlayer(ply)
|
||
|
|
local alive = util.GetAlivePlayers()
|
||
|
|
|
||
|
|
if #alive < 1 then return nil end
|
||
|
|
|
||
|
|
local prev = nil
|
||
|
|
local choice = nil
|
||
|
|
|
||
|
|
if IsValid(ply) then
|
||
|
|
for k,p in ipairs(alive) do
|
||
|
|
if prev == ply then
|
||
|
|
choice = p
|
||
|
|
end
|
||
|
|
|
||
|
|
prev = p
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
if not IsValid(choice) then
|
||
|
|
choice = alive[1]
|
||
|
|
end
|
||
|
|
|
||
|
|
return choice
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Uppercases the first character only
|
||
|
|
function string.Capitalize(str)
|
||
|
|
return string.upper(string.sub(str, 1, 1)) .. string.sub(str, 2)
|
||
|
|
end
|
||
|
|
util.Capitalize = string.Capitalize
|
||
|
|
|
||
|
|
-- Color unpacking
|
||
|
|
function clr(color) return color.r, color.g, color.b, color.a; end
|
||
|
|
|
||
|
|
if CLIENT then
|
||
|
|
-- Is screenpos on screen?
|
||
|
|
function IsOffScreen(scrpos)
|
||
|
|
return not scrpos.visible or scrpos.x < 0 or scrpos.y < 0 or scrpos.x > ScrW() or scrpos.y > ScrH()
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function AccessorFuncDT(tbl, varname, name)
|
||
|
|
tbl["Get" .. name] = function(s) return s.dt and s.dt[varname] end
|
||
|
|
tbl["Set" .. name] = function(s, v) if s.dt then s.dt[varname] = v end end
|
||
|
|
end
|
||
|
|
|
||
|
|
function util.PaintDown(start, effname, ignore)
|
||
|
|
local btr = util.TraceLine({start=start, endpos=(start + Vector(0,0,-256)), filter=ignore, mask=MASK_SOLID})
|
||
|
|
|
||
|
|
util.Decal(effname, btr.HitPos+btr.HitNormal, btr.HitPos-btr.HitNormal)
|
||
|
|
end
|
||
|
|
|
||
|
|
local function DoBleed(ent)
|
||
|
|
if not IsValid(ent) or (ent:IsPlayer() and (not ent:Alive() or not ent:IsTerror())) then
|
||
|
|
return
|
||
|
|
end
|
||
|
|
|
||
|
|
local jitter = VectorRand() * 30
|
||
|
|
jitter.z = 20
|
||
|
|
|
||
|
|
util.PaintDown(ent:GetPos() + jitter, "Blood", ent)
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Something hurt us, start bleeding for a bit depending on the amount
|
||
|
|
function util.StartBleeding(ent, dmg, t)
|
||
|
|
if dmg < 5 or not IsValid(ent) then
|
||
|
|
return
|
||
|
|
end
|
||
|
|
|
||
|
|
if ent:IsPlayer() and (not ent:Alive() or not ent:IsTerror()) then
|
||
|
|
return
|
||
|
|
end
|
||
|
|
|
||
|
|
local times = math.Clamp(math.Round(dmg / 15), 1, 20)
|
||
|
|
|
||
|
|
local delay = math.Clamp(t / times , 0.1, 2)
|
||
|
|
|
||
|
|
if ent:IsPlayer() then
|
||
|
|
times = times * 2
|
||
|
|
delay = delay / 2
|
||
|
|
end
|
||
|
|
|
||
|
|
timer.Create("bleed" .. ent:EntIndex(), delay, times,
|
||
|
|
function() DoBleed(ent) end)
|
||
|
|
end
|
||
|
|
|
||
|
|
function util.StopBleeding(ent)
|
||
|
|
timer.Remove("bleed" .. ent:EntIndex())
|
||
|
|
end
|
||
|
|
|
||
|
|
local zapsound = Sound("npc/assassin/ball_zap1.wav")
|
||
|
|
function util.EquipmentDestroyed(pos)
|
||
|
|
local effect = EffectData()
|
||
|
|
effect:SetOrigin(pos)
|
||
|
|
util.Effect("cball_explode", effect)
|
||
|
|
sound.Play(zapsound, pos)
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Useful default behaviour for semi-modal DFrames
|
||
|
|
function util.BasicKeyHandler(pnl, kc)
|
||
|
|
-- passthrough F5
|
||
|
|
if kc == KEY_F5 then
|
||
|
|
RunConsoleCommand("jpeg")
|
||
|
|
else
|
||
|
|
pnl:Close()
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function util.noop() end
|
||
|
|
function util.passthrough(x) return x end
|
||
|
|
|
||
|
|
-- Fisher-Yates shuffle
|
||
|
|
local rand = math.random
|
||
|
|
function table.Shuffle(t)
|
||
|
|
local n = #t
|
||
|
|
|
||
|
|
while n > 1 do
|
||
|
|
-- n is now the last pertinent index
|
||
|
|
local k = rand(n) -- 1 <= k <= n
|
||
|
|
-- Quick swap
|
||
|
|
t[n], t[k] = t[k], t[n]
|
||
|
|
n = n - 1
|
||
|
|
end
|
||
|
|
|
||
|
|
return t
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Override with nil check
|
||
|
|
function table.HasValue(tbl, val)
|
||
|
|
if not tbl then return end
|
||
|
|
|
||
|
|
for k, v in pairs(tbl) do
|
||
|
|
if v == val then return true end
|
||
|
|
end
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Value equality for tables
|
||
|
|
function table.EqualValues(a, b)
|
||
|
|
if a == b then return true end
|
||
|
|
|
||
|
|
for k, v in pairs(a) do
|
||
|
|
if v != b[k] then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Basic table.HasValue pointer checks are insufficient when checking a table of
|
||
|
|
-- tables, so this uses table.EqualValues instead.
|
||
|
|
function table.HasTable(tbl, needle)
|
||
|
|
if not tbl then return end
|
||
|
|
|
||
|
|
for k, v in pairs(tbl) do
|
||
|
|
if v == needle then
|
||
|
|
return true
|
||
|
|
elseif table.EqualValues(v, needle) then
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Returns copy of table with only specific keys copied
|
||
|
|
function table.CopyKeys(tbl, keys)
|
||
|
|
if not (tbl and keys) then return end
|
||
|
|
|
||
|
|
local out = {}
|
||
|
|
local val = nil
|
||
|
|
for _, k in pairs(keys) do
|
||
|
|
val = tbl[k]
|
||
|
|
if istable(val) then
|
||
|
|
out[k] = table.Copy(val)
|
||
|
|
else
|
||
|
|
out[k] = val
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return out
|
||
|
|
end
|
||
|
|
|
||
|
|
local gsub = string.gsub
|
||
|
|
-- Simple string interpolation:
|
||
|
|
-- string.Interp("{killer} killed {victim}", {killer = "Bob", victim = "Joe"})
|
||
|
|
-- returns "Bob killed Joe"
|
||
|
|
-- No spaces or special chars in parameter name, just alphanumerics.
|
||
|
|
function string.Interp(str, tbl)
|
||
|
|
return gsub(str, '{(%w+)}', tbl)
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Short helper for input.LookupBinding, returns capitalised key or a default
|
||
|
|
function Key(binding, default)
|
||
|
|
local b = input.LookupBinding(binding)
|
||
|
|
if not b then return default end
|
||
|
|
|
||
|
|
return string.upper(b)
|
||
|
|
end
|
||
|
|
|
||
|
|
local exp = math.exp
|
||
|
|
-- Equivalent to ExponentialDecay from Source's mathlib.
|
||
|
|
-- Convenient for falloff curves.
|
||
|
|
function math.ExponentialDecay(halflife, dt)
|
||
|
|
-- ln(0.5) = -0.69..
|
||
|
|
return exp((-0.69314718 / halflife) * dt)
|
||
|
|
end
|
||
|
|
|
||
|
|
function Dev(level, ...)
|
||
|
|
if cvars and cvars.Number("developer", 0) >= level then
|
||
|
|
Msg("[TTT dev]")
|
||
|
|
-- table.concat does not tostring, derp
|
||
|
|
|
||
|
|
local params = {...}
|
||
|
|
for i=1,#params do
|
||
|
|
Msg(" " .. tostring(params[i]))
|
||
|
|
end
|
||
|
|
|
||
|
|
Msg("\n")
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function IsPlayer(ent)
|
||
|
|
return ent and ent:IsValid() and ent:IsPlayer()
|
||
|
|
end
|
||
|
|
|
||
|
|
function IsRagdoll(ent)
|
||
|
|
return ent and ent:IsValid() and ent:GetClass() == "prop_ragdoll"
|
||
|
|
end
|
||
|
|
|
||
|
|
local band = bit.band
|
||
|
|
function util.BitSet(val, bit)
|
||
|
|
return band(val, bit) == bit
|
||
|
|
end
|
||
|
|
|
||
|
|
if CLIENT then
|
||
|
|
local healthcolors = {
|
||
|
|
healthy = Color(0, 255, 0, 255),
|
||
|
|
hurt = Color(170, 230, 10, 255),
|
||
|
|
wounded = Color(230, 215, 10, 255),
|
||
|
|
badwound= Color(255, 140, 0, 255),
|
||
|
|
death = Color(255, 0, 0, 255)
|
||
|
|
};
|
||
|
|
|
||
|
|
function util.HealthToString(health, maxhealth)
|
||
|
|
maxhealth = maxhealth or 100
|
||
|
|
|
||
|
|
if health > maxhealth * 0.9 then
|
||
|
|
return "hp_healthy", healthcolors.healthy
|
||
|
|
elseif health > maxhealth * 0.7 then
|
||
|
|
return "hp_hurt", healthcolors.hurt
|
||
|
|
elseif health > maxhealth * 0.45 then
|
||
|
|
return "hp_wounded", healthcolors.wounded
|
||
|
|
elseif health > maxhealth * 0.2 then
|
||
|
|
return "hp_badwnd", healthcolors.badwound
|
||
|
|
else
|
||
|
|
return "hp_death", healthcolors.death
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
local karmacolors = {
|
||
|
|
max = Color(255, 255, 255, 255),
|
||
|
|
high = Color(255, 240, 135, 255),
|
||
|
|
med = Color(245, 220, 60, 255),
|
||
|
|
low = Color(255, 180, 0, 255),
|
||
|
|
min = Color(255, 130, 0, 255),
|
||
|
|
};
|
||
|
|
|
||
|
|
function util.KarmaToString(karma)
|
||
|
|
local maxkarma = GetGlobalInt("ttt_karma_max", 1000)
|
||
|
|
|
||
|
|
if karma > maxkarma * 0.89 then
|
||
|
|
return "karma_max", karmacolors.max
|
||
|
|
elseif karma > maxkarma * 0.8 then
|
||
|
|
return "karma_high", karmacolors.high
|
||
|
|
elseif karma > maxkarma * 0.65 then
|
||
|
|
return "karma_med", karmacolors.med
|
||
|
|
elseif karma > maxkarma * 0.5 then
|
||
|
|
return "karma_low", karmacolors.low
|
||
|
|
else
|
||
|
|
return "karma_min", karmacolors.min
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function util.IncludeClientFile(file)
|
||
|
|
include(file)
|
||
|
|
end
|
||
|
|
else
|
||
|
|
function util.IncludeClientFile(file)
|
||
|
|
AddCSLuaFile(file)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Like string.FormatTime but simpler (and working), always a string, no hour
|
||
|
|
-- support
|
||
|
|
function util.SimpleTime(seconds, fmt)
|
||
|
|
if not seconds then seconds = 0 end
|
||
|
|
|
||
|
|
local ms = (seconds - math.floor(seconds)) * 100
|
||
|
|
seconds = math.floor(seconds)
|
||
|
|
local s = seconds % 60
|
||
|
|
seconds = (seconds - s) / 60
|
||
|
|
local m = seconds % 60
|
||
|
|
|
||
|
|
return string.format(fmt, m, s, ms)
|
||
|
|
end
|