mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 21:53:46 +03:00
Upload
This commit is contained in:
446
lua/tfa/modules/tfa_ballistics.lua
Normal file
446
lua/tfa/modules/tfa_ballistics.lua
Normal file
@@ -0,0 +1,446 @@
|
||||
--[[
|
||||
| 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)
|
||||
Reference in New Issue
Block a user