diff --git a/lua/acf/server/sv_acfbase.lua b/lua/acf/server/sv_acfbase.lua index 2c893efb..71c85892 100644 --- a/lua/acf/server/sv_acfbase.lua +++ b/lua/acf/server/sv_acfbase.lua @@ -111,24 +111,24 @@ function ACF_Activate( Entity , Recalc ) end end -local IGNORED_CLASSES = { - gmod_ghost = true, - prop_ragdoll = true, - ace_debris = true, - sent_prop2mesh = true, -} - -function ACF_Check( Entity ) - - if not IsValid(Entity) then return false end - - local physobj = Entity:GetPhysicsObject() +local IGNORED_CLASSES = { + gmod_ghost = true, + prop_ragdoll = true, + ace_debris = true, + sent_prop2mesh = true, +} + +function ACF_Check( Entity ) + + if not IsValid(Entity) then return false end + + local physobj = Entity:GetPhysicsObject() if not ( physobj:IsValid() and (physobj:GetMass() or 0) > 0 and not Entity:IsWorld() and not Entity:IsWeapon() ) then return false end - local Class = Entity:GetClass() - if IGNORED_CLASSES[Class] or ( Class ~= "func_breakable" and string.find( Class , "func_" )) then return false end - if Entity.Exploding then return false end - + local Class = Entity:GetClass() + if IGNORED_CLASSES[Class] or (ACF.TraceFilter and ACF.TraceFilter[Class]) or ( Class ~= "func_breakable" and string.find( Class , "func_" )) then return false end + if Entity.Exploding then return false end + if not Entity.ACF or (Entity.ACF and isnumber(Entity.ACF.Material)) then ACF_Activate( Entity ) elseif Entity.ACF.Mass ~= physobj:GetMass() or (not IsValid(Entity.ACF.PhysObj) or Entity.ACF.PhysObj ~= physobj) then @@ -211,8 +211,8 @@ function ACF_CalcDamage( Entity , Energy , FrArea , Angle , Type) --y=-5/16x + b damageMult = ACF.HPDamageMult end - -- RHA Penetration - local maxPenetration = ACE_CalcPenetration(Energy, FrArea) + -- RHA Penetration + local maxPenetration = ACE_CalcPenetration(Energy, FrArea) -- Projectile caliber. Messy, function signature local caliber = 20 * (FrArea ^ (1 / ACF.PenAreaMod) / 3.1416) ^ 0.5 @@ -839,4 +839,4 @@ function chatMessageGlobal( message, color) --Like chatMessagePly but it just go chatMessagePly( ply , message, color) end end -]]-- +]]-- diff --git a/lua/acf/server/sv_acfdamage.lua b/lua/acf/server/sv_acfdamage.lua index 1c5a5d1f..bf5c3dd7 100644 --- a/lua/acf/server/sv_acfdamage.lua +++ b/lua/acf/server/sv_acfdamage.lua @@ -2,7 +2,7 @@ ACE.Spall = {} ACE.CurSpallIndex = 0 ACE.SpallMax = 250 - + -- optimization; reuse tables for ballistics traces local TraceRes = {} local TraceInit = { output = TraceRes } @@ -635,7 +635,7 @@ function ACF_Spall_HESH( HitPos, HitVec, Filter, HEFiller, Caliber, Armour, Infl local Velocityfactor = 0.12 local Max_Spall_Vel = 7000 local MassFactor = 6 - + local Max_Spalls = 128 -- print("HE: " .. HEFiller) @@ -654,8 +654,8 @@ function ACF_Spall_HESH( HitPos, HitVec, Filter, HEFiller, Caliber, Armour, Infl SpallWeight = SpallWeight * MassFactor local SpallArea = 4 * (TotalWeight / SpallWeight) local SpallEnergy = ACF_Kinetic(SpallVel, SpallWeight, Max_Spall_Vel) - - -- print("AR: " .. SpallArea) + + -- print("AR: " .. SpallArea) -- print("TW: " .. TotalWeight) diff --git a/lua/acf/server/sv_pointshandling.lua b/lua/acf/server/sv_pointshandling.lua index 348da3aa..0f78a0f2 100644 --- a/lua/acf/server/sv_pointshandling.lua +++ b/lua/acf/server/sv_pointshandling.lua @@ -713,13 +713,14 @@ ACE_CalcContraptionArmor = function(ent) end end - local ignoredArmor = { - acf_gun = true, - acf_rack = true, + local ignoredArmor = table.Copy(ACF.TraceFilter or {}) + table.Merge(ignoredArmor, { + acf_gun = false, + acf_rack = false, ace_crewseat_gunner = true, ace_crewseat_loader = true, ace_crewseat_driver = true - } + }) -- Build an orthonormal basis from a direction. local function basisFromDir(dir) @@ -1186,3 +1187,4 @@ function ACE_EnsureArmor(con, baseEnt, force) end _G.ACE_EnsureArmor = ACE_EnsureArmor + diff --git a/lua/acf/shared/sh_ace_functions.lua b/lua/acf/shared/sh_ace_functions.lua index 4c4a2ebf..f514b4e1 100644 --- a/lua/acf/shared/sh_ace_functions.lua +++ b/lua/acf/shared/sh_ace_functions.lua @@ -870,102 +870,6 @@ function ACE_SafeRound1(value) return math.Round(ACE_SafeNonNegative(value), 1) end -do - -- Ensure all ACE/ACF traces respect ACF.TraceFilter, even when a local filter - -- is missing or incomplete in a specific trace call site. - if util and not ACE._TraceFilterWrapped then - local RawTraceLine = util.TraceLine - local RawTraceHull = util.TraceHull - local RawQuickTrace = util.QuickTrace - local RawLegacyTraceLine = util.LegacyTraceLine - - local function shouldIgnoreByClass(ent) - if not IsValid(ent) then return false end - - local class = ent:GetClass() - if not class or class == "" then return false end - - local ignored = ACF and ACF.TraceFilter - return ignored and ignored[class] or false - end - - local function normalizeFilter(filter) - if isfunction(filter) then - return function(ent) - if shouldIgnoreByClass(ent) then return true end - return filter(ent) - end - end - - if IsValid(filter) then - return function(ent) - if shouldIgnoreByClass(ent) then return true end - return ent == filter - end - end - - if istable(filter) then - local lookup = {} - for _, item in pairs(filter) do - if IsValid(item) then - lookup[item] = true - end - end - - return function(ent) - if shouldIgnoreByClass(ent) then return true end - return lookup[ent] or false - end - end - - return function(ent) - return shouldIgnoreByClass(ent) - end - end - - if isfunction(RawTraceLine) then - util.TraceLine = function(traceData) - if not istable(traceData) then - return RawTraceLine(traceData) - end - - traceData.filter = normalizeFilter(traceData.filter) - return RawTraceLine(traceData) - end - end - - if isfunction(RawTraceHull) then - util.TraceHull = function(traceData) - if not istable(traceData) then - return RawTraceHull(traceData) - end - - traceData.filter = normalizeFilter(traceData.filter) - return RawTraceHull(traceData) - end - end - - if isfunction(RawQuickTrace) then - util.QuickTrace = function(startPos, delta, filter) - return RawQuickTrace(startPos, delta, normalizeFilter(filter)) - end - end - - if isfunction(RawLegacyTraceLine) then - util.LegacyTraceLine = function(traceData) - if not istable(traceData) then - return RawLegacyTraceLine(traceData) - end - - traceData.filter = normalizeFilter(traceData.filter) - return RawLegacyTraceLine(traceData) - end - end - - ACE._TraceFilterWrapped = true - end -end - function ACE_FormatDetailLabel(ent) if not ACE_IsEnt(ent) then return "Unknown" end @@ -1006,10 +910,55 @@ end function ACE_GetMissileGuidanceFactor(guidanceValue) local factors = ACE.MissileGuidanceFactors or {} local fallback = tonumber(factors.Dumb) or 1 - local name = ACE_GetConfigurableName(guidanceValue, "Dumb") - local factor = tonumber(factors[name]) or fallback - return math.max(factor, 0) + local function normalizeName(name) + if type(name) ~= "string" or name == "" then return nil end + return (name:gsub("%s+", "_"):gsub("%-", "_")) + end + + local function resolveFactorFromName(name) + name = normalizeName(name) + if not name then return nil end + + local direct = tonumber(factors[name]) + if direct then return direct end + + local lower = string.lower(name) + for key, value in pairs(factors) do + if string.lower(tostring(key)) == lower then + return tonumber(value) + end + end + + return nil + end + + -- Direct string forms, including configurable "Name:arg=val". + if type(guidanceValue) == "string" then + local name = ACE_GetConfigurableName(guidanceValue, "Dumb") + local factor = resolveFactorFromName(name) or resolveFactorFromName(guidanceValue) or fallback + return math.max(factor, 0) + end + + -- Configurable/table forms used at runtime by missile entities. + if istable(guidanceValue) then + local candidates = { + guidanceValue.Name, + guidanceValue.name, + guidanceValue.ClassName, + guidanceValue.class, + guidanceValue.GuidanceName, + guidanceValue.Guidance, + guidanceValue.Type + } + + for _, candidate in ipairs(candidates) do + local factor = resolveFactorFromName(candidate) + if factor then return math.max(factor, 0) end + end + end + + return math.max(fallback, 0) end -- Resolve the gun class string for ammo bullet data. @@ -1124,6 +1073,14 @@ function ACE_IsATGMCostAmmo(bdata) return ACE_GetAmmoGunClass(bdata) == "ATGM" end +-- Resolve guidance scaling used by missile point/threat math. +local function ACE_GetAmmoGuidancePenFactor(bdata) + if not bdata or not ACE_IsAmmoMissileType(bdata) then return 1 end + -- Guidance should act as an upward threat modifier in pen-space, not make + -- missiles artificially cheap when using low-end guidance packages. + return math.max(ACE_GetMissileGuidanceFactor(bdata.Data7), 1) +end + -- Calculate per-missile legacy points (manufacturing-cost basis), including guidance. function ACE_CalcMissileLegacyRoundCost(bdata) if not istable(bdata) then return 0 end @@ -1152,8 +1109,12 @@ function ACE_CalcMissileLegacyRoundCost(bdata) end basePts = math.max(basePts, minBase) + + -- Guidance is already applied in missile threat/penetration scaling for ATGM perf points. + return math.max(basePts, 0) end + -- Non-ATGM missile racks still use legacy pointcost, so guidance is applied here. return math.max(basePts, 0) * factor end @@ -1186,7 +1147,8 @@ function ACE_GetRofThreatFactor(rps, cfg) local kneeRpm = tonumber(cfg.RofKneeRpm) or 0 if kneeRpm <= 0 then return 0 end - local rpm = rpsValue * 60 + local minRpm = tonumber(cfg.MinRofRpm) or 0 + local rpm = math.max(rpsValue * 60, minRpm) return rpm / (rpm + kneeRpm) end @@ -1210,12 +1172,12 @@ function ACE_GetReadyRackCap(calMm) end -- Calculate per-round points (no RPS factors) for ammo allocation weighting. -function ACE_GetAmmoRoundPoints(bdata) - if not bdata then return 0 end - - local maxPen = ACE_GetAmmoMaxPen(bdata) - local blastMass = ACE_GetAmmoBlastMass(bdata) - if maxPen <= 0 and blastMass <= 0 then return 0 end +function ACE_GetAmmoRoundPoints(bdata) + if not bdata then return 0 end + + local maxPen = ACE_GetAmmoMaxPen(bdata) * ACE_GetAmmoGuidancePenFactor(bdata) + local blastMass = ACE_GetAmmoBlastMass(bdata) + if maxPen <= 0 and blastMass <= 0 then return 0 end local calMm = ACE_GetAmmoCaliberMm(bdata) if calMm <= 0 then return 0 end @@ -1259,12 +1221,12 @@ function ACE_GetAmmoRoundPoints(bdata) end -- Calculate threat weight for ready-rack allocation. -function ACE_GetAmmoThreatWeight(bdata) - if not bdata then return 0 end - - local maxPen = ACE_GetAmmoMaxPen(bdata) - local blastMass = ACE_GetAmmoBlastMass(bdata) - if maxPen <= 0 and blastMass <= 0 then return 0 end +function ACE_GetAmmoThreatWeight(bdata) + if not bdata then return 0 end + + local maxPen = ACE_GetAmmoMaxPen(bdata) * ACE_GetAmmoGuidancePenFactor(bdata) + local blastMass = ACE_GetAmmoBlastMass(bdata) + if maxPen <= 0 and blastMass <= 0 then return 0 end local cfg = ACE.AmmoCostConfig or {} local refBlast = cfg.RefBlastMass or 0 diff --git a/lua/autorun/acf_globals.lua b/lua/autorun/acf_globals.lua index 28106257..8d379a1e 100644 --- a/lua/autorun/acf_globals.lua +++ b/lua/autorun/acf_globals.lua @@ -39,9 +39,9 @@ ACF.SWEPInaccuracyMul = 0.5 ---------------------------------- Debris ---------------------------------- ACF.DebrisIgniteChance = 0.25 -ACF.DebrisScale = 20 -- Ignore debris that is less than this bounding radius. -ACF.DebrisChance = 0.5 -ACF.DebrisLifeTime = 60 +ACF.DebrisScale = 10 -- Ignore debris that is less than this bounding radius. +ACF.DebrisChance = 1 +ACF.DebrisLifeTime = 30 ---------------------------------- Fuel & fuel Tank config ---------------------------------- @@ -102,14 +102,15 @@ ACF.BulletIndexLimit = 5000 -- The maximum number of bullets in flight at an ACF.SkyboxGraceZone = 100 -- grace zone for the high angle fire ACF.SkyboxMinCaliber = 5 -ACF.TraceFilter = { -- entities that cause issue with acf and should be not be processed at all - - prop_vehicle_crane = true, - prop_dynamic = true, - npc_strider = true, - worldspawn = true, --The worldspawn in infinite maps is fake. Since the IsWorld function will not do something to avoid this case, that i will put it here. - -} +ACF.TraceFilter = { -- entities that cause issue with acf and should be not be processed at all + + prop_vehicle_crane = true, + prop_dynamic = true, + npc_strider = true, + sent_prop2mesh = true, + worldspawn = true, --The worldspawn in infinite maps is fake. Since the IsWorld function will not do something to avoid this case, that i will put it here. + +} ACF.DragDiv = 40 -- Drag fudge factor ACF.VelScale = 1 -- Scale factor for the shell velocities in the game world @@ -129,32 +130,32 @@ ACF.LargeEngineThreshold = 100 --Engine size in hp required to need a driver ACF.LargeGunsRequireGunners = 1 --Should engines over a certain hp need a driver? Modified by console commands. ACF.LargeGunsThreshold = 40 --Cannon size in mm required to need a driver -ACF.PointsLimit = 10000 -- The maximum legal point value. -ACF.MaxWeight = 200000 -- The max weight in kg. - -ACE.PointCostConfig = ACE.PointCostConfig or { - ArmorFrontWeight = 1.0, -- Front armor contribution in armor points. - ArmorSideWeight = 2.5, -- Side armor contribution in armor points. - ArmorScale = 4.0, -- Final armor points multiplier. - CrewSeatFlat = 250, -- Flat point cost per crew seat entity. - MinDetailPoints = 300 -- Minimum points to list an entry in armor tool breakdown. -} - -ACE.GunPointCostMultiplier = tonumber(ACE.GunPointCostMultiplier) or tonumber(ACE.CannonPointMul) or 0.85 -- Multiplier for gun/cannon point cost. -ACE.EnginePointCostMultiplier = tonumber(ACE.EnginePointCostMultiplier) or tonumber(ACE.EnginePointMul) or 0.69 -- Multiplier for engine point cost. -ACF.LegacyManufacturingPointsPerTon = tonumber(ACF.LegacyManufacturingPointsPerTon) or tonumber(ACF.PointsPerTon) or 42 -- Legacy manufacturing cost coefficient per ton. -ACE.LegacyAmmoPointsPerTon = tonumber(ACE.LegacyAmmoPointsPerTon) or tonumber(ACE.AmmoPerTon) or 100 -- Legacy non-missile ammo points per ton. -ACE.CrewSeatPointCost = tonumber(ACE.CrewSeatPointCost) - or tonumber(ACE.CrewSeatCostFlat) - or tonumber(ACE.PointCostConfig and ACE.PointCostConfig.CrewSeatFlat) - or 250 -- Flat point cost per crew seat. - --- Backward-compatible aliases (deprecated names). -ACE.CannonPointMul = ACE.GunPointCostMultiplier -ACE.EnginePointMul = ACE.EnginePointCostMultiplier -ACF.PointsPerTon = ACF.LegacyManufacturingPointsPerTon -ACE.AmmoPerTon = ACE.LegacyAmmoPointsPerTon -ACE.CrewSeatCostFlat = ACE.CrewSeatPointCost +ACF.PointsLimit = 10000 -- The maximum legal point value. +ACF.MaxWeight = 200000 -- The max weight in kg. + +ACE.PointCostConfig = ACE.PointCostConfig or { + ArmorFrontWeight = 1.0, -- Front armor contribution in armor points. + ArmorSideWeight = 2.2, -- Side armor contribution in armor points. + ArmorScale = 4.0, -- Final armor points multiplier. + CrewSeatFlat = 250, -- Flat point cost per crew seat entity. + MinDetailPoints = 300 -- Minimum points to list an entry in armor tool breakdown. +} + +ACE.GunPointCostMultiplier = tonumber(ACE.GunPointCostMultiplier) or tonumber(ACE.CannonPointMul) or 0.85 -- Multiplier for gun/cannon point cost. +ACE.EnginePointCostMultiplier = tonumber(ACE.EnginePointCostMultiplier) or tonumber(ACE.EnginePointMul) or 0.69 -- Multiplier for engine point cost. +ACF.LegacyManufacturingPointsPerTon = tonumber(ACF.LegacyManufacturingPointsPerTon) or tonumber(ACF.PointsPerTon) or 42 -- Legacy manufacturing cost coefficient per ton. +ACE.LegacyAmmoPointsPerTon = tonumber(ACE.LegacyAmmoPointsPerTon) or tonumber(ACE.AmmoPerTon) or 100 -- Legacy non-missile ammo points per ton. +ACE.CrewSeatPointCost = tonumber(ACE.CrewSeatPointCost) + or tonumber(ACE.CrewSeatCostFlat) + or tonumber(ACE.PointCostConfig and ACE.PointCostConfig.CrewSeatFlat) + or 250 -- Flat point cost per crew seat. + +-- Backward-compatible aliases (deprecated names). +ACE.CannonPointMul = ACE.GunPointCostMultiplier +ACE.EnginePointMul = ACE.EnginePointCostMultiplier +ACF.PointsPerTon = ACF.LegacyManufacturingPointsPerTon +ACE.AmmoPerTon = ACE.LegacyAmmoPointsPerTon +ACE.CrewSeatCostFlat = ACE.CrewSeatPointCost -- Deprecated: armor mass-based cost has been replaced by LOS armor scan. -- @@ -195,10 +196,11 @@ ACE.LegacyMatCostTables = ACE.LegacyMatCostTables or { RHA = 1 } ACE.AmmoCostConfig = { - BaseRoundPts = 385, -- Tuned so 25x APFSDS @ 750mm at 12 RPM is ~3333 pts (120/125mm class). + BaseRoundPts = 340, -- Tuned so 25x APFSDS @ 750mm at 12 RPM is ~3333 pts (120/125mm class). RefPen = 700, -- Reference penetration (mm) for pen scaling. RefCaliber = 100, -- Reference caliber (mm) for caliber scaling. - RofKneeRpm = 14, -- RoF knee for saturation: RoF/(RoF+k), using RPM. + RofKneeRpm = 8, -- RoF knee for saturation: RoF/(RoF+k), using RPM. + MinRofRpm = 6, -- Minimum RPM factored into ROF threat scaling. RefBlastMass = 6, -- Reference HE filler mass (kg) for blast scaling. BlastExp = 1.1, -- Blast curve exponent. BlastWeight = 0.25, -- Blend weight for blast vs penetration threat. @@ -324,7 +326,7 @@ if SERVER then CreateConVar("sbox_max_acf_ammo", 100) -- ammo limit CreateConVar("sbox_max_acf_misc", 100) -- misc ents limit CreateConVar("sbox_max_acf_rack", 24) -- Racks limit - CreateConVar("sbox_max_ace_crewseat", 100) + CreateConVar("sbox_max_ace_crewseat", 100) CreateConVar("acf_mines_max", 50) -- The mine limit CreateConVar("acf_meshvalue", 1) @@ -434,18 +436,46 @@ if SERVER then cvars.AddChangeCallback("acf_spalling_multipler", ACF_CVarChangeCallback) cvars.AddChangeCallback("acf_gunfire", ACF_CVarChangeCallback) cvars.AddChangeCallback("acf_debris_lifetime", ACF_CVarChangeCallback) - cvars.AddChangeCallback("acf_debris_children", ACF_CVarChangeCallback) - cvars.AddChangeCallback("acf_explosions_scaled_he_max", ACF_CVarChangeCallback) - cvars.AddChangeCallback("acf_explosions_scaled_ents_max", ACF_CVarChangeCallback) - cvars.AddChangeCallback("acf_legacyrecoil", ACF_CVarChangeCallback) - cvars.AddChangeCallback("acf_legality_enginesrequirefuel", ACF_CVarChangeCallback) - cvars.AddChangeCallback("acf_legality_largeenginesneeddriver", ACF_CVarChangeCallback) - cvars.AddChangeCallback("acf_legality_largeenginethreshold", ACF_CVarChangeCallback) - cvars.AddChangeCallback("acf_legality_largegunsneedgunner", ACF_CVarChangeCallback) - cvars.AddChangeCallback("acf_legality_largegunthreshold", ACF_CVarChangeCallback) - cvars.AddChangeCallback("acf_enable_dp", ACF_CVarChangeCallback) - -elseif CLIENT then +cvars.AddChangeCallback("acf_debris_children", ACF_CVarChangeCallback) +cvars.AddChangeCallback("acf_explosions_scaled_he_max", ACF_CVarChangeCallback) +cvars.AddChangeCallback("acf_explosions_scaled_ents_max", ACF_CVarChangeCallback) +cvars.AddChangeCallback("acf_legacyrecoil", ACF_CVarChangeCallback) +cvars.AddChangeCallback("acf_legality_enginesrequirefuel", ACF_CVarChangeCallback) +cvars.AddChangeCallback("acf_legality_largeenginesneeddriver", ACF_CVarChangeCallback) +cvars.AddChangeCallback("acf_legality_largeenginethreshold", ACF_CVarChangeCallback) +cvars.AddChangeCallback("acf_legality_largegunsneedgunner", ACF_CVarChangeCallback) +cvars.AddChangeCallback("acf_legality_largegunthreshold", ACF_CVarChangeCallback) +cvars.AddChangeCallback("acf_enable_dp", ACF_CVarChangeCallback) + +-- Apply archived/server convars at startup so values persist across restarts and reconnects. +local startupSync = { + "acf_healthmod", + "acf_armormod", + "acf_ammomod", + "acf_spalling", + "acf_spalling_multipler", + "acf_gunfire", + "acf_debris_lifetime", + "acf_debris_children", + "acf_explosions_scaled_he_max", + "acf_explosions_scaled_ents_max", + "acf_legacyrecoil", + "acf_legality_enginesrequirefuel", + "acf_legality_largeenginesneeddriver", + "acf_legality_largeenginethreshold", + "acf_legality_largegunsneedgunner", + "acf_legality_largegunthreshold", + "acf_enable_dp" +} + +for _, name in ipairs(startupSync) do + local convar = GetConVar(name) + if convar then + ACF_CVarChangeCallback(name, nil, convar:GetString()) + end +end + +elseif CLIENT then ---------------------------------- Clientside Convars ---------------------------------- CreateClientConVar( "acf_enable_lighting", 1, true ) --Should missiles emit light while their motors are burning? Looks nice but hits framerate. Set to 1 to enable, set to 0 to disable, set to another number to set minimum light-size. diff --git a/lua/autorun/client/cl_acfm_menuinject.lua b/lua/autorun/client/cl_acfm_menuinject.lua index a5a32094..566d1b7d 100644 --- a/lua/autorun/client/cl_acfm_menuinject.lua +++ b/lua/autorun/client/cl_acfm_menuinject.lua @@ -213,7 +213,8 @@ function CreateRackSelectGUI(node) acfmenupanel.CData.RackSelect = vgui.Create( "DComboBox", acfmenupanel.CustomDisplay ) acfmenupanel.CData.RackSelect:SetSize(100, 30) - acfmenupanel.CData.RackSelect.OnSelect = function( _ , _ , data ) + acfmenupanel.CData.RackSelect.OnSelect = function( panel, index, _, data ) + data = data or panel:GetOptionData(index) RunConsoleCommand( "acfmenu_data9", data ) local rack = ACF.Weapons.Racks[data] @@ -307,7 +308,8 @@ function ModifyACFMenu(panel) end - local rootNodes = HomeNode.ChildNodes:GetChildren() --lets find all our folder inside of Main menu + local rootNodes = HomeNode and HomeNode.ChildNodes and HomeNode.ChildNodes:GetChildren() -- lets find all our folders inside the main menu + if not rootNodes then return false end local gunsNode @@ -319,43 +321,52 @@ function ModifyACFMenu(panel) end end - if gunsNode then - local classNodes = gunsNode.ChildNodes:GetChildren() - local gunClasses = ACF.Classes.GunClass + if not (gunsNode and gunsNode.ChildNodes) then return false end - for _, node in pairs(classNodes) do - local gunNodeElement = node.ChildNodes + local classNodes = gunsNode.ChildNodes:GetChildren() + local gunClasses = ACF.Classes.GunClass + local foundAnyChildren = false + local patchedAny = false + local foundAnyMissileEntries = false - if gunNodeElement then - local gunNodes = gunNodeElement:GetChildren() + for _, node in pairs(classNodes) do + local gunNodeElement = node.ChildNodes + if not gunNodeElement then continue end - for _, gun in pairs(gunNodes) do - local class = gunClasses[gun.mytable.gunclass] + local gunNodes = gunNodeElement:GetChildren() + if #gunNodes > 0 then + foundAnyChildren = true + end + + for _, gun in pairs(gunNodes) do + if not (gun.mytable and gun.mytable.gunclass) then continue end + + local class = gunClasses[gun.mytable.gunclass] - if (class and class.type == "missile") and not gun.ACFMOverridden then - local oldclick = gun.DoClick + if class and class.type == "missile" then + foundAnyMissileEntries = true - gun.DoClick = function(self) - oldclick(self) - CreateRackSelectGUI(self) - end + if not gun.ACFMOverridden then + local oldclick = gun.DoClick - gun.ACFMOverridden = true + gun.DoClick = function(self) + oldclick(self) + CreateRackSelectGUI(self) end + + gun.ACFMOverridden = true + patchedAny = true end - else - ErrorNoHalt("ACFM: Unable to find guns for class " .. node:GetText() .. ".\n") end end - else - ErrorNoHalt("ACFM: Unable to find the ACF Guns node.") end + return foundAnyChildren and (patchedAny or foundAnyMissileEntries) + end function FindACFMenuPanel() - if acfmenupanel then - ModifyACFMenu(acfmenupanel) + if acfmenupanel and ModifyACFMenu(acfmenupanel) then timer.Remove("FindACFMenuPanel") end end diff --git a/lua/entities/ace_crewseat_driver/init.lua b/lua/entities/ace_crewseat_driver/init.lua index c6c520d9..dd24dfef 100644 --- a/lua/entities/ace_crewseat_driver/init.lua +++ b/lua/entities/ace_crewseat_driver/init.lua @@ -25,6 +25,7 @@ function ENT:Initialize() end function MakeACE_Crewseat_Driver(Owner, Pos, Angle, Id, EntityData) + if not IsValid(Owner) then return false end if not Owner:CheckLimit("_ace_crewseat") then return false end Id = Id or "Crewseat_Driver" diff --git a/lua/entities/ace_crewseat_gunner/init.lua b/lua/entities/ace_crewseat_gunner/init.lua index f2cf402b..6744c0d0 100644 --- a/lua/entities/ace_crewseat_gunner/init.lua +++ b/lua/entities/ace_crewseat_gunner/init.lua @@ -26,6 +26,7 @@ function ENT:Initialize() end function MakeACE_Crewseat_Gunner(Owner, Pos, Angle, Id, EntityData) + if not IsValid(Owner) then return false end if not Owner:CheckLimit("_ace_crewseat") then return false end Id = Id or "Crewseat_Gunner" diff --git a/lua/entities/ace_crewseat_loader/init.lua b/lua/entities/ace_crewseat_loader/init.lua index 24234b04..5fa38703 100644 --- a/lua/entities/ace_crewseat_loader/init.lua +++ b/lua/entities/ace_crewseat_loader/init.lua @@ -28,6 +28,7 @@ function ENT:Initialize() end function MakeACE_Crewseat_Loader(Owner, Pos, Angle, Id, EntityData) + if not IsValid(Owner) then return false end if not Owner:CheckLimit("_ace_crewseat") then return false end Id = Id or "Crewseat_Loader" diff --git a/lua/weapons/gmod_tool/stools/acfarmorprop.lua b/lua/weapons/gmod_tool/stools/acfarmorprop.lua index 41fbe460..bb98072d 100644 --- a/lua/weapons/gmod_tool/stools/acfarmorprop.lua +++ b/lua/weapons/gmod_tool/stools/acfarmorprop.lua @@ -186,7 +186,6 @@ function TOOL:Reload( trace ) local Contraption = ent:GetContraption() or nil local details = Contraption and Contraption.ACEPointsDetails or nil - local armorLines = Contraption and Contraption.ACEArmorDetails or nil local PointVal = 0 local PtsArmor = 0 @@ -412,6 +411,25 @@ local function ACE_GetAmmoCostForCaliber(con, caliberMm) return total end +-- Sum ammo points for crates compatible with a rack. +local function ACE_GetAmmoCostForRack(con, rack) + if not con or not con.ents or not IsValid(rack) then return 0 end + if not ACF_CanLinkRack or not rack.Id then return 0 end + + local total = 0 + + for ent in pairs(con.ents) do + if IsValid(ent) and ent:GetClass() == "acf_ammo" then + local bdata = ent.BulletData + if istable(bdata) and ACF_CanLinkRack(rack.Id, bdata.Id, bdata, rack) then + total = total + (ACE_GetAmmoCratePointsForContraption(ent, con, ent) or 0) + end + end + end + + return total +end + -- Resolve point category for a class. local function ACE_GetPointsCategory(ent) if not IsValid(ent) then return nil end @@ -424,7 +442,7 @@ end -- Compute popup points and label for an entity. -- Order: entity points, gun-caliber ammo total, then category total. -local ArmorPopupDebug = SERVER and CreateConVar("ace_debug_armor_popup", "0", FCVAR_ARCHIVE, "Debug armor popup per-entity contribution lookup.", 0, 1) or nil +local ArmorPopupDebug = SERVER and CreateConVar("acf_debug_armor_popup", "0", FCVAR_ARCHIVE, "Debug armor popup per-entity contribution lookup.", 0, 1) or nil local ArmorPopupDebugLast = {} local function ACE_DebugArmorPopup(ply, ent, con, matchedRow) @@ -477,6 +495,14 @@ local function ACE_GetPopupPoints(ent, ply) end end + -- Racks fall back to the ammo cost of crates compatible with this rack. + if cls == "acf_rack" then + local ammoCost = ACE_GetAmmoCostForRack(con, ent) + if ammoCost > 0 then + return ammoCost, "Total Rack Ammo Cost" + end + end + if con and ACE_EnsureArmor then ACE_EnsureArmor(con, ent, false) end