Files
wnsrc/lua/tfa/modules/tfa_ballistics.lua
lifestorm 6a58f406b1 Upload
2024-08-04 23:54:45 +03:00

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)