ACF_CustomMod/lua/acf/server/sv_acfdamage.lua

437 lines
14 KiB
Lua

-- This file is meant for the advanced damage functions used by the Armored Combat Framework
function ACF_HE( Hitpos , HitNormal , FillerMass, FragMass , Inflictor, NoOcc, Ammo ) --HitPos = Detonation center, FillerMass = mass of TNT being detonated in KG, FragMass = Mass of the round casing for fragmentation purposes, Inflictor owner of said TNT
local Power = FillerMass * ACF.HEPower --Power in KiloJoules of the filler mass of TNT
local Radius = (FillerMass)^0.33*8*39.37 --Scalling law found on the net, based on 1PSI overpressure from 1 kg of TNT at 15m
local MaxSphere = (4 * 3.1415 * (Radius*2.54 )^2) --Surface Aera of the sphere at maximum radius
local Amp = math.min(Power/2000,50)
util.ScreenShake( Hitpos, Amp, Amp, Amp/15, Radius*10 )
--local Targets = ents.FindInSphere( Hitpos, Radius )
local Targets = ents.GetAll()
for k,v in pairs (Targets) do
local epos = v:GetPos()
if Hitpos:Distance(epos) > Radius then
Targets[k] = nil
end
end
local Fragments = math.max(math.floor((FillerMass/FragMass)*ACF.HEFrag),2)
local FragWeight = FragMass/Fragments
local FragVel = (Power*50000/FragWeight/Fragments)^0.5
local FragAera = (FragWeight/7.8)^0.33
local OccFilter = { NoOcc }
local LoopKill = true
while LoopKill and Power > 0 do
LoopKill = false
local PowerSpent = 0
local Iterations = 0
local Damage = {}
local TotalAera = 0
for i,Tar in pairs(Targets) do
Iterations = i
if ( Tar != nil and Power > 0 and not Tar.Exploding ) then
local Type = ACF_Check(Tar)
if ( Type ) then
local Hitat = nil
if Type == "Squishy" then --A little hack so it doesn't check occlusion at the feet of players
local Eyes = Tar:LookupAttachment("eyes")
if Eyes then
Hitat = Tar:GetAttachment( Eyes )
if Hitat then
--Msg("Hitting Eyes\n")
Hitat = Hitat.Pos
else
Hitat = Tar:NearestPoint( Hitpos )
end
end
else
Hitat = Tar:NearestPoint( Hitpos )
end
local Occlusion = {}
Occlusion.start = Hitpos
Occlusion.endpos = Hitat + (Hitat-Hitpos):GetNormalized()*100
Occlusion.filter = OccFilter
Occlusion.mask = MASK_SOLID
local Occ = util.TraceLine( Occlusion )
if ( !Occ.Hit and Hitpos != Hitat ) then
local Hitat = Tar:GetPos()
local Occlusion = {}
Occlusion.start = Hitpos
Occlusion.endpos = Hitat + (Hitat-Hitpos):GetNormalized()*100
Occlusion.filter = OccFilter
Occlusion.mask = MASK_SOLID
Occ = util.TraceLine( Occlusion )
end
if ( Occ.Hit and Occ.Entity:EntIndex() != Tar:EntIndex() ) then
--print("Hit "..Occ.Entity:GetModel())
elseif ( !Occ.Hit and Hitpos != Hitat ) then
--print("No Hit "..Occ.Entity:GetModel())
--print((Hitpos - Hitat):Length())
else
Targets[i] = nil --Remove the thing we just hit from the table so we don't hit it again in the next round
local Table = {}
Table.Ent = Tar
Table.Dist = Hitpos:Distance(Tar:GetPos())
Table.Vec = (Tar:GetPos() - Hitpos):GetNormal()
local Sphere = math.max(4 * 3.1415 * (Table.Dist*2.54 )^2,1) --Surface Aera of the sphere at the range of that prop
Table.Aera = math.min((Tar.ACF.MaxHealth*ACF.Threshold)/Sphere,0.5)*MaxSphere --Project the aera of the prop to the aera of the shadow it projects at the explosion max radius
table.insert(Damage, Table) --Add it to the Damage table so we know to damage it once we tallied everything
TotalAera = TotalAera + Table.Aera
end
else
--print("INVALID: "..Tar:GetClass())
Targets[i] = nil --Target was invalid, so let's ignore it
table.insert( OccFilter , Tar )
end
end
end
--Msg("ACF_HE Damage:\n")
--PrintTable(Damage)
for i,Table in pairs(Damage) do
local Tar = Table.Ent
local AeraFraction = Table.Aera/TotalAera
local PowerFraction = Power * AeraFraction --How much of the total power goes to that prop
--print("ACF_HE Target: "..Tar:GetModel() or "unknown")
--print("ACF_HE Power: "..PowerFraction or "nill")
local Blast = {}
Blast.Momentum = PowerFraction/(math.max(1,Table.Dist/200)^0.05)
Blast.Penetration = PowerFraction^ACF.HEBlastPen*Tar.ACF.MaxHealth
local BlastRes = ACF_Damage ( Tar , Blast , Tar.ACF.MaxHealth , 0 , Inflictor ,0 , Ammo )--Vel is just the speed of sound in air
PowerSpent = PowerSpent + PowerFraction*BlastRes.Loss/2--Removing the energy spent killing props
local FragHit = Fragments * AeraFraction
local FragVel = math.max(FragVel - ( (Table.Dist/FragVel) * FragVel^2 * FragWeight^0.33/10000 )/ACF.DragDiv,0)
local FragKE = ACF_Kinetic( FragVel , FragWeight*FragHit, 1500 )
if FragHit < 0 then
if math.Rand(0,1) > FragHit then FragHit = 1 else FragHit = 0 end
end
local FragRes = ACF_Damage ( Tar , FragKE , (FragWeight/7.8)^0.33*FragHit , 0 , Inflictor , 0, Ammo )
if (BlastRes and BlastRes.Kill) or (FragRes and FragRes.Kill) then
local Debris = ACF_HEKill( Tar , Table.Vec , PowerFraction )
table.insert( OccFilter , Debris ) --Add the debris created to the ignore so we don't hit it in other rounds
LoopKill = true
else
local phys = Tar:GetPhysicsObject()
if (phys:IsValid()) then
phys:ApplyForceOffset( Table.Vec * PowerFraction * 100 , Hitpos ) --Assuming about a tenth of the energy goes to propelling the target prop (Power in KJ * 1000 to get J then divided by 10)
end
end
end
Power = math.max(Power - PowerSpent,0)
end
end
function ACF_Spall( HitPos , HitVec , HitMask , KE , Caliber , Armour , Inflictor )
--if(!ACF.Spalling) then
if true then -- Folks say it's black magic and it kills their firstborns. So I had to disable it with more powerful magic.
return
end
local TotalWeight = 3.1416*(Caliber/2)^2 * Armour * 0.00079
local Spall = math.max(math.floor(Caliber*ACF.KEtoSpall),2)
local SpallWeight = TotalWeight/Spall
local SpallVel = (KE*2000/SpallWeight)^0.5/Spall
local SpallAera = (SpallWeight/7.8)^0.33
local SpallEnergy = ACF_Kinetic( SpallVel , SpallWeight, 600 )
--print(SpallWeight)
--print(SpallVel)
for i = 1,Spall do
local SpallTr = { }
SpallTr.start = HitPos
SpallTr.endpos = HitPos + (HitVec:GetNormalized()+VectorRand()/2):GetNormalized()*SpallVel
SpallTr.filter = HitMask
ACF_SpallTrace( HitVec , SpallTr , SpallEnergy , SpallAera , Inflictor )
end
end
function ACF_SpallTrace( HitVec , SpallTr , SpallEnergy , SpallAera , Inflictor )
local SpallRes = util.TraceLine(SpallTr)
if SpallRes.Hit and ACF_Check( SpallRes.Entity ) then
local Angle = ACF_GetHitAngle( SpallRes.HitNormal , HitVec )
local HitRes = ACF_Damage( SpallRes.Entity , SpallEnergy , SpallAera , Angle , Inflictor, 0 ) --DAMAGE !!
if HitRes.Kill then
ACF_APKill( SpallRes.Entity , HitVec:GetNormalized() , SpallEnergy.Kinetic )
end
if HitRes.Overkill > 0 then
table.insert( SpallTr.filter , Target ) --"Penetrate" (Ingoring the prop for the retry trace)
SpallEnergy.Penetration = SpallEnergy.Penetration*(1-HitRes.Loss)
SpallEnergy.Momentum = SpallEnergy.Momentum*(1-HitRes.Loss)
ACF_SpallTrace( HitVec , SpallTr , SpallEnergy , SpallAera , Inflictor )
end
end
end
function ACF_RoundImpact( Bullet, Speed, Energy, Target, HitPos, HitNormal , Bone ) --Simulate a round impacting on a prop
local Angle = ACF_GetHitAngle( HitNormal , Bullet["Flight"] )
local Ricochet = 0
local MinAngle = math.min(Bullet["Ricochet"] - Speed/39.37/15,89) --Making the chance of a ricochet get higher as the speeds increase
if Angle > math.random(MinAngle,90) and Angle < 89.9 then --Checking for ricochet
Ricochet = (Angle/100) --If ricocheting, calculate how much of the energy is dumped into the plate and how much is carried by the ricochet
Energy.Penetration = Energy.Penetration - Energy.Penetration*Ricochet/4 --Ricocheting can save plates that would theorically get penetrated, can add up to 1/4 rating
end
local HitRes = ACF_Damage ( Target , Energy , Bullet["PenAera"] , Angle , Bullet["Owner"] , Bone, Bullet["Gun"] ) --DAMAGE !!
ACF_KEShove(Target, HitPos, Bullet["Flight"]:GetNormal(), Energy.Kinetic*HitRes.Loss*1000*Bullet["ShovePower"] )
if HitRes.Kill then
local Debris = ACF_APKill( Target , (Bullet["Flight"]):GetNormalized() , Energy.Kinetic )
table.insert( Bullet["Filter"] , Debris )
end
HitRes.Ricochet = false
if Ricochet > 0 then
Bullet["Pos"] = HitPos
Bullet["Flight"] = (Bullet["Flight"]:GetNormalized() + HitNormal*(1-Ricochet+0.05) + VectorRand()*0.05):GetNormalized() * Speed * Ricochet
HitRes.Ricochet = true
end
return HitRes
end
function ACF_PenetrateGround( Bullet, Energy, HitPos )
local MaxDig = ((Energy.Penetration/Bullet["PenAera"])*ACF.KEtoRHA/ACF.GroundtoRHA)/25.4
local CurDig = 0
local DigStep = math.min(50,MaxDig)
for i = 1,MaxDig/DigStep do
--Msg("Step : ")
--print(i)
CurDig = DigStep*i
local DigTr = { }
DigTr.start = HitPos + (Bullet["Flight"]):GetNormalized()*CurDig
DigTr.endpos = HitPos
DigTr.filter = Bullet["Filter"]
DigTr.mask = 16395
local DigRes = util.TraceLine(DigTr) --Trace to see if it will hit anything
if DigRes.Hit then
if DigRes.Fraction > 0.01 and DigRes.Fraction < 0.99 then
local Powerloss = (MaxDig - (CurDig - DigStep*DigRes.Fraction))/MaxDig
--print(Powerloss)
Bullet["Flight"] = Bullet["Flight"] * Powerloss
--Msg("Penetrated the wall\n")
Bullet["Pos"] = DigRes.HitPos
return true
else
return nil
end
else
--Msg("Didn't Hit\n")
end
end
return nil
end
function ACF_KEShove(Target, Pos, Vec, KE )
local phys = Target:GetPhysicsObject()
if (Target:GetParent():IsValid()) then
phys = Target:GetParent():GetPhysicsObject()
end
if (phys:IsValid()) then
phys:ApplyForceOffset( Vec:GetNormal() * KE, Pos )
end
end
function ACF_HEKill( Entity , HitVector , Energy )
--print("ACF_HEKill ent: ".. Entity:GetModel() or "unknown")
--print("ACF_HEKill Energy "..Energy or "nill")
local obj = Entity:GetPhysicsObject()
local grav = true
local mass = nil
if obj:IsValid() and ISSITP then
grav = obj:IsGravityEnabled()
mass = obj:GetMass()
end
constraint.RemoveAll( Entity )
Entity:Remove()
local Debris = ents.Create( "Debris" )
Debris:SetModel( Entity:GetModel() )
Debris:SetAngles( Entity:GetAngles() )
Debris:SetPos( Entity:GetPos() )
Debris:SetMaterial("models/props_wasteland/metal_tram001a")
Debris:Spawn()
Debris:Ignite(60,0)
Debris:Activate()
local phys = Debris:GetPhysicsObject()
if (phys:IsValid()) then
phys:ApplyForceOffset( HitVector:GetNormal() * Energy * 350 , Debris:GetPos()+VectorRand()*20 )
phys:EnableGravity( grav )
if(mass != nil) then
phys:SetMass(mass)
end
end
return Debris
end
function ACF_APKill( Entity , HitVector , Power )
constraint.RemoveAll( Entity )
Entity:Remove()
local Debris = ents.Create( "Debris" )
Debris:SetModel( Entity:GetModel() )
Debris:SetAngles( Entity:GetAngles() )
Debris:SetPos( Entity:GetPos() )
Debris:SetMaterial(Entity:GetMaterial())
Debris:SetColor(Color(120,120,120,255))
Debris:Spawn()
Debris:Activate()
local BreakEffect = EffectData()
BreakEffect:SetOrigin( Entity:GetPos() )
BreakEffect:SetScale( 20 )
util.Effect( "WheelDust", BreakEffect )
local phys = Debris:GetPhysicsObject()
if (phys:IsValid()) then
phys:ApplyForceOffset( HitVector:GetNormal() * Power * 350 , Debris:GetPos()+VectorRand()*20 )
end
return Debris
end
function ACF_AmmoExplosion( Origin , Pos )
--old method, purely based on ammo count
--local HEWeight = Origin.Ammo/2
--treats propellant as 1x the explosive power of TNT
--local BoomPower = Origin.BulletData["BoomPower"]
--if not BoomPower then BoomPower = 0.002 end --make sure refills are explosive
--local HEWeight = BoomPower * Origin.Ammo
--treats propellant as ~1/8x the explosive power of TNT
local HEContent, PropContent
if Origin.RoundType == "Refill" then
HEContent = 0.001
PropContent = 0.001
else
HEContent = Origin.BulletData["FillerMass"] or 0
PropContent = Origin.BulletData["PropMass"] or 0
end
local LastHE = 0
local HEWeight = (HEContent+PropContent*(ACF.PBase/ACF.HEPower))*Origin.Ammo
local Power = HEWeight * ACF.HEPower --Power in KiloJoules of the filler mass of TNT
local Radius = (HEWeight)^0.33*8*39.37 --Scalling law found on the net, based on 1PSI overpressure from 1 kg of TNT at 15m
local Search = true
local Filter = {Origin}
local Inflictor = nil
if( Origin.Inflictor ) then
Inflictor = Origin.Inflictor
end
Origin.IsExplosive = false
Origin.Exploding = true
Origin:Remove()
while Search do
for key,Found in pairs(ents.FindInSphere(Pos, Radius)) do
if Found.IsExplosive and not Found.Exploding then
local Hitat = Found:NearestPoint( Pos )
local Occlusion = {}
Occlusion.start = Pos
Occlusion.endpos = Hitat
Occlusion.filter = Filter
local Occ = util.TraceLine( Occlusion )
if ( Occ.Fraction == 0 ) then
table.insert(Filter,Occ.Entity)
local Occlusion = {}
Occlusion.start = Pos
Occlusion.endpos = Hitat
Occlusion.filter = Filter
Occ = util.TraceLine( Occlusion )
--print("Ignoring nested prop")
end
if ( Occ.Hit and Occ.Entity:EntIndex() != Found.Entity:EntIndex() ) then
--Msg("Target Occluded\n")
else
local FoundHE, FoundPropel
if Found.RoundType == "Refill" then
FoundHE = 0.001
FoundPropel = 0.001
else
FoundHE = Found.BulletData["FillerMass"] or 0
FoundPropel = Found.BulletData["PropMass"] or 0
end
local FoundHEWeight = (FoundHE+FoundPropel*(ACF.PBase/ACF.HEPower))*Found.Ammo
--local FoundHE = Found.Ammo*2
HEWeight = HEWeight + FoundHEWeight
Found.IsExplosive = false
Found.DamageAction = false
Found.KillAction = false
Found.Exploding = true
table.insert(Filter,Found)
Found:Remove()
end
end
end
if HEWeight > LastHE then
Search = true
LastHE = HEWeight
Radius = (HEWeight)^0.33*8*39.37
else
Search = false
end
end
ACF_HE( Pos , Vector(0,0,1) , HEWeight , HEWeight*0.5 , Inflictor , Origin, Origin.Entity )
local Flash = EffectData()
Flash:SetOrigin( Pos )
Flash:SetNormal( Vector(0,0,-1) )
Flash:SetRadius( math.max( Radius, 1 ) ) --Radius of the smoke
util.Effect( "ACF_Scaled_Explosion", Flash )
end
function ACF_GetHitAngle( HitNormal , HitVector )
HitVector = HitVector*-1
local Angle = math.min(math.deg(math.acos(HitNormal:Dot( HitVector:GetNormal() ) ) ),89.999 )
--Msg("Angle : " ..Angle.. "\n")
return Angle
end