mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
403 lines
12 KiB
Lua
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 |