-- 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