Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
404 changes: 220 additions & 184 deletions lua/acf/server/sv_acfbase.lua

Large diffs are not rendered by default.

960 changes: 723 additions & 237 deletions lua/acf/server/sv_acfdamage.lua

Large diffs are not rendered by default.

126 changes: 126 additions & 0 deletions lua/acf/shared/rounds/ace_roundfunctions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,132 @@ do
return PlayerData, Data, ServerData, GUIData
end

--match sv_acfdamage.lua
function ACF_CalcFragmentCount(fillerMass, casingMass)
local totalMass = fillerMass + casingMass

local avgFragMassGrams = 0.1 + totalMass * 0.3
avgFragMassGrams = math.Clamp(avgFragMassGrams, 0.1, 5.0)

local baseFrags = (casingMass * 1000) / avgFragMassGrams

local maxFrags = ACF.MaxFragmentCount or 2000
local cappedFrags = math.Clamp(math.floor(baseFrags), 2, maxFrags)

return cappedFrags, math.floor(baseFrags)
end

function ACF_CalcGurneyFragVel(fillerMass, casingMass)
local cmRatio = fillerMass / math.max(casingMass, 0.001)
local velocity = ACF.GurneyConstant * math.sqrt(cmRatio) / math.sqrt(1 + cmRatio / 2)
return velocity
end

function ACF_CalcFragmentArea(fragMass)
local massGrams = fragMass * 1000
local areaCm2 = 0.5 * (massGrams ^ (2/3))
return areaCm2
end

function ACF_GetHEDisplayData(fillerMass, casingMass)
local GUIData = {}

fillerMass = tonumber(fillerMass) or 0
casingMass = tonumber(casingMass) or 0

-- Matches ACE_CalculateHERadius in sv_acfdamage.lua (meters)
GUIData.BlastRadius = fillerMass ^ 0.33 * 8

-- Fragment count
local fragsCapped, fragsUncapped = ACF_CalcFragmentCount(fillerMass, casingMass)
fragsCapped = math.max(tonumber(fragsCapped) or 0, 1)
fragsUncapped = math.max(tonumber(fragsUncapped) or 0, 0)

GUIData.Fragments = fragsCapped
GUIData.FragmentsUncapped = fragsUncapped

-- Frag mass / vel
GUIData.FragMass = casingMass / fragsCapped
GUIData.FragVel = ACF_CalcGurneyFragVel(fillerMass, casingMass)
GUIData.FragArea = ACF_CalcFragmentArea(GUIData.FragMass)

-- Power
GUIData.Power = fillerMass * (ACF.HEPower or 0)

-- Blast penetration
GUIData.CanBlastPen = GUIData.Power > (ACF.HEBlastPenMinPow or math.huge)
if GUIData.CanBlastPen then
GUIData.BlastPen = GUIData.Power / (ACF.HEBlastPenetration or 1)
GUIData.BlastPenRadius = GUIData.BlastRadius / (ACF.HEBlastPenRadiusMul or 1)
else
GUIData.BlastPen = 0
GUIData.BlastPenRadius = 0
GUIData.FillerNeeded = ((ACF.HEBlastPenMinPow or 0) - GUIData.Power) / (ACF.HEPower or 1)
end

-- If no casing, treat as blast-only for display
if casingMass <= 0 or GUIData.FragMass <= 0 then
GUIData.FragEffectiveRange = GUIData.BlastRadius * 1.5
GUIData.FragRadius = GUIData.BlastRadius * 2.5
return GUIData
end

-- Fragment range scaling (matches your sv_acfdamage.lua approach approximately)
local fragDensity = tonumber(ACF.FragDensity) or 0
local dragCoef = tonumber(ACF.FragDragCoef) or 0
local airDensity = tonumber(ACF.FragAirDensity) or 0

if fragDensity <= 0 or dragCoef <= 0 or airDensity <= 0 then
GUIData.FragEffectiveRange = GUIData.BlastRadius * 1.5
GUIData.FragRadius = GUIData.BlastRadius * 2.5
return GUIData
end

local fragVolume = GUIData.FragMass / fragDensity
local fragDiameter = (fragVolume * 6 / math.pi) ^ (1/3)
local fragArea = math.pi * (fragDiameter / 2) ^ 2
if fragArea <= 0 then
GUIData.FragEffectiveRange = GUIData.BlastRadius * 1.5
GUIData.FragRadius = GUIData.BlastRadius * 2.5
return GUIData
end

local ballisticCoef = GUIData.FragMass / (dragCoef * fragArea)
if ballisticCoef <= 0 then
GUIData.FragEffectiveRange = GUIData.BlastRadius * 1.5
GUIData.FragRadius = GUIData.BlastRadius * 2.5
return GUIData
end

local dragConstant = airDensity / (2 * ballisticCoef)

local effectiveVelRatio = ACF.HEFragEffectiveVelRatio or 0.25
local targetVel = math.max(GUIData.FragVel * effectiveVelRatio, ACF.MinLethalFragVel or 0)

if GUIData.FragVel > targetVel and dragConstant > 0 then
local rawRange = ((GUIData.FragVel / targetVel) - 1) * 1000 / (dragConstant * GUIData.FragVel)
local sourceUnits = rawRange * 39.37

local chargeScale = math.Clamp(1.1 - fillerMass * 0.15, 0.50, 1.0)
local velBonus = math.Clamp(0.4 + GUIData.FragVel / 2500, 0.5, 1.2)
local combinedScale = 0.40 * chargeScale * velBonus

local scaledRange = sourceUnits * combinedScale
scaledRange = math.max(scaledRange, GUIData.BlastRadius * 39.37 * 1.5)

local extendedZoneMul = math.Clamp(1.4 + fillerMass * 0.2, 1.4, 1.8)
local maxRange = math.min(scaledRange * extendedZoneMul, (ACF.HEFragMaxRange or 7874))

GUIData.FragEffectiveRange = scaledRange / 39.37
GUIData.FragRadius = maxRange / 39.37
else
GUIData.FragEffectiveRange = GUIData.BlastRadius * 1.5
GUIData.FragRadius = GUIData.BlastRadius * 2.5
end

return GUIData
end

function ACF_RoundShellCapacity( Momentum, FrArea, Caliber, ProjLength )

local MinWall = 0.2 + ((Momentum / FrArea) ^ 0.7) / 50 --The minimal shell wall thickness required to survive firing at the current energy level
Expand Down
33 changes: 23 additions & 10 deletions lua/acf/shared/rounds/roundaphe.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ function Round.convert( _, PlayerData )
GUIData.MaxFillerVol = math.min(GUIData.ProjVolume,MaxVol * 0.9)
GUIData.FillerVol = math.min(PlayerData.Data5,GUIData.MaxFillerVol)
Data.FillerMass = GUIData.FillerVol * ACF.HEDensity / 1000
Data.FragMass = math.max(Data.ProjMass - Data.FillerMass, 0)

Data.ProjMass = math.max(GUIData.ProjVolume-GUIData.FillerVol,0) * 7.9 / 1000 + Data.FillerMass
Data.MuzzleVel = ACF_MuzzleVelocity( Data.PropMass, Data.ProjMass, Data.Caliber )
Expand Down Expand Up @@ -75,16 +76,27 @@ function Round.convert( _, PlayerData )
end

function Round.getDisplayData(Data)
local GUIData = {}

local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.ProjMass, Data.LimitVel)
GUIData.MaxPen = (Energy.Penetration / Data.PenArea) * ACF.KEtoRHA

local casingMass = Data.FragMass or math.max(Data.ProjMass - Data.FillerMass, 0)
local HE = ACF_GetHEDisplayData(Data.FillerMass, casingMass)

GUIData.BlastRadius = HE.BlastRadius
GUIData.Fragments = HE.Fragments
GUIData.FragmentsUncapped = HE.FragmentsUncapped
GUIData.FragMass = HE.FragMass
GUIData.FragVel = HE.FragVel
GUIData.FragArea = HE.FragArea
GUIData.Power = HE.Power
GUIData.CanBlastPen = HE.CanBlastPen
GUIData.BlastPen = HE.BlastPen
GUIData.BlastPenRadius = HE.BlastPenRadius
GUIData.FragEffectiveRange = HE.FragEffectiveRange
GUIData.FragRadius = HE.FragRadius

local GUIData = {}
local Energy = ACF_Kinetic( Data.MuzzleVel * 39.37 , Data.ProjMass, Data.LimitVel )
local FragMass = Data.ProjMass - Data.FillerMass

GUIData.MaxPen = (Energy.Penetration / Data.PenArea) * ACF.KEtoRHA
GUIData.BlastRadius = Data.FillerMass ^ 0.33 * 8
GUIData.Fragments = math.max(math.floor((Data.FillerMass / FragMass) * ACF.HEFrag),2)
GUIData.FragMass = FragMass / GUIData.Fragments
GUIData.FragVel = (Data.FillerMass * ACF.HEPower * 1000 / GUIData.FragMass / GUIData.Fragments) ^ 0.5
return GUIData
end

Expand Down Expand Up @@ -175,7 +187,8 @@ end

function Round.endflight( Index, Bullet, HitPos, HitNormal )

ACF_HE( HitPos - Bullet.Flight:GetNormalized() * 3, HitNormal, Bullet.FillerMass, Bullet.ProjMass - Bullet.FillerMass, Bullet.Owner, nil, Bullet.Gun )
Bullet.FragMass = Bullet.FragMass or math.max((Bullet.ProjMass or 0) - (Bullet.FillerMass or 0), 0)
ACF_HE(HitPos - Bullet.Flight:GetNormalized() * 3, HitNormal, Bullet.FillerMass, Bullet.FragMass, Bullet.Owner, nil, Bullet.Gun)
ACF_RemoveBullet( Index )

end
Expand Down
57 changes: 47 additions & 10 deletions lua/acf/shared/rounds/roundclusterhe.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ function Round.convert( _, PlayerData )

--Volume of the projectile as a cylinder - Volume of the filler * density of steel + Volume of the filler * density of TNT
Data.ProjMass = math.max(GUIData.ProjVolume-PlayerData.Data5,0) * 7.9 / 1000 + math.min(PlayerData.Data5,GUIData.ProjVolume) * ACF.HEDensity / 1000
Data.FragMass = math.max(Data.ProjMass - Data.FillerMass, 0)
Data.MuzzleVel = ACF_MuzzleVelocity( Data.PropMass, Data.ProjMass, Data.Caliber )
local Energy = ACF_Kinetic( Data.MuzzleVel * 39.37 , Data.ProjMass, Data.LimitVel )
local MaxVol = ACF_RoundShellCapacity( Energy.Momentum, Data.FrArea, Data.Caliber, Data.ProjLength )
Expand Down Expand Up @@ -80,15 +81,34 @@ end

function Round.getDisplayData(Data)
local GUIData = {}

GUIData.FuseDistance = Data.FuseDistance
GUIData.BombletCount = math.Round(math.Clamp(math.Round(Data.FillerMass * 2),10,160) * Data.ClusterMult / 100)
GUIData.AdjFillerMass = Data.FillerMass / GUIData.BombletCount
local AdjProjMass = Data.ProjMass / 2
GUIData.BlastRadius = GUIData.AdjFillerMass ^ 0.33 * 8
local FragMass = AdjProjMass - GUIData.AdjFillerMass
GUIData.Fragments = math.max(math.floor((GUIData.AdjFillerMass / FragMass) * ACF.HEFrag),2)
GUIData.FragMass = FragMass / GUIData.Fragments
GUIData.FragVel = (GUIData.AdjFillerMass * ACF.HEPower * 1000 / GUIData.FragMass / GUIData.Fragments) ^ 0.5

-- number of bomblets (same as your server logic)
GUIData.BombletCount = math.Round(math.Clamp(math.Round((Data.FillerMass or 0) * 2), 10, 160) * (Data.ClusterMult or 100) / 100)

-- Per-bomblet filler and casing assumptions:
-- Your cluster spawner sets ProjMass ~= parent.ProjMass / Bomblets / 2 for each bomblet.
-- So for display, approximate per-bomblet projectile mass as parent.ProjMass / (Bomblets * 2).
local bomblets = math.max(GUIData.BombletCount, 1)

local fillerPer = (Data.FillerMass or 0) / bomblets
local projPer = (Data.ProjMass or 0) / bomblets / 2
local casingPer = math.max(projPer - fillerPer, 0)

GUIData.AdjFillerMass = fillerPer

local HE = ACF_GetHEDisplayData(fillerPer, casingPer)

GUIData.BlastRadius = HE.BlastRadius
GUIData.Fragments = HE.Fragments
GUIData.FragmentsUncapped = HE.FragmentsUncapped
GUIData.FragMass = HE.FragMass
GUIData.FragVel = HE.FragVel
GUIData.FragArea = HE.FragArea
GUIData.FragEffectiveRange = HE.FragEffectiveRange
GUIData.FragRadius = HE.FragRadius

return GUIData
end

Expand Down Expand Up @@ -174,6 +194,7 @@ do
GEnt.BulletDataC["Crate"] = bdata.Crate
GEnt.BulletDataC["DragCoef"] = bdata.DragCoef / Bomblets / 2
GEnt.BulletDataC["FillerMass"] = bdata.FillerMass / Bomblets --nan armor ocurrs when this value is > 1
GEnt.BulletDataC["FragMass"] = math.max(GEnt.BulletDataC["ProjMass"] - GEnt.BulletDataC["FillerMass"], 0)

--print(GEnt.BulletDataC["FillerMass"])
--print(Bomblets)
Expand Down Expand Up @@ -262,7 +283,15 @@ do

--local ACF_HE_Math = Bullet.Pos - Bullet.Flight:GetNormalized() * 3, Bullet.Flight:GetNormalized(), Bullet.FillerMass / 20, Bullet.ProjMass - Bullet.FillerMass
--ACF_HE(ACF_HE_Math, Bullet.Owner, nil, Bullet.Gun ) --Seperation airbursts. Fillermass reduced by 20 because it's the seperation charge.
ACF_HE( Bullet.Pos - Bullet.Flight:GetNormalized() * 3, Bullet.Flight:GetNormalized(), Bullet.FillerMass / 20, Bullet.ProjMass - Bullet.FillerMass, Bullet.Owner, nil, Bullet.Gun ) --Seperation airbursts. Fillermass reduced by 20 because it's the seperation charge.

local sepFiller = (Bullet.FillerMass or 0) / 20
-- Separation charge should have small fragmentation too.
-- Use a small fraction of the shell casing (or even 0 if you want blast-only separation).
local casing = math.max((Bullet.ProjMass or 0) - (Bullet.FillerMass or 0), 0)
local sepFrag = casing * 0.05

ACF_HE(Bullet.Pos - Bullet.Flight:GetNormalized() * 3, Bullet.Flight:GetNormalized(), sepFiller, sepFrag, Bullet.Owner, nil, Bullet.Gun)

local GunEnt = Bullet.Gun
if IsValid(GunEnt) then
--print("Valid")
Expand All @@ -279,7 +308,15 @@ do
function Round.endflight( Index, Bullet)
ACF_BulletClient( Index, Bullet, "Update" , 1 , Bullet.Pos ) --Ends the bullet flight on the clientside

ACF_HE( Bullet.Pos - Bullet.Flight:GetNormalized() * 3, Bullet.Flight:GetNormalized(), Bullet.FillerMass / 20, Bullet.ProjMass - Bullet.FillerMass, Bullet.Owner, nil, Bullet.Gun ) --Seperation airbursts. Fillermass reduced by 20 because it's the seperation charge.
local sepFiller = (Bullet.FillerMass or 0) / 20

-- Separation charge should have small fragmentation too.
-- Use a small fraction of the shell casing (or even 0 if you want blast-only separation).
local casing = math.max((Bullet.ProjMass or 0) - (Bullet.FillerMass or 0), 0)
local sepFrag = casing * 0.05

ACF_HE(Bullet.Pos - Bullet.Flight:GetNormalized() * 3, Bullet.Flight:GetNormalized(), sepFiller, sepFrag, Bullet.Owner, nil, Bullet.Gun)

local GunEnt = Bullet.Gun
if IsValid(GunEnt) then
--print("Valid")
Expand Down
Loading
Loading