174 lines
5.4 KiB
Lua
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)
|