mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
447 lines
14 KiB
Lua
447 lines
14 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/
|
|
--]]
|
|
|
|
|
|
-- Copyright (c) 2018-2020 TFA Base Devs
|
|
|
|
-- 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.
|
|
|
|
-- Degrees to accuracy vector, Valve's formula from SDK 2013
|
|
TFA.DegreesToAccuracy = math.sin((math.pi / 180) / 2) -- approx. 0.00873
|
|
|
|
--default cvar integration
|
|
local cv_gravity = GetConVar("sv_gravity")
|
|
|
|
--[[local function TimeScale(v)
|
|
return v * game.GetTimeScale() / TFA.Ballistics.SubSteps
|
|
end]]
|
|
|
|
--init code
|
|
TFA.Ballistics = TFA.Ballistics or {}
|
|
TFA.Ballistics.Enabled = false
|
|
TFA.Ballistics.Gravity = Vector(0, 0, -cv_gravity:GetFloat())
|
|
TFA.Ballistics.Bullets = TFA.Ballistics.Bullets or {}
|
|
TFA.Ballistics.Bullets.bullet_registry = TFA.Ballistics.Bullets.bullet_registry or {}
|
|
TFA.Ballistics.BulletLife = 10
|
|
TFA.Ballistics.UnitScale = TFA.UnitScale or 39.3701 --meters to inches
|
|
TFA.Ballistics.AirResistance = 1
|
|
TFA.Ballistics.WaterResistance = 3
|
|
TFA.Ballistics.WaterEntranceResistance = 6
|
|
|
|
TFA.Ballistics.DamageVelocityLUT = {
|
|
[13] = 350, --shotgun
|
|
[25] = 425, --mp5k etc.
|
|
[35] = 900, --ak-12
|
|
[65] = 830, --SVD
|
|
[120] = 1100 --sniper cap
|
|
}
|
|
|
|
TFA.Ballistics.VelocityMultiplier = 1
|
|
TFA.Ballistics.SubSteps = 1
|
|
TFA.Ballistics.BulletCreationNetString = "TFABallisticsBullet"
|
|
|
|
TFA.Ballistics.TracerStyles = {
|
|
[0] = "",
|
|
[1] = "tfa_bullet_smoke_tracer",
|
|
[2] = "tfa_bullet_fire_tracer"
|
|
}
|
|
|
|
setmetatable(TFA.Ballistics.TracerStyles, {
|
|
["__index"] = function(t, k) return t[math.Round(tonumber(k) or 1)] or t[1] end
|
|
})
|
|
|
|
if SERVER then
|
|
util.AddNetworkString(TFA.Ballistics.BulletCreationNetString)
|
|
end
|
|
|
|
--bullet class
|
|
local function IncludeClass(fn)
|
|
include("tfa/ballistics/" .. fn .. ".lua")
|
|
AddCSLuaFile("tfa/ballistics/" .. fn .. ".lua")
|
|
end
|
|
|
|
IncludeClass("bullet")
|
|
--cvar code
|
|
local function CreateReplConVar(cvarname, cvarvalue, description, ...)
|
|
return CreateConVar(cvarname, cvarvalue, CLIENT and {FCVAR_REPLICATED} or {FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY}, description, ...)
|
|
end -- replicated only on clients, archive/notify on server
|
|
|
|
local cv_enabled = CreateReplConVar("sv_tfa_ballistics_enabled", "0", "Enable TFA Ballistics?")
|
|
local cv_bulletlife = CreateReplConVar("sv_tfa_ballistics_bullet_life", 10, "Time to process bullets before removing.")
|
|
local cv_res_air = CreateReplConVar("sv_tfa_ballistics_bullet_damping_air", 1, "Air resistance, which makes bullets arc faster.")
|
|
local cv_res_water = CreateReplConVar("sv_tfa_ballistics_bullet_damping_water", 3, "Water resistance, which makes bullets arc faster in water.")
|
|
local cv_vel = CreateReplConVar("sv_tfa_ballistics_bullet_velocity", 1, "Global velocity multiplier for TFA ballistics bullets.")
|
|
local cv_substep = CreateReplConVar("sv_tfa_ballistics_substeps", 1, "Substeps for ballistics; more is more precise, at the cost of performance.")
|
|
local sv_tfa_ballistics_custom_gravity = CreateReplConVar("sv_tfa_ballistics_custom_gravity", 0, "Enable sv_gravity override for ballistics")
|
|
local sv_tfa_ballistics_custom_gravity_value = CreateReplConVar("sv_tfa_ballistics_custom_gravity_value", 0, "Z velocity down of custom gravity")
|
|
CreateReplConVar("sv_tfa_ballistics_mindist", -1, "Minimum distance to activate; -1 for always.")
|
|
|
|
local function updateCVars()
|
|
TFA.Ballistics.BulletLife = cv_bulletlife:GetFloat()
|
|
TFA.Ballistics.AirResistance = cv_res_air:GetFloat()
|
|
TFA.Ballistics.WaterResistance = cv_res_water:GetFloat()
|
|
TFA.Ballistics.WaterEntranceResistance = TFA.Ballistics.WaterResistance * 2
|
|
TFA.Ballistics.VelocityMultiplier = cv_vel:GetFloat()
|
|
|
|
if sv_tfa_ballistics_custom_gravity:GetBool() then
|
|
TFA.Ballistics.Gravity.z = -sv_tfa_ballistics_custom_gravity_value:GetFloat()
|
|
else
|
|
TFA.Ballistics.Gravity.z = -cv_gravity:GetFloat()
|
|
end
|
|
|
|
TFA.Ballistics.Enabled = cv_enabled:GetBool()
|
|
TFA.Ballistics.SubSteps = cv_substep:GetInt()
|
|
end
|
|
|
|
cvars.AddChangeCallback("sv_tfa_ballistics_enabled", updateCVars, "TFA")
|
|
cvars.AddChangeCallback("sv_tfa_ballistics_bullet_life", updateCVars, "TFA")
|
|
cvars.AddChangeCallback("sv_tfa_ballistics_bullet_damping_air", updateCVars, "TFA")
|
|
cvars.AddChangeCallback("sv_tfa_ballistics_bullet_damping_water", updateCVars, "TFA")
|
|
cvars.AddChangeCallback("sv_tfa_ballistics_bullet_velocity", updateCVars, "TFA")
|
|
cvars.AddChangeCallback("sv_tfa_ballistics_substeps", updateCVars, "TFA")
|
|
cvars.AddChangeCallback("sv_tfa_ballistics_mindist", updateCVars, "TFA")
|
|
cvars.AddChangeCallback("sv_tfa_ballistics_custom_gravity", updateCVars, "TFA")
|
|
cvars.AddChangeCallback("sv_tfa_ballistics_custom_gravity_value", updateCVars, "TFA")
|
|
cvars.AddChangeCallback("sv_gravity", updateCVars, "TFA Ballistics")
|
|
updateCVars()
|
|
|
|
--client cvar code
|
|
local cv_receive, cv_tracers_style, cv_tracers_mp
|
|
|
|
if CLIENT then
|
|
cv_receive = CreateClientConVar("cl_tfa_ballistics_mp", "1", true, false, "Receive bullet data from other players?")
|
|
CreateClientConVar("cl_tfa_ballistics_fx_bullet", "1", true, false, "Display bullet models for each TFA ballistics bullet?")
|
|
cv_tracers_style = CreateClientConVar("cl_tfa_ballistics_fx_tracers_style", "1", true, false, "Style of tracers for TFA ballistics? 0=disable,1=smoke")
|
|
cv_tracers_mp = CreateClientConVar("cl_tfa_ballistics_fx_tracers_mp", "1", true, false, "Enable tracers for other TFA ballistics users?")
|
|
CreateClientConVar("cl_tfa_ballistics_fx_tracers_adv", "1", true, false, "Enable advanced tracer calculations for other users? This corrects smoke trail to their barrel")
|
|
end
|
|
|
|
--utility func
|
|
local function Remap(inp, u, v, x, y)
|
|
return (inp - u) / (v - u) * (y - x) + x
|
|
end
|
|
|
|
--Accessors
|
|
local CopyTable = table.Copy
|
|
|
|
function TFA.Ballistics.Bullets:Add(bulletStruct, originalBulletData)
|
|
local bullet = TFA.Ballistics:Bullet(bulletStruct)
|
|
bullet.bul = CopyTable(originalBulletData or bullet.bul)
|
|
bullet.last_update = CurTime() - TFA.FrameTime()
|
|
|
|
table.insert(self.bullet_registry, bullet)
|
|
|
|
bullet:_setup()
|
|
|
|
if SERVER and game.GetTimeScale() > 0.99 then
|
|
-- always update bullet since they are being added from predicted hook
|
|
bullet:Update(CurTime())
|
|
end
|
|
end
|
|
|
|
function TFA.Ballistics.Bullets:Update(ply)
|
|
--local delta = TimeScale(SysTime() - (self.lastUpdate or (SysTime() - FrameTime())))
|
|
local delta = CurTime()
|
|
|
|
--self.lastUpdate = SysTime()
|
|
local toremove
|
|
local lply = CLIENT and LocalPlayer()
|
|
|
|
for i, bullet in ipairs(self.bullet_registry) do
|
|
if bullet.delete then
|
|
if not toremove then
|
|
toremove = {}
|
|
end
|
|
|
|
table.insert(toremove, i)
|
|
elseif not ply and not bullet.playerOwned or CLIENT and bullet.owner ~= lply or ply == bullet.owner then
|
|
for _ = 1, TFA.Ballistics.SubSteps do
|
|
bullet:Update(delta)
|
|
end
|
|
end
|
|
end
|
|
|
|
if toremove then
|
|
for i = #toremove, 1, -1 do
|
|
table.remove(self.bullet_registry, toremove[i])
|
|
end
|
|
end
|
|
end
|
|
|
|
function TFA.Ballistics:AutoDetectVelocity(damage)
|
|
local lutMin, lutMax, LUT, DMGs
|
|
LUT = self.DamageVelocityLUT
|
|
DMGs = table.GetKeys(LUT)
|
|
table.sort(DMGs)
|
|
|
|
for _, v in ipairs(DMGs) do
|
|
if v < damage then
|
|
lutMin = v
|
|
elseif lutMin then
|
|
lutMax = v
|
|
break
|
|
end
|
|
end
|
|
|
|
if not lutMax then
|
|
lutMax = DMGs[#DMGs]
|
|
lutMin = DMGs[#DMGs - 1]
|
|
elseif not lutMin then
|
|
lutMin = DMGs[1]
|
|
lutMax = DMGs[2]
|
|
end
|
|
|
|
return Remap(damage, lutMin, lutMax, LUT[lutMin], LUT[lutMax])
|
|
end
|
|
|
|
function TFA.Ballistics:ShouldUse(wep)
|
|
if not IsValid(wep) or not wep.IsTFAWeapon then
|
|
return false
|
|
end
|
|
|
|
local shouldUse = wep:GetStatL("UseBallistics")
|
|
|
|
if shouldUse == nil then
|
|
if wep:GetStatL("TracerPCF") then
|
|
return false
|
|
end
|
|
|
|
return self.Enabled
|
|
else
|
|
return shouldUse
|
|
end
|
|
end
|
|
|
|
local sv_tfa_recoil_legacy = GetConVar("sv_tfa_recoil_legacy")
|
|
|
|
function TFA.Ballistics:FireBullets(wep, bulletStruct, angIn, bulletOverride)
|
|
if not IsValid(wep) then return end
|
|
if not IsValid(wep:GetOwner()) then return end
|
|
|
|
local vel
|
|
|
|
if bulletStruct.Velocity then
|
|
vel = bulletStruct.Velocity
|
|
elseif wep.GetStat and wep:GetStatL("Primary.Velocity") then
|
|
vel = wep:GetStatL("Primary.Velocity") * TFA.Ballistics.UnitScale
|
|
elseif wep.Primary and wep.Primary.Velocity then
|
|
vel = wep.Primary.Velocity * TFA.Ballistics.UnitScale
|
|
elseif wep.Velocity then
|
|
vel = wep.Velocity * TFA.Ballistics.UnitScale
|
|
else
|
|
local dmg
|
|
|
|
if wep.GetStat and wep:GetStatL("Primary.Damage") then
|
|
dmg = wep:GetStatL("Primary.Damage")
|
|
else
|
|
dmg = wep.Primary.Damage or wep.Damage or 30
|
|
end
|
|
|
|
vel = TFA.Ballistics:AutoDetectVelocity(dmg) * TFA.Ballistics.UnitScale
|
|
end
|
|
|
|
vel = vel * (TFA.Ballistics.VelocityMultiplier or 1)
|
|
|
|
local oldNum = bulletStruct.Num
|
|
bulletStruct.Num = 1
|
|
bulletStruct.IsBallistics = true
|
|
|
|
local owner = wep:GetOwner()
|
|
local isnpc = owner:IsNPC()
|
|
local ac = bulletStruct.Spread
|
|
local sharedRandomSeed = "Ballistics" .. CurTime()
|
|
|
|
for i = 1, oldNum do
|
|
local ang
|
|
|
|
if angIn then
|
|
ang = angIn
|
|
else
|
|
ang = owner:GetAimVector():Angle()
|
|
|
|
if sv_tfa_recoil_legacy:GetBool() and not isnpc then
|
|
ang:Add(owner:GetViewPunchAngles())
|
|
else
|
|
ang.p = ang.p + wep:GetViewPunchP()
|
|
ang.y = ang.y + wep:GetViewPunchY()
|
|
end
|
|
end
|
|
|
|
if not angIn then
|
|
ang:RotateAroundAxis(ang:Up(), util.SharedRandom(sharedRandomSeed, -ac.x * 45, ac.x * 45, 0 + i))
|
|
ang:RotateAroundAxis(ang:Right(), util.SharedRandom(sharedRandomSeed, -ac.y * 45, ac.y * 45, 1 + i))
|
|
end
|
|
|
|
local struct = {
|
|
owner = owner, --used for dmginfo SetAttacker
|
|
inflictor = wep, --used for dmginfo SetInflictor
|
|
damage = bulletStruct.Damage, --floating point number representing inflicted damage
|
|
force = bulletStruct.Force,
|
|
pos = bulletOverride and bulletStruct.Src or owner:GetShootPos(), --b.Src, --vector representing current position
|
|
velocity = (bulletOverride and bulletStruct.Dir or ang:Forward()) * vel, --b.Dir * vel, --vector representing movement velocity
|
|
model = wep.BulletModel or bulletStruct.Model, --optional variable representing the given model
|
|
smokeparticle = bulletStruct.SmokeParticle,
|
|
customPosition = bulletStruct.CustomPosition or bulletOverride,
|
|
IgnoreEntity = bulletStruct.IgnoreEntity
|
|
}
|
|
|
|
if CLIENT then
|
|
if not struct.smokeparticle then
|
|
struct.smokeparticle = TFA.Ballistics.TracerStyles[cv_tracers_style:GetInt()]
|
|
end
|
|
end
|
|
|
|
self.Bullets:Add(struct, bulletStruct)
|
|
|
|
if SERVER then
|
|
net.Start(TFA.Ballistics.BulletCreationNetString)
|
|
|
|
net.WriteEntity(struct.owner)
|
|
net.WriteEntity(struct.inflictor)
|
|
net.WriteFloat(struct.damage)
|
|
net.WriteFloat(struct.force)
|
|
net.WriteVector(struct.pos)
|
|
|
|
net.WriteDouble(struct.velocity.x)
|
|
net.WriteDouble(struct.velocity.y)
|
|
net.WriteDouble(struct.velocity.z)
|
|
|
|
net.WriteString(struct.model or '')
|
|
net.WriteString(struct.smokeparticle or '')
|
|
net.WriteBool(struct.customPosition == true)
|
|
net.WriteEntity(struct.IgnoreEntity or NULL)
|
|
|
|
net.WriteVector(bulletStruct.Src)
|
|
net.WriteNormal(bulletStruct.Dir)
|
|
net.WriteEntity(bulletStruct.Attacker)
|
|
net.WriteVector(bulletStruct.Spread)
|
|
net.WriteFloat(vel)
|
|
|
|
if game.SinglePlayer() or isnpc then
|
|
net.SendPVS(struct.pos)
|
|
else
|
|
net.SendOmit(owner)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function TFA.Ballistics.Bullets:Render()
|
|
for i = 1, #self.bullet_registry do
|
|
self.bullet_registry[i]:Render()
|
|
end
|
|
end
|
|
|
|
local sp = game.SinglePlayer()
|
|
|
|
--Netcode and Hooks
|
|
if CLIENT then
|
|
net.Receive(TFA.Ballistics.BulletCreationNetString, function()
|
|
if not sp and not cv_receive:GetBool() then return end
|
|
|
|
local owner = net.ReadEntity()
|
|
local inflictor = net.ReadEntity()
|
|
local damage = net.ReadFloat()
|
|
local force = net.ReadFloat()
|
|
local pos = net.ReadVector()
|
|
local velocity = Vector(net.ReadDouble(), net.ReadDouble(), net.ReadDouble())
|
|
local model = net.ReadString()
|
|
local smokeparticle = net.ReadString()
|
|
local customPosition = net.ReadBool()
|
|
local IgnoreEntity = net.ReadEntity()
|
|
|
|
|
|
local Src = net.ReadVector()
|
|
local Dir = net.ReadNormal()
|
|
local Attacker = net.ReadEntity()
|
|
local Spread = net.ReadVector()
|
|
local Velocity = net.ReadFloat()
|
|
|
|
if not IsValid(owner) or not IsValid(inflictor) then return end
|
|
|
|
if not cv_tracers_mp:GetBool() and owner ~= LocalPlayer() then
|
|
smokeparticle = ""
|
|
elseif smokeparticle == "" then
|
|
smokeparticle = TFA.Ballistics.TracerStyles[cv_tracers_style:GetInt()]
|
|
end
|
|
|
|
local struct = {
|
|
owner = owner,
|
|
inflictor = inflictor,
|
|
damage = damage,
|
|
force = force,
|
|
pos = pos,
|
|
velocity = velocity,
|
|
model = model ~= "" and model or nil,
|
|
smokeparticle = smokeparticle,
|
|
customPosition = customPosition,
|
|
IgnoreEntity = IgnoreEntity,
|
|
}
|
|
|
|
local bulletStruct = {
|
|
Damage = damage,
|
|
Force = force,
|
|
Num = 1,
|
|
Src = Src,
|
|
Dir = Dir,
|
|
Attacker = Attacker,
|
|
Spread = Spread,
|
|
SmokeParticle = smokeparticle,
|
|
CustomPosition = customPosition,
|
|
Model = model ~= "" and model or nil,
|
|
Velocity = Velocity,
|
|
IsBallistics = true,
|
|
}
|
|
|
|
TFA.Ballistics.Bullets:Add(struct, bulletStruct)
|
|
end)
|
|
end
|
|
|
|
if CLIENT then
|
|
hook.Add("FinishMove", "TFABallisticsTick", function(self)
|
|
if IsFirstTimePredicted() then
|
|
TFA.Ballistics.Bullets:Update(self)
|
|
end
|
|
end)
|
|
else
|
|
hook.Add("PlayerPostThink", "TFABallisticsTick", function(self)
|
|
TFA.Ballistics.Bullets:Update(self)
|
|
end)
|
|
end
|
|
|
|
hook.Add("Tick", "TFABallisticsTick", function()
|
|
TFA.Ballistics.Bullets:Update()
|
|
|
|
if CLIENT and sp then
|
|
TFA.Ballistics.Bullets:Update(LocalPlayer())
|
|
end
|
|
end)
|
|
|
|
--Rendering
|
|
hook.Add("PostDrawOpaqueRenderables", "TFABallisticsRender", function()
|
|
TFA.Ballistics.Bullets:Render()
|
|
end)
|