Files
garrysmod-coop/gamemode/weaponsystem.lua
T

174 lines
5.4 KiB
Lua

local respawnDistance = CreateConVar(
"gc_respawndistance",
"75",
FCVAR_ARCHIVE,
"Distance a map item may move from its spawn before it is returned"
)
local respawnDelay = CreateConVar(
"gc_itemrespawntime",
"1",
FCVAR_ARCHIVE,
"Seconds before a collected or displaced map item respawns"
)
local trackedItems = {}
local suppressRespawns = false
local function IsRespawnable(ent)
if not IsValid(ent) then return false end
local className = ent:GetClass()
return string.StartWith(className, "item_") or string.StartWith(className, "weapon_")
end
local function CaptureTemplate(ent)
local bodygroups = {}
for _, bodygroup in ipairs(ent:GetBodyGroups() or {}) do
bodygroups[bodygroup.id] = ent:GetBodygroup(bodygroup.id)
end
return {
className = ent:GetClass(),
position = ent:GetPos(),
angles = ent:GetAngles(),
model = ent:GetModel(),
skin = ent:GetSkin(),
material = ent:GetMaterial(),
color = ent:GetColor(),
collisionGroup = ent:GetCollisionGroup(),
name = ent:GetName(),
keyValues = ent:GetKeyValues(),
bodygroups = bodygroups
}
end
local TrackItem
local RespawnItem
local function ScheduleRespawn(template)
if suppressRespawns then return end
timer.Simple(math.max(respawnDelay:GetFloat(), 0), function()
if suppressRespawns then return end
RespawnItem(template)
end)
end
TrackItem = function(ent, template)
if not IsValid(ent) or trackedItems[ent] then return end
template = template or CaptureTemplate(ent)
trackedItems[ent] = template
ent:CallOnRemove("CustomCoopRespawnItem", function(removed)
local savedTemplate = trackedItems[removed]
trackedItems[removed] = nil
if savedTemplate then
ScheduleRespawn(savedTemplate)
end
end)
end
RespawnItem = function(template)
local ent = ents.Create(template.className)
if not IsValid(ent) then
ErrorNoHalt("[custom_coop] Could not respawn " .. template.className .. "; retrying in 5 seconds\n")
timer.Simple(5, function()
if not suppressRespawns then RespawnItem(template) end
end)
return
end
-- Restore mapper-provided properties before Spawn. Position, angles and model
-- are restored explicitly because GetKeyValues does not always include them.
for key, value in pairs(template.keyValues or {}) do
if key ~= "classname" and key ~= "origin" and key ~= "angles" and key ~= "hammerid" then
ent:SetKeyValue(key, tostring(value))
end
end
ent:SetPos(template.position + Vector(0, 0, 3))
ent:SetAngles(template.angles)
if template.model and template.model ~= "" then ent:SetModel(template.model) end
if template.name and template.name ~= "" then ent:SetName(template.name) end
ent:Spawn()
ent:Activate()
if not IsValid(ent) then
ScheduleRespawn(template)
return
end
ent:SetSkin(template.skin or 0)
ent:SetMaterial(template.material or "")
ent:SetColor(template.color or color_white)
ent:SetCollisionGroup(template.collisionGroup or COLLISION_GROUP_NONE)
for id, value in pairs(template.bodygroups or {}) do
ent:SetBodygroup(id, value)
end
TrackItem(ent, template)
ent:EmitSound("items/suitchargeok1.wav", 60, 100, 0.35)
end
hook.Add("OnEntityCreated", "CustomCoopTrackMapItems", function(ent)
-- OnEntityCreated fires before keyvalues and map-created state are ready.
timer.Simple(0, function()
if IsRespawnable(ent) and ent:CreatedByMap() then
TrackItem(ent)
end
end)
end)
hook.Add("WeaponEquip", "CustomCoopRespawnCollectedWeapon", function(weapon)
local template = trackedItems[weapon]
if not template then return end
-- A newly acquired weapon becomes the player's inventory entity and is not
-- removed. Detach it and immediately start a new respawn cycle for its slot.
trackedItems[weapon] = nil
weapon:RemoveCallOnRemove("CustomCoopRespawnItem")
ScheduleRespawn(template)
end)
-- Prevent +use from dragging a map pickup away. Touch pickup still works, and
-- duplicate weapons are left to the engine's EquipAmmo path so they correctly
-- replenish reserve ammo and disappear only when ammo was actually accepted.
hook.Add("AllowPlayerPickup", "CustomCoopProtectMapItems", function(_, ent)
if trackedItems[ent] then return false end
end)
timer.Create("CustomCoopReturnDisplacedItems", 1, 0, function()
local maxDistance = math.max(respawnDistance:GetFloat(), 0)
local maxDistanceSquared = maxDistance * maxDistance
for ent, template in pairs(trackedItems) do
if not IsValid(ent) then
trackedItems[ent] = nil
elseif not IsValid(ent:GetParent()) and ent:GetPos():DistToSqr(template.position) > maxDistanceSquared then
SafeRemoveEntityDelayed(ent, 0)
end
end
end)
hook.Add("PlayerSpawn", "CustomCoopKeepWeaponsOnDeath", function(ply)
timer.Simple(0, function()
if IsValid(ply) then ply:ShouldDropWeapon(false) end
end)
end)
hook.Add("PreCleanupMap", "CustomCoopPauseItemRespawns", function()
suppressRespawns = true
trackedItems = {}
end)
hook.Add("PostCleanupMap", "CustomCoopResumeItemRespawns", function()
suppressRespawns = false
end)
hook.Add("ShutDown", "CustomCoopStopItemRespawns", function()
suppressRespawns = true
end)