diff --git a/lua/acf/damage/damage_cl.lua b/lua/acf/damage/damage_cl.lua index 4f222271..b935b892 100644 --- a/lua/acf/damage/damage_cl.lua +++ b/lua/acf/damage/damage_cl.lua @@ -95,178 +95,4 @@ do end end end) -end - -do -- Debris Effects ------------------------ - local Effects = ACF.Utilities.Effects - local AllowDebris = GetConVar("acf_debris") - local CollideAll = GetConVar("acf_debris_collision") - local DebrisLife = GetConVar("acf_debris_lifetime") - local GibMult = GetConVar("acf_debris_gibmultiplier") - local GibLife = GetConVar("acf_debris_giblifetime") - local GibModel = "models/gibs/metal_gib%s.mdl" - - local function Particle(Entity, Effect) - return CreateParticleSystem(Entity, Effect, PATTACH_ABSORIGIN_FOLLOW) - end - - local function FadeAway(Entity) - if not IsValid(Entity) then return end - - local Smoke = Entity.SmokeParticle - local Ember = Entity.EmberParticle - - Entity:SetRenderMode(RENDERMODE_TRANSCOLOR) - Entity:SetRenderFX(kRenderFxFadeSlow) -- NOTE: Not synced to CurTime() - Entity:CallOnRemove("ACF_Debris_Fade", function() - Entity:StopAndDestroyParticles() - end) - - if IsValid(Smoke) then Smoke:StopEmission() end - if IsValid(Ember) then Ember:StopEmission() end - - timer.Simple(5, function() - if not IsValid(Entity) then return end - - Entity:Remove() - end) - end - - local function Ignite(Entity, Lifetime, IsGib) - if IsGib then - Particle(Entity, "burning_gib_01") - - timer.Simple(Lifetime * 0.2, function() - if not IsValid(Entity) then return end - - Entity:StopParticlesNamed("burning_gib_01") - end) - else - Entity.SmokeParticle = Particle(Entity, "smoke_small_01b") - - Particle(Entity, "env_fire_small_smoke") - - timer.Simple(Lifetime * 0.4, function() - if not IsValid(Entity) then return end - - Entity:StopParticlesNamed("env_fire_small_smoke") - end) - end - end - - local function CreateDebris(Data) - local Debris = ents.CreateClientProp(Data.Model) - - if not IsValid(Debris) then return end - - local Lifetime = DebrisLife:GetFloat() * math.Rand(0.5, 1) - - Debris:SetPos(Data.Position) - Debris:SetAngles(Data.Angles) - Debris:SetColor(Data.Color) - Debris:SetMaterial(Data.Material) - - if not CollideAll:GetBool() then - Debris:SetCollisionGroup(COLLISION_GROUP_WORLD) - end - - Debris:Spawn() - - Debris.EmberParticle = Particle(Debris, "embers_medium_01") - - if Data.Ignite and math.Rand(0, 0.5) < ACF.DebrisIgniteChance then - Ignite(Debris, Lifetime) - else - Debris.SmokeParticle = Particle(Debris, "smoke_exhaust_01a") - end - - local PhysObj = Debris:GetPhysicsObject() - - if IsValid(PhysObj) then - PhysObj:ApplyForceOffset(Data.Normal * Data.Power, Data.Position + VectorRand() * 20) - end - - timer.Simple(Lifetime, function() - FadeAway(Debris) - end) - - return Debris - end - - local function CreateGib(Data, Min, Max) - local Gib = ents.CreateClientProp(GibModel:format(math.random(1, 5))) - - if not IsValid(Gib) then return end - - local Lifetime = GibLife:GetFloat() * math.Rand(0.5, 1) - local Offset = ACF.RandomVector(Min, Max) - - Offset:Rotate(Data.Angles) - - Gib:SetPos(Data.Position + Offset) - Gib:SetAngles(AngleRand(-180, 180)) - Gib:SetModelScale(math.Rand(0.5, 2)) - Gib:SetMaterial(Data.Material) - Gib:SetColor(Data.Color) - Gib:Spawn() - - Gib.SmokeParticle = Particle(Gib, "smoke_gib_01") - - if math.random() < ACF.DebrisIgniteChance then - Ignite(Gib, Lifetime, true) - end - - local PhysObj = Gib:GetPhysicsObject() - - if IsValid(PhysObj) then - PhysObj:ApplyForceOffset(Data.Normal * Data.Power, Gib:GetPos() + VectorRand() * 20) - end - - timer.Simple(Lifetime, function() - FadeAway(Gib) - end) - - return true - end - - net.Receive("ACF_Debris", function() - local Data = util.JSONToTable(net.ReadString()) - - if not AllowDebris:GetBool() then return end - - local Debris = CreateDebris(Data) - - if IsValid(Debris) then - local Multiplier = GibMult:GetFloat() - local Radius = Debris:BoundingRadius() - local Min = Debris:OBBMins() - local Max = Debris:OBBMaxs() - - if Data.CanGib and Multiplier > 0 then - local GibCount = math.Clamp(Radius * 0.1, 1, math.max(10 * Multiplier, 1)) - - for _ = 1, GibCount do - if not CreateGib(Data, Min, Max) then - break - end - end - end - end - - local EffectTable = { - Origin = Data.Position, -- TODO: Change this to the hit vector, but we need to redefine HitVec as HitNorm - Scale = 20, - } - - Effects.CreateEffect("cball_explode", EffectTable) - end) - - game.AddParticles("particles/fire_01.pcf") - - PrecacheParticleSystem("burning_gib_01") - PrecacheParticleSystem("env_fire_small_smoke") - PrecacheParticleSystem("smoke_gib_01") - PrecacheParticleSystem("smoke_exhaust_01a") - PrecacheParticleSystem("smoke_small_01b") - PrecacheParticleSystem("embers_medium_01") -end ----------------------------------------- +end \ No newline at end of file diff --git a/lua/acf/damage/debris_cl.lua b/lua/acf/damage/debris_cl.lua new file mode 100644 index 00000000..62a2fb44 --- /dev/null +++ b/lua/acf/damage/debris_cl.lua @@ -0,0 +1,220 @@ +local ACF = ACF + +local Effects = ACF.Utilities.Effects +local AllowDebris = GetConVar("acf_debris") +local CollideAll = GetConVar("acf_debris_collision") +local DebrisLife = GetConVar("acf_debris_lifetime") +local GibMult = GetConVar("acf_debris_gibmultiplier") +local GibLife = GetConVar("acf_debris_giblifetime") +local GibModel = "models/gibs/metal_gib%s.mdl" + +local function Particle(Entity, Effect) + return CreateParticleSystem(Entity, Effect, PATTACH_ABSORIGIN_FOLLOW) +end + +local function FadeAway(Entity) + if not IsValid(Entity) then return end + + local Smoke = Entity.SmokeParticle + local Ember = Entity.EmberParticle + + Entity:SetRenderMode(RENDERMODE_TRANSCOLOR) + Entity:SetRenderFX(kRenderFxFadeSlow) -- NOTE: Not synced to CurTime() + Entity:CallOnRemove("ACF_Debris_Fade", function() + Entity:StopAndDestroyParticles() + end) + + if IsValid(Smoke) then Smoke:StopEmission() end + if IsValid(Ember) then Ember:StopEmission() end + + timer.Simple(5, function() + if not IsValid(Entity) then return end + + Entity:Remove() + end) +end + +local function Ignite(Entity, Lifetime, IsGib) + if IsGib then + Particle(Entity, "burning_gib_01") + + timer.Simple(Lifetime * 0.2, function() + if not IsValid(Entity) then return end + + Entity:StopParticlesNamed("burning_gib_01") + end) + else + Entity.SmokeParticle = Particle(Entity, "smoke_small_01b") + + Particle(Entity, "env_fire_small_smoke") + + timer.Simple(Lifetime * 0.4, function() + if not IsValid(Entity) then return end + + Entity:StopParticlesNamed("env_fire_small_smoke") + end) + end +end + +local function CreateDebris(Model, Position, Angles, Material, Color, Normal, Power, ShouldIgnite) + local Debris = ents.CreateClientProp(Model) + + if not IsValid(Debris) then return end + + local Lifetime = DebrisLife:GetFloat() * math.Rand(0.5, 1) + + Debris:SetPos(Position) + Debris:SetAngles(Angles) + Debris:SetMaterial(Material) + Debris:SetColor(Color) + + if not CollideAll:GetBool() then + Debris:SetCollisionGroup(COLLISION_GROUP_WORLD) + end + + Debris:Spawn() + + Debris.EmberParticle = Particle(Debris, "embers_medium_01") + + if ShouldIgnite and math.Rand(0, 0.5) < ACF.DebrisIgniteChance then + Ignite(Debris, Lifetime) + else + Debris.SmokeParticle = Particle(Debris, "smoke_exhaust_01a") + end + + local PhysObj = Debris:GetPhysicsObject() + + if IsValid(PhysObj) then + PhysObj:ApplyForceOffset(Normal * Power, Position + VectorRand() * 20) + end + + timer.Simple(Lifetime, function() + FadeAway(Debris) + end) + + return Debris +end + +local function CreateGib(Position, Angles, Material, Color, Normal, Power, Min, Max) + local Gib = ents.CreateClientProp(GibModel:format(math.random(1, 5))) + + if not IsValid(Gib) then return end + + local Lifetime = GibLife:GetFloat() * math.Rand(0.5, 1) + local Offset = ACF.RandomVector(Min, Max) + + Offset:Rotate(Angles) + + Gib:SetPos(Position + Offset) + Gib:SetAngles(AngleRand(-180, 180)) + Gib:SetModelScale(math.Rand(0.5, 2)) + Gib:SetMaterial(Material) + Gib:SetColor(Color) + Gib:Spawn() + + Gib.SmokeParticle = Particle(Gib, "smoke_gib_01") + + if math.random() < ACF.DebrisIgniteChance then + Ignite(Gib, Lifetime, true) + end + + local PhysObj = Gib:GetPhysicsObject() + + if IsValid(PhysObj) then + PhysObj:ApplyForceOffset(Normal * Power, Gib:GetPos() + VectorRand() * 20) + end + + timer.Simple(Lifetime, function() + FadeAway(Gib) + end) + + return true +end + +function ACF.CreateDebris(Model, Position, Angles, Material, Color, Normal, Power, CanGib, Ignite) + if not AllowDebris:GetBool() then return end + if not Model then return end + + local Debris = CreateDebris(Model, Position, Angles, Material, Color, Normal, Power, CanGib, Ignite) + + if IsValid(Debris) then + local Multiplier = GibMult:GetFloat() + local Radius = Debris:BoundingRadius() + local Min = Debris:OBBMins() + local Max = Debris:OBBMaxs() + + if CanGib and Multiplier > 0 then + local GibCount = math.Clamp(Radius * 0.1, 1, math.max(10 * Multiplier, 1)) + + for _ = 1, GibCount do + if not CreateGib(Position, Angles, Material, Color, Normal, Power, Min, Max) then + break + end + end + end + end + + local EffectTable = { + Origin = Position, -- TODO: Change this to the hit vector, but we need to redefine HitVec as HitNorm + Scale = 20, + } + + Effects.CreateEffect("cball_explode", EffectTable) +end + +local EntData = {} + +-- Store data of potentially ACF-killed entities for debris use, then remove from cache soon after +hook.Add("EntityRemoved", "ACF_Debris_TrackEnts", function(Ent) + local EntID = Ent:EntIndex() + if EntID == -1 then return end + + EntData[EntID] = { + Model = Ent:GetModel(), + Material = Ent:GetMaterial(), + Color = Ent:GetColor(), + Position = Ent:GetPos(), + Angles = Ent:GetAngles(), + } + + timer.Simple(2, function() + if not EntData[EntID] then return end + EntData[EntID] = nil + end) +end) + +net.Receive("ACF_Debris", function() + local EntID = net.ReadUInt(13) + local Normal = Vector(net.ReadInt(8) / 100, net.ReadInt(8) / 100, net.ReadInt(8) / 100) + local Power = net.ReadUInt(16) + local CanGib = net.ReadBool() + local Ignite = net.ReadBool() + + timer.Simple(0, function() + local EntInfo = EntData[EntID] + local NewColor = EntInfo.Color:ToVector() * math.Rand(0.3, 0.6) + + ACF.CreateDebris( + EntInfo.Model, + EntInfo.Position, + EntInfo.Angles, + EntInfo.Material, + NewColor:ToColor(), + Normal, + Power, + CanGib, + Ignite + ) + + EntData[EntID] = nil + end) +end) + +game.AddParticles("particles/fire_01.pcf") + +PrecacheParticleSystem("burning_gib_01") +PrecacheParticleSystem("env_fire_small_smoke") +PrecacheParticleSystem("smoke_gib_01") +PrecacheParticleSystem("smoke_exhaust_01a") +PrecacheParticleSystem("smoke_small_01b") +PrecacheParticleSystem("embers_medium_01") \ No newline at end of file diff --git a/lua/acf/damage/debris_sv.lua b/lua/acf/damage/debris_sv.lua index 51305476..a56c0394 100644 --- a/lua/acf/damage/debris_sv.lua +++ b/lua/acf/damage/debris_sv.lua @@ -7,10 +7,16 @@ local Queue = {} local function SendQueue() for Entity, Data in pairs(Queue) do - local JSON = util.TableToJSON(Data) - net.Start("ACF_Debris") - net.WriteString(JSON) + + net.WriteUInt(Data.ID, 13) + net.WriteInt(Data.Normal.x * 100, 8) + net.WriteInt(Data.Normal.y * 100, 8) + net.WriteInt(Data.Normal.z * 100, 8) + net.WriteUInt(Data.Power, 16) + net.WriteBool(Data.CanGib) + net.WriteBool(Data.Ignite) + net.SendPVS(Data.Position) Queue[Entity] = nil @@ -21,19 +27,13 @@ local function DebrisNetter(Entity, Normal, Power, CanGib, Ignite) if not ACF.GetServerBool("CreateDebris") then return end if Queue[Entity] then return end - local Current = Entity:GetColor() - local New = Vector(Current.r, Current.g, Current.b) * math.Rand(0.3, 0.6) - if not next(Queue) then timer.Create("ACF_DebrisQueue", 0, 1, SendQueue) end Queue[Entity] = { + ID = Entity:EntIndex(), Position = Entity:GetPos(), - Angles = Entity:GetAngles(), - Material = Entity:GetMaterial(), - Model = Entity:GetModel(), - Color = Color(New.x, New.y, New.z, Current.a), Normal = Normal, Power = Power, CanGib = CanGib or nil,