Files
wnsrc/lua/tfa/ballistics/bullet.lua
lifestorm 94063e4369 Upload
2024-08-04 22:55:00 +03:00

403 lines
12 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.
local vector_origin = Vector()
--[[Bullet Struct:
[BULLET_ID] = {
["owner"] = Entity, --used for dmginfo SetAttacker
["inflictor"] = Entity, --used for dmginfo SetInflictor
["damage"] = Double, --floating point number representing inflicted damage
["force"] = Double,
["pos"] = Vector, --vector representing current position
["velocity"] = Vector, --vector representing movement velocity
["model"] = String --optional variable representing the given model,
["bul"] = {} --optional table containing bullet data,
["smokeparticle"] = String, --smoke particle name from within pcf
["bulletOverride"] = Bool --disable coming out of gun barrel on clientside
}
]]
local BallisticBullet = {
["owner"] = NULL,
["inflictor"] = NULL,
["damage"] = 0,
["force"] = 0,
["pos"] = vector_origin,
["velocity"] = vector_origin,
["model"] = "models/bullets/w_pbullet1.mdl",
["bul"] = {},
["delete"] = false,
["smokeparticle"] = "tfa_bullet_smoke_tracer"
}
local traceRes = {}
local traceData = {
mask = MASK_SHOT,
collisiongroup = COLLISION_GROUP_NONE,
ignoreworld = false,
output = traceRes
}
local MASK_SHOT_NOWATER = MASK_SHOT
--main update block
function BallisticBullet:Update(delta)
if self.delete then return end
self:_setup()
if self.delete then return end
local realdelta = (delta - self.last_update) / TFA.Ballistics.SubSteps
self.last_update = delta
local newPos = self:_getnewPosition(realdelta)
newPos = self:_checkWater(realdelta, newPos)
self:_accelerate(realdelta)
self:_moveSafe(newPos)
end
--internal function for sanity checks, etc.
function BallisticBullet:_setup()
self.creationTime = CurTime()
if (not IsValid(self.owner)) or (not IsValid(self.inflictor)) then
self:Remove()
end
if CurTime() > self.creationTime + TFA.Ballistics.BulletLife then
self:Remove()
end
self.playerOwned = self.owner.IsPlayer and self.owner:IsPlayer()
self.startVelocity = self.velocity:Length()
self.startDamage = self.damage
end
function BallisticBullet:_think()
if (not IsValid(self.owner)) or (not IsValid(self.inflictor)) then
self:Remove()
end
if CurTime() > self.creationTime + TFA.Ballistics.BulletLife then
self:Remove()
end
end
--internal function for calculating position change
function BallisticBullet:_getnewPosition(delta)
--verlet
return self.pos + (self.velocity + TFA.Ballistics.Gravity * delta * 0.5) * delta
end
--internal function for handling water
function BallisticBullet:_checkWater(delta, target)
local newPos = target
traceData.start = self.pos
traceData.endpos = newPos
traceData.filter = {self.owner, self.inflictor}
traceData.mask = MASK_WATER
util.TraceLine(traceData)
if traceRes.Hit and traceRes.Fraction < 1 and traceRes.Fraction > 0 and not self.Underwater then
self.Underwater = true
newPos = traceRes.HitPos + traceRes.Normal
self.velocity = self.velocity / TFA.Ballistics.WaterEntranceResistance
local fx = EffectData()
fx:SetOrigin(newPos)
local sc = math.sqrt(self.damage / 28) * 6
fx:SetScale(sc)
util.Effect("gunshotsplash", fx)
end
return newPos
end
--internal function for handling acceleration
local function GetWind()
return vector_origin
end
if StormFox and StormFox.Version then
if StormFox.Version < 2 then -- SF1
local SF_GetNetworkData = StormFox.GetNetworkData
function GetWind()
local windSpeed = SF_GetNetworkData("Wind") * TFA.Ballistics.UnitScale
local windAng = Angle(0, SF_GetNetworkData("WindAngle"), 0)
return windSpeed * windAng:Forward():GetNormalized()
end
elseif StormFox.Wind then -- SF2
local SFW_GetForce = StormFox.Wind.GetForce
local SFW_GetYaw = StormFox.Wind.GetYaw
function GetWind()
local windSpeed = SFW_GetForce() * TFA.Ballistics.UnitScale
local windAng = Angle(0, SFW_GetYaw(), 0)
return windSpeed * windAng:Forward():GetNormalized()
end
end
end
function BallisticBullet:_accelerate(delta)
local dragDensity = self.Underwater and TFA.Ballistics.WaterResistance or TFA.Ballistics.AirResistance
local drag = -self.velocity:GetNormalized() * self.velocity:Length() * self.velocity:Length() * 0.00006 * dragDensity
local wind = GetWind()
if self.Underwater then
self.velocity = self.velocity / (1 + TFA.Ballistics.WaterResistance * delta)
end
self.velocity = self.velocity + (TFA.Ballistics.Gravity + drag + wind) * delta
self.damage = self.startDamage * math.sqrt(self.velocity:Length() / self.startVelocity)
end
local IsInWorld, IsInWorld2
do
local tr = {collisiongroup = COLLISION_GROUP_WORLD}
function IsInWorld2(pos)
tr.start = pos
tr.endpos = pos
return not util.TraceLine(tr).AllSolid
end
end
if CLIENT then
IsInWorld = IsInWorld2
else
IsInWorld = util.IsInWorld
end
--internal function for moving with collision test
function BallisticBullet:_moveSafe(newPos)
if not self.tr_filter then
if IsValid(self.IgnoreEntity) then
self.tr_filter = {self.owner, self.inflictor, self.IgnoreEntity}
else
self.tr_filter = {self.owner, self.inflictor}
end
end
traceData.start = self.pos
traceData.endpos = newPos + (newPos - self.pos):GetNormalized()
traceData.filter = self.tr_filter
traceData.mask = MASK_SHOT_NOWATER
--collision trace
if self.playerOwned then
self.owner:LagCompensation(true)
end
util.TraceLine(traceData)
if self.playerOwned then
self.owner:LagCompensation(false)
end
--collision check
if traceRes.Hit and traceRes.Fraction < 1 and traceRes.Fraction > 0 then
self:Impact(traceRes)
elseif IsInWorld(newPos) then
self.pos = newPos
else
self:Remove()
end
end
--called when hitting something, or manually if necessary
function BallisticBullet:Impact(tr)
self.pos = tr.HitPos
self:Remove()
if CLIENT and (game.SinglePlayer() or self.owner ~= LocalPlayer()) then return end
if tr.HitSky then return end
local vn = self.velocity:GetNormalized()
local bul = {
["Damage"] = self.damage,
["Force"] = self.force,
["Num"] = 1,
["Src"] = self.pos - vn * 4,
["Dir"] = vn * 8,
["Spread"] = vector_origin,
["IgnoreEntity"] = self.owner,
["Attacker"] = self.owner,
["Distance"] = 8,
["Tracer"] = 0
}
setmetatable(bul, {
["__index"] = self.bul
})
self.owner:FireBullets(bul)
end
--Render
--local cv_bullet_style, cv_tracers_adv
local cv_bullet_style
if CLIENT then
CreateClientConVar("cl_tfa_ballistics_mp", "1", true, false, "Receive bullet data from other players?")
cv_bullet_style = CreateClientConVar("cl_tfa_ballistics_fx_bullet", "1", true, false, "Display bullet models for each TFA ballistics bullet?")
CreateClientConVar("cl_tfa_ballistics_fx_tracers_style", "1", true, false, "Style of tracers for TFA ballistics? 0=disable,1=smoke")
CreateClientConVar("cl_tfa_ballistics_fx_tracers_mp", "1", true, false, "Enable tracers for other TFA ballistics users?")
--cv_tracers_adv = CreateClientConVar("cl_tfa_ballistics_fx_tracers_adv", "1", true, false, "Enable advanced tracer calculations for other users? This corrects smoke trail to their barrel")
--[[
cv_receive = GetConVar("cl_tfa_ballistics_mp")
cv_bullet_style = GetConVar("cl_tfa_ballistics_fx_bullet")
cv_tracers_style = GetConVar("cl_tfa_ballistics_fx_tracers_style")
cv_tracers_mp = GetConVar("cl_tfa_ballistics_fx_tracers_mp")
cv_tracers_adv = GetConVar("cl_tfa_ballistics_fx_tracers_adv")
]]
--
end
--[[local DEFANGPOS = {
Pos = vector_origin,
Ang = angle_zero
}]]
function BallisticBullet:Render()
if SERVER then return end
if self.delete then return end
if not self.curmodel then
self.curmodel = ClientsideModel(self.model, RENDERGROUP_OPAQUE)
self.curmodel:SetNoDraw(not cv_bullet_style:GetBool())
end
--[==[if IsValid(self.curmodel) and (cv_bullet_style:GetBool() or self.smokeparticle ~= "") then
if self.customPosition then
fpos = self.pos
--fang = self.velocity:Angle()
else
if self.owner == GetViewEntity() or self.owner == LocalPlayer() then
local spos, sang = self.pos, self.velocity:Angle()
self.curmodel:SetPos(spos)
self.curmodel:SetAngles(sang)
if not self.vOffsetPos then
local att
if self.inflictor.GetMuzzleAttachment and self.inflictor:GetMuzzleAttachment() then
att = self.inflictor:GetMuzzleAttachment()
else
att = self.inflictor.MuzzleAttachmentRaw or 1
end
if LocalPlayer():ShouldDrawLocalPlayer() then
local npos = LocalPlayer():GetActiveWeapon():GetAttachment(att) or DEFANGPOS
self.vOffsetPos = self.curmodel:WorldToLocal(npos.Pos)
self.vOffsetAng = self.curmodel:WorldToLocalAngles(npos.Ang)
else
local npos = LocalPlayer():GetViewModel():GetAttachment(att) or DEFANGPOS
self.vOffsetPos = self.curmodel:WorldToLocal(npos.Pos)
self.vOffsetAng = self.curmodel:WorldToLocalAngles(npos.Ang)
end
end
fpos = self.curmodel:LocalToWorld(self.vOffsetPos)
--fang = self.curmodel:LocalToWorldAngles(self.vOffsetAng)
elseif self.owner:IsPlayer() and cv_tracers_adv:GetBool() then
local spos, sang = self.pos, self.velocity:Angle()
self.curmodel:SetPos(spos)
self.curmodel:SetAngles(sang)
if not self.vOffsetPos then
local npos = self.owner:GetActiveWeapon():GetAttachment(1) or DEFANGPOS
self.vOffsetPos = self.curmodel:WorldToLocal(npos.Pos)
self.vOffsetAng = self.curmodel:WorldToLocalAngles(npos.Ang)
end
fpos = self.curmodel:LocalToWorld(self.vOffsetPos)
--fang = self.curmodel:LocalToWorldAngles(self.vOffsetAng)
else
fpos = self.pos
--fang = self.velocity:Angle()
end
end
--[[if cv_bullet_style:GetBool() then
self.curmodel:SetupBones()
self.curmodel:DrawModel()
end]]
end]==]
local fpos, fang = self.pos, self.velocity:Angle()
self.curmodel:SetPos(fpos)
self.curmodel:SetAngles(fang)
if self.smokeparticle ~= "" and not self.cursmoke then
self.cursmoke = CreateParticleSystem(self.curmodel, self.smokeparticle, PATTACH_ABSORIGIN_FOLLOW, 1)
if not self.cursmoke then return end
self.cursmoke:StartEmission()
elseif self.cursmoke and IsValid(self.owner) then
self.cursmoke:SetSortOrigin(self.owner.GetShootPos and self.owner:GetShootPos() or self.owner.EyePos and self.owner:EyePos() or vector_origin)
if self.Underwater then
self.cursmoke:StopEmission()
self.cursmoke = nil
self.smokeparticle = ""
end
end
end
function BallisticBullet:Remove()
if self.cursmoke then
self.cursmoke:StopEmission()
self.cursmoke = nil
end
if self.curmodel and self.curmodel.Remove then
self.curmodel:Remove()
self.curmodel = nil
end
self.delete = true
end
local CopyTable = table.Copy
function TFA.Ballistics:Bullet(t)
local b = CopyTable(t or {})
setmetatable(b, {
["__index"] = BallisticBullet
})
return b
end