ACF_CustomMod/lua/acf/server/sv_acfbase.lua

344 lines
12 KiB
Lua

local UpdateIndex = 0
function ACF_UpdateVisualHealth(Entity)
if Entity.ACF.PrHealth == Entity.ACF.Health then return end
if not ACF_HealthUpdateList then
ACF_HealthUpdateList = {}
timer.Create("ACF_HealthUpdateList", 1, 1, function() // We should send things slowly to not overload traffic.
local Table = {}
for k,v in pairs(ACF_HealthUpdateList) do
if IsValid( v ) then
table.insert(Table,{ID = v:EntIndex(), Health = v.ACF.Health, MaxHealth = v.ACF.MaxHealth})
end
end
net.Start("ACF_RenderDamage")
net.WriteTable(Table)
net.Broadcast()
ACF_HealthUpdateList = nil
end)
end
table.insert(ACF_HealthUpdateList, Entity)
end
function ACF_Activate ( Entity , Recalc )
--Density of steel = 7.8g cm3 so 7.8kg for a 1mx1m plate 1m thick
if Entity.SpecialHealth then
Entity:ACF_Activate( Recalc )
return
end
Entity.ACF = Entity.ACF or {}
local Count
local PhysObj = Entity:GetPhysicsObject()
if PhysObj:GetMesh() then Count = #PhysObj:GetMesh() end
if PhysObj:IsValid() and Count and Count>100 then
if not Entity.ACF.Aera then
Entity.ACF.Aera = (PhysObj:GetSurfaceArea() * 6.45) * 0.52505066107
end
--if not Entity.ACF.Volume then
-- Entity.ACF.Volume = (PhysObj:GetVolume() * 16.38)
--end
else
local Size = Entity.OBBMaxs(Entity) - Entity.OBBMins(Entity)
if not Entity.ACF.Aera then
Entity.ACF.Aera = ((Size.x * Size.y)+(Size.x * Size.z)+(Size.y * Size.z)) * 6.45
end
--if not Entity.ACF.Volume then
-- Entity.ACF.Volume = Size.x * Size.y * Size.z * 16.38
--end
end
Entity.ACF.Ductility = Entity.ACF.Ductility or 0
local Area = (Entity.ACF.Aera+Entity.ACF.Aera*math.Clamp(Entity.ACF.Ductility,-0.8,0.8))
local Armour = Entity:GetPhysicsObject():GetMass()*1000 / Area / 0.78 --So we get the equivalent thickness of that prop in mm if all it's weight was a steel plate
local Health = Area/ACF.Threshold --Setting the threshold of the prop aera gone
local Percent = 1
if Recalc and Entity.ACF.Health and Entity.ACF.MaxHealth then
Percent = Entity.ACF.Health/Entity.ACF.MaxHealth
end
Entity.ACF.Health = Health * Percent
Entity.ACF.MaxHealth = Health
Entity.ACF.Armour = Armour * (0.5 + Percent/2)
Entity.ACF.MaxArmour = Armour * ACF.ArmorMod
Entity.ACF.Type = nil
Entity.ACF.Mass = PhysObj:GetMass()
--Entity.ACF.Density = (PhysObj:GetMass()*1000)/Entity.ACF.Volume
if Entity:IsPlayer() || Entity:IsNPC() then
Entity.ACF.Type = "Squishy"
elseif Entity:IsVehicle() then
Entity.ACF.Type = "Vehicle"
else
Entity.ACF.Type = "Prop"
end
--print(Entity.ACF.Health)
end
function ACF_Check ( Entity )
if ( IsValid(Entity) ) then
if ( Entity:GetPhysicsObject():IsValid() and !Entity:IsWorld() and !Entity:IsWeapon() ) then
local Class = Entity:GetClass()
if ( Class != "gmod_ghost" and Class != "debris" and Class != "prop_ragdoll" and not string.find( Class , "func_" ) ) then
if !Entity.ACF then
ACF_Activate( Entity )
elseif Entity.ACF.Mass != Entity:GetPhysicsObject():GetMass() then
ACF_Activate( Entity , true )
end
--print("ACF_Check "..Entity.ACF.Type)
return Entity.ACF.Type
end
end
end
return false
end
function ACF_Damage ( Entity , Energy , FrAera , Angle , Inflictor , Bone, Gun )
local Activated = ACF_Check( Entity )
local CanDo = hook.Run("ACF_BulletDamage", Activated, Entity, Energy, FrAera, Angle, Inflictor, Bone, Gun )
if CanDo == false then
return { Damage = 0, Overkill = 0, Loss = 0, Kill = false }
end
if Entity.SpecialDamage then
return Entity:ACF_OnDamage( Entity , Energy , FrAera , Angle , Inflictor , Bone )
elseif Activated == "Prop" then
return ACF_PropDamage( Entity , Energy , FrAera , Angle , Inflictor , Bone )
elseif Activated == "Vehicle" then
return ACF_VehicleDamage( Entity , Energy , FrAera , Angle , Inflictor , Bone, Gun )
elseif Activated == "Squishy" then
return ACF_SquishyDamage( Entity , Energy , FrAera , Angle , Inflictor , Bone, Gun )
end
end
function ACF_CalcDamage( Entity , Energy , FrAera , Angle )
local Armour = Entity.ACF.Armour/math.abs( math.cos(math.rad(Angle)) ) --Calculate Line Of Sight thickness of the armour
local Structure = Entity.ACF.Density --Structural strengh of the material, derived from prop density, denser stuff is more vulnerable (Density is different than armour, calculated off real volume)
local MaxPenetration = (Energy.Penetration / FrAera) * ACF.KEtoRHA --Let's see how deep the projectile penetrates ( Energy = Kinetic Energy, FrAera = Frontal aera in cm2 )
--print(MaxPenetration)
local Penetration = math.min( MaxPenetration , Armour ) --Clamp penetration to the armour thickness
local HitRes = {}
--BNK Stuff
local dmul = 1
if (ISBNK) then
local cvar = GetConVarNumber("sbox_godmode")
if (cvar == 1) then
dmul = 0
end
end
--SITP Stuff
local var = 1
if (ISSITP) then
if(!Entity.sitp_spacetype) then
Entity.sitp_spacetype = "space"
end
if(Entity.sitp_spacetype != "space" and Entity.sitp_spacetype != "planet") then
var = 0
end
end
HitRes.Damage = var * dmul * (Penetration/Armour)^2 * FrAera -- This is the volume of the hole caused by our projectile
--print("ACF_CalcDamage Damage "..HitRes.Damage)
HitRes.Overkill = (MaxPenetration - Penetration)
HitRes.Loss = Penetration/MaxPenetration
return HitRes
end
function ACF_PropDamage( Entity , Energy , FrAera , Angle , Inflictor , Bone )
local HitRes = ACF_CalcDamage( Entity , Energy , FrAera , Angle )
HitRes.Kill = false
if HitRes.Damage >= Entity.ACF.Health then
HitRes.Kill = true
else
Entity.ACF.Health = Entity.ACF.Health - HitRes.Damage
Entity.ACF.Armour = Entity.ACF.MaxArmour * (0.5 + Entity.ACF.Health/Entity.ACF.MaxHealth/2) --Simulating the plate weakening after a hit
if Entity.ACF.PrHealth then
ACF_UpdateVisualHealth(Entity)
end
Entity.ACF.PrHealth = Entity.ACF.Health
end
return HitRes
end
function ACF_VehicleDamage( Entity , Energy , FrAera , Angle , Inflictor , Bone, Gun )
local HitRes = ACF_CalcDamage( Entity , Energy , FrAera , Angle )
local Driver = Entity:GetDriver()
if Driver:IsValid() then
--if Ammo == true then
-- Driver.KilledByAmmo = true
--end
Driver:TakeDamage( HitRes.Damage*40 , Inflictor, Gun )
--if Ammo == true then
-- Driver.KilledByAmmo = false
--end
end
HitRes.Kill = false
if HitRes.Damage >= Entity.ACF.Health then
HitRes.Kill = true
else
Entity.ACF.Health = Entity.ACF.Health - HitRes.Damage
Entity.ACF.Armour = Entity.ACF.Armour * (0.5 + Entity.ACF.Health/Entity.ACF.MaxHealth/2) --Simulating the plate weakening after a hit
end
return HitRes
end
function ACF_SquishyDamage( Entity , Energy , FrAera , Angle , Inflictor , Bone, Gun)
local Size = Entity:BoundingRadius()
local Mass = Entity:GetPhysicsObject():GetMass()
local HitRes = {}
local Damage = 0
local Target = {ACF = {Armour = 0.1}} --We create a dummy table to pass armour values to the calc function
if (Bone) then
if ( Bone == 1 ) then --This means we hit the head
Target.ACF.Armour = Mass*0.02 --Set the skull thickness as a percentage of Squishy weight, this gives us 2mm for a player, about 22mm for an Antlion Guard. Seems about right
HitRes = ACF_CalcDamage( Target , Energy , FrAera , Angle ) --This is hard bone, so still sensitive to impact angle
Damage = HitRes.Damage*20
if HitRes.Overkill > 0 then --If we manage to penetrate the skull, then MASSIVE DAMAGE
Target.ACF.Armour = Size*0.25*0.01 --A quarter the bounding radius seems about right for most critters head size
HitRes = ACF_CalcDamage( Target , Energy , FrAera , 0 )
Damage = Damage + HitRes.Damage*100
end
Target.ACF.Armour = Mass*0.065 --Then to check if we can get out of the other side, 2x skull + 1x brains
HitRes = ACF_CalcDamage( Target , Energy , FrAera , Angle )
Damage = Damage + HitRes.Damage*20
elseif ( Bone == 0 or Bone == 2 or Bone == 3 ) then --This means we hit the torso. We are assuming body armour/tough exoskeleton/zombie don't give fuck here, so it's tough
Target.ACF.Armour = Mass*0.08 --Set the armour thickness as a percentage of Squishy weight, this gives us 8mm for a player, about 90mm for an Antlion Guard. Seems about right
HitRes = ACF_CalcDamage( Target , Energy , FrAera , Angle ) --Armour plate,, so sensitive to impact angle
Damage = HitRes.Damage*5
if HitRes.Overkill > 0 then
Target.ACF.Armour = Size*0.5*0.02 --Half the bounding radius seems about right for most critters torso size
HitRes = ACF_CalcDamage( Target , Energy , FrAera , 0 )
Damage = Damage + HitRes.Damage*50 --If we penetrate the armour then we get into the important bits inside, so DAMAGE
end
Target.ACF.Armour = Mass*0.185 --Then to check if we can get out of the other side, 2x armour + 1x guts
HitRes = ACF_CalcDamage( Target , Energy , FrAera , Angle )
elseif ( Bone == 4 or Bone == 5 ) then --This means we hit an arm or appendage, so ormal damage, no armour
Target.ACF.Armour = Size*0.2*0.02 --A fitht the bounding radius seems about right for most critters appendages
HitRes = ACF_CalcDamage( Target , Energy , FrAera , 0 ) --This is flesh, angle doesn't matter
Damage = HitRes.Damage*30 --Limbs are somewhat less important
elseif ( Bone == 6 or Bone == 7 ) then
Target.ACF.Armour = Size*0.2*0.02 --A fitht the bounding radius seems about right for most critters appendages
HitRes = ACF_CalcDamage( Target , Energy , FrAera , 0 ) --This is flesh, angle doesn't matter
Damage = HitRes.Damage*30 --Limbs are somewhat less important
elseif ( Bone == 10 ) then --This means we hit a backpack or something
Target.ACF.Armour = Size*0.1*0.02 --Arbitrary size, most of the gear carried is pretty small
HitRes = ACF_CalcDamage( Target , Energy , FrAera , 0 ) --This is random junk, angle doesn't matter
Damage = HitRes.Damage*2 --Damage is going to be fright and shrapnel, nothing much
else --Just in case we hit something not standard
Target.ACF.Armour = Size*0.2*0.02
HitRes = ACF_CalcDamage( Target , Energy , FrAera , 0 )
Damage = HitRes.Damage*30
end
else --Just in case we hit something not standard
Target.ACF.Armour = Size*0.2*0.02
HitRes = ACF_CalcDamage( Target , Energy , FrAera , 0 )
Damage = HitRes.Damage*10
end
local dmul = 2.5
--BNK stuff
if (ISBNK) then
if(Entity.freq and Inflictor.freq) then
if (Entity != Inflictor) and (Entity.freq == Inflictor.freq) then
dmul = 0
end
end
end
--SITP stuff
local var = 1
if(!Entity.sitp_spacetype) then
Entity.sitp_spacetype = "space"
end
if(Entity.sitp_spacetype == "homeworld") then
var = 0
end
--if Ammo == true then
-- Entity.KilledByAmmo = true
--end
Entity:TakeDamage( Damage * dmul * var, Inflictor, Gun )
--if Ammo == true then
-- Entity.KilledByAmmo = false
--end
HitRes.Kill = false
--print(Damage)
--print(Bone)
return HitRes
end
----------------------------------------------------------
-- Returns a table of all physically connected entities
-- ignoring ents attached by only nocollides
----------------------------------------------------------
function ACF_GetAllPhysicalConstraints( ent, ResultTable )
local ResultTable = ResultTable or {}
if not IsValid( ent ) then return end
if ResultTable[ ent ] then return end
ResultTable[ ent ] = ent
local ConTable = constraint.GetTable( ent )
for k, con in ipairs( ConTable ) do
-- skip shit that is attached by a nocollide
if con.Type == "NoCollide" then continue end
for EntNum, Ent in pairs( con.Entity ) do
ACF_GetAllPhysicalConstraints( Ent.Entity, ResultTable )
end
end
return ResultTable
end