Fix coop weapon pickup limits

This commit is contained in:
2026-09-06 17:12:27 +02:00
parent aa7b4f8e7b
commit e855d07d37
+97 -17
View File
@@ -15,6 +15,30 @@ local respawnDelay = CreateConVar(
local trackedItems = {}
local suppressRespawns = false
-- Sandbox defaults this to 9999, overriding every Source ammo type's intended
-- carry limit. Coop uses the original per-ammo limits instead.
local maxAmmoOverride = GetConVar("gmod_maxammo")
if maxAmmoOverride and maxAmmoOverride:GetInt() > 0 then
RunConsoleCommand("gmod_maxammo", "0")
end
local function ClampPlayerAmmo(ply)
for ammoType = 1, 255 do
local maximum = game.GetAmmoMax(ammoType)
if maximum and maximum >= 0 and ply:GetAmmoCount(ammoType) > maximum then
ply:SetAmmo(maximum, ammoType)
end
end
end
-- Also repairs reserves accumulated while the sandbox-wide 9999 override was
-- active. Unknown and explicitly infinite ammo types are left unchanged.
timer.Simple(0, function()
for _, ply in ipairs(player.GetAll()) do
if IsValid(ply) then ClampPlayerAmmo(ply) end
end
end)
-- These use the item_ prefix but are map machinery or containers rather than
-- touch pickups. Ammo crates are already infinite; the others may be removed
-- deliberately by Hammer I/O and must not be recreated as loose supplies.
@@ -42,16 +66,18 @@ local itemAmmoTypes = {
item_rpg_round = "RPG_Round"
}
-- These weapons store their ammunition directly on the player rather than in
-- a clip, so Clip1() cannot tell us how much a duplicate pickup is worth.
local cliplessWeaponAmmo = {
weapon_frag = 1,
weapon_rpg = 1,
weapon_slam = 1
}
local function CanReceiveAmmo(ply, ammoType)
if ammoType == nil or ammoType < 0 then return false end
local maximum
local maxAmmoOverride = GetConVar("gmod_maxammo")
if maxAmmoOverride and maxAmmoOverride:GetInt() > 0 then
maximum = maxAmmoOverride:GetInt()
else
maximum = game.GetAmmoMax(ammoType)
end
local maximum = game.GetAmmoMax(ammoType)
-- Unknown/custom ammo types are left to their own pickup implementation.
if maximum == nil or maximum < 0 then return true end
@@ -74,6 +100,8 @@ local function CaptureTemplate(ent)
bodygroups[bodygroup.id] = ent:GetBodygroup(bodygroup.id)
end
local isWeapon = ent:IsWeapon()
return {
className = ent:GetClass(),
position = ent:GetPos(),
@@ -85,7 +113,9 @@ local function CaptureTemplate(ent)
collisionGroup = ent:GetCollisionGroup(),
name = ent:GetName(),
keyValues = ent:GetKeyValues(),
bodygroups = bodygroups
bodygroups = bodygroups,
clip1 = isWeapon and ent:Clip1() or nil,
clip2 = isWeapon and ent:Clip2() or nil
}
end
@@ -104,8 +134,11 @@ end
TrackItem = function(ent, template)
if not IsValid(ent) or trackedItems[ent] then return end
template = template or CaptureTemplate(ent)
template = template or ent.CustomCoopRespawnTemplate or CaptureTemplate(ent)
trackedItems[ent] = template
-- Lua entity fields survive script auto-refresh. Keeping the template on
-- the entity lets a fresh copy of this file recover its original spawn.
ent.CustomCoopRespawnTemplate = template
ent:CallOnRemove("CustomCoopRespawnItem", function(removed)
local savedTemplate = trackedItems[removed]
@@ -147,6 +180,11 @@ RespawnItem = function(template)
return
end
-- Spawn normally fills these from the weapon script. Restore the captured
-- values as well so mapper-specified pickup quantities survive recreation.
if template.clip1 ~= nil then ent:SetClip1(template.clip1) end
if template.clip2 ~= nil then ent:SetClip2(template.clip2) end
ent:SetSkin(template.skin or 0)
ent:SetMaterial(template.material or "")
ent:SetColor(template.color or color_white)
@@ -168,6 +206,19 @@ hook.Add("OnEntityCreated", "CustomCoopTrackMapItems", function(ent)
end)
end)
local function TrackExistingItems()
for _, ent in ipairs(ents.GetAll()) do
if IsRespawnable(ent) and (ent:CreatedByMap() or ent.CustomCoopRespawnTemplate) then
TrackItem(ent)
end
end
end
-- InitPostEntity handles a normal map load; the zero-delay scan handles GMod's
-- script auto-refresh, where existing entities do not fire OnEntityCreated.
hook.Add("InitPostEntity", "CustomCoopTrackExistingMapItems", TrackExistingItems)
timer.Simple(0, TrackExistingItems)
hook.Add("WeaponEquip", "CustomCoopRespawnCollectedWeapon", function(weapon)
local template = trackedItems[weapon]
if not template then return end
@@ -179,17 +230,46 @@ hook.Add("WeaponEquip", "CustomCoopRespawnCollectedWeapon", function(weapon)
ScheduleRespawn(template)
end)
-- A duplicate weapon is removed through WEAPON:EquipAmmo even when the player
-- cannot accept more ammo. Refusing only the useless attempt leaves the pickup
-- available and silent until this player spends ammunition (or someone else
-- who needs it touches it).
-- Duplicate HL2 weapons do not transfer ammo consistently in GMod. Handle
-- tracked map weapons here so useful ammo is transferred exactly once, while
-- a full player leaves the pickup available and silent for someone else.
hook.Add("PlayerCanPickupWeapon", "CustomCoopIgnoreFullDuplicateWeapons", function(ply, weapon)
if not trackedItems[weapon] or not ply:HasWeapon(weapon:GetClass()) then return end
if not ply:HasWeapon(weapon:GetClass()) then return end
if not CanReceiveAmmo(ply, weapon:GetPrimaryAmmoType())
and not CanReceiveAmmo(ply, weapon:GetSecondaryAmmoType()) then
return false
local primaryAmmoType = weapon:GetPrimaryAmmoType()
local secondaryAmmoType = weapon:GetSecondaryAmmoType()
-- Some map logic creates weapons dynamically, so they are not part of the
-- map-entity tracker. A second copy of an ammo-less weapon is still always
-- useless and should never produce another pickup event.
if primaryAmmoType < 0 and secondaryAmmoType < 0 then return false end
if not trackedItems[weapon] then return end
local primaryAmount = math.max(weapon:Clip1(), 0)
if primaryAmount == 0 then
primaryAmount = cliplessWeaponAmmo[weapon:GetClass()] or 0
end
local given = 0
if primaryAmmoType >= 0 and primaryAmount > 0 then
given = given + (ply:GiveAmmo(primaryAmount, primaryAmmoType) or 0)
end
local secondaryAmount = math.max(weapon:Clip2(), 0)
if secondaryAmmoType >= 0 and secondaryAmount > 0 then
given = given + (ply:GiveAmmo(secondaryAmount, secondaryAmmoType) or 0)
end
-- Handle the duplicate ourselves. The native path is inconsistent for
-- HL2 weapons under GMod: it may drain or hide the entity without giving
-- usable reserve ammo. Removing only after GiveAmmo succeeds also makes a
-- full pickup silent and leaves it available to another player.
if given > 0 then
SafeRemoveEntityDelayed(weapon, 0)
end
return false
end)
-- Stock ammo items normally reject a full player themselves. Checking here as