Skip to content

Commit

Permalink
Merge pull request #257 from GMLambda/npc-scaling-limit
Browse files Browse the repository at this point in the history
Add ability to limit scaled max live children and max npcs for spawners
  • Loading branch information
knoxed authored Jan 21, 2024
2 parents 11ddf27 + e4963fe commit ebfff4c
Showing 1 changed file with 130 additions and 74 deletions.
204 changes: 130 additions & 74 deletions entities/entities/lambda_npcmaker.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ SF_NPCMAKER_ALWAYSUSERADIUS = 256 -- Use radius spawn whenever spawning
SF_NPCMAKER_NOPRELOADMODELS = 512 -- Suppress preloading into the cache of all referenced .mdl files
local HULL_HUMAN_MINS = Vector(-13, -13, 0)
local HULL_HUMAN_MAXS = Vector(13, 13, 72)

function ENT:PreInitialize()
DbgPrint(self, "ENT:PreInitialize")
BaseClass.PreInitialize(self)
Expand All @@ -33,45 +32,94 @@ function ENT:PreInitialize()
self:SetInputFunction("Toggle", self.Toggle)
self:SetInputFunction("Spawn", self.InputSpawnNPC)
self:SetInputFunction("SetMaxChildren", self.SetMaxChildren)
self:SetInputFunction("SetScaledMaxChildren", self.SetScaledMaxChildren)
self:SetInputFunction("AddMaxChildren", self.AddMaxChildren)
self:SetInputFunction("SetMaxLiveChildren", self.SetMaxLiveChildren)
self:SetInputFunction("SetScaledMaxLiveChildren", self.SetScaledMaxLiveChildren)
self:SetInputFunction("SetSpawnFrequency", self.SetSpawnFrequency)

self:SetupNWVar("Disabled", "bool", {
Default = false,
KeyValue = "StartDisabled"
})

self:SetupNWVar("MaxNPCCount", "int", {
Default = 0,
KeyValue = "MaxNPCCount",
OnChange = self.OnChangedMaxValues
})

self:SetupNWVar("MaxLiveChildren", "int", {
Default = 0,
KeyValue = "MaxLiveChildren",
OnChange = self.OnChangedMaxValues
})

self:SetupNWVar("DisableScaling", "bool", {
Default = 0,
KeyValue = "DisableScaling",
OnChange = self.OnChangedMaxValues
})

self:SetupNWVar("SpawnFrequency", "float", {
Default = 0,
KeyValue = "SpawnFrequency"
})

self:SetupNWVar("LiveChildren", "int", {
Default = 0
})

self:SetupNWVar("CreatedCount", "int", {
Default = 0
})
self:SetupNWVar(
"Disabled",
"bool",
{
Default = false,
KeyValue = "StartDisabled"
}
)

self:SetupNWVar(
"MaxNPCCount",
"int",
{
Default = 0,
KeyValue = "MaxNPCCount",
OnChange = self.OnChangedMaxValues
}
)

self:SetupNWVar(
"MaxScaledNPCCount",
"int",
{
Default = 0,
KeyValue = "MaxScaledNPCCount",
OnChange = self.OnChangedMaxValues
}
)

self:SetupNWVar(
"MaxLiveChildren",
"int",
{
Default = 0,
KeyValue = "MaxLiveChildren",
OnChange = self.OnChangedMaxValues
}
)

self:SetupNWVar(
"MaxScaledLiveChildren",
"int",
{
Default = 0,
KeyValue = "MaxScaledLiveChildren",
OnChange = self.OnChangedMaxValues
}
)

self:SetupNWVar(
"DisableScaling",
"bool",
{
Default = 0,
KeyValue = "DisableScaling",
OnChange = self.OnChangedMaxValues
}
)

self:SetupNWVar(
"SpawnFrequency",
"float",
{
Default = 0,
KeyValue = "SpawnFrequency"
}
)

self:SetupNWVar(
"LiveChildren",
"int",
{
Default = 0
}
)

self:SetupNWVar(
"CreatedCount",
"int",
{
Default = 0
}
)
end

function ENT:OnChangedMaxValues()
Expand All @@ -88,7 +136,6 @@ function ENT:Initialize()
DbgPrint(self, "ENT:Initialize")
BaseClass.Initialize(self)
self:SetSolid(SOLID_NONE)

if self:GetNWVar("Disabled") == false then
self:NextThink(CurTime() + 0.1)
self.Think = self.MakerThink
Expand All @@ -101,6 +148,10 @@ function ENT:SetMaxChildren(data)
self:SetNWVar("MaxNPCCount", tonumber(data or 0))
end

function ENT:SetMaxScaledChildren(data)
self:SetNWVar("MaxScaledNPCCount", tonumber(data or 0))
end

function ENT:AddMaxChildren(data)
self:SetNWVar("MaxNPCCount", self:GetNWVar("MaxNPCCount", 0) + tonumber(data or 0))
end
Expand All @@ -109,6 +160,10 @@ function ENT:SetMaxLiveChildren(data)
self:SetNWVar("MaxLiveChildren", tonumber(data or 0))
end

function ENT:SetMaxScaledLiveChildren(data)
self:SetNWVar("MaxScaledLiveChildren", tonumber(data or 0))
end

function ENT:SetDisableScaling(scaling)
self:SetNWVar("DisableScaling", tobool(scaling))
end
Expand All @@ -119,21 +174,22 @@ end

function ENT:InputSpawnNPC()
DbgPrint(self, "ENT:InputSpawnNPC")

if not self:IsDepleted() then
self:MakeNPC()
end
end

function ENT:HumanHullFits(pos)
-- ai_hull_t Human_Hull (bits_HUMAN_HULL, "HUMAN_HULL", Vector(-13,-13, 0), Vector(13, 13, 72), Vector(-8,-8, 0), Vector( 8, 8, 72) );
local tr = util.TraceHull({
start = pos,
endpos = pos + Vector(0, 0, 1),
mins = HULL_HUMAN_MINS,
maxs = HULL_HUMAN_MAXS,
mask = MASK_NPCSOLID
})
local tr = util.TraceHull(
{
start = pos,
endpos = pos + Vector(0, 0, 1),
mins = HULL_HUMAN_MINS,
maxs = HULL_HUMAN_MAXS,
mask = MASK_NPCSOLID
}
)

return tr.Fraction == 1.0
end
Expand All @@ -145,7 +201,6 @@ end
function ENT:GetScaleCount()
if self:GetNWVar("DisableScaling") == true then return 0 end
if GAMEMODE.MapScript and GAMEMODE.MapScript.DisableNPCScaling == true then return 0 end

if self.PrecacheData ~= nil then
local class = self.PrecacheData["classname"]
if IsFriendEntityName(class) then return 0 end
Expand All @@ -161,8 +216,13 @@ end
function ENT:GetScaledMaxLiveChildren()
if self.CachedMaxLiveChildren ~= nil then return self.CachedMaxLiveChildren end
local maxLiveChildren = self:GetNWVar("MaxLiveChildren")
local maxScaledLiveChildren = self:GetNWVar("MaxScaledLiveChildren")
local scaledCount = self:GetScaleCount()
local res = math.Clamp(maxLiveChildren + scaledCount, 0, 100)
if maxScaledLiveChildren > 0 then
res = math.Clamp(res, 0, maxScaledLiveChildren)
end

self.CachedMaxLiveChildren = res

return res
Expand All @@ -171,8 +231,13 @@ end
function ENT:GetScaledMaxNPCs()
if self.CachedMaxNPCCount ~= nil then return self.CachedMaxNPCCount end
local maxNPCCount = self:GetNWVar("MaxNPCCount")
local maxScaledNPCCount = self:GetNWVar("MaxScaledNPCCount")
local scaledCount = self:GetScaleCount()
local res = math.Clamp(maxNPCCount + scaledCount, 0, 100)
if maxScaledNPCCount > 0 then
res = math.Clamp(res, 0, maxScaledNPCCount)
end

self.CachedMaxNPCCount = res

return res
Expand All @@ -182,7 +247,6 @@ end
-- This will check various conditions to see if we should consider distance.
function ENT:ShouldUseDistance()
local multiSpawn = false

if self:HasSpawnFlags(SF_NPCMAKER_INF_CHILD) == true then
multiSpawn = true
elseif self:GetNWVar("MaxNPCCount") == 1 and self:GetScaledMaxNPCs() > self:GetNWVar("MaxNPCCount") then
Expand Down Expand Up @@ -227,7 +291,6 @@ local ForcedNPCS = {

function ENT:CanMakeNPC(ignoreSolidEnts)
ignoreSolidEnts = ignoreSolidEnts or false

if self:IsDepleted() then
DbgPrint(self, "Depleted")

Expand All @@ -237,7 +300,6 @@ function ENT:CanMakeNPC(ignoreSolidEnts)
local maxLiveChildren = self:GetNWVar("MaxLiveChildren")
local liveChildren = self:GetNWVar("LiveChildren")
local scaledMaxLiveChildren = self:GetScaledMaxLiveChildren()

if maxLiveChildren > 0 and liveChildren >= scaledMaxLiveChildren then
DbgPrint(self, "Too many live children, live: " .. tostring(liveChildren) .. ", max scaled: " .. tostring(scaledMaxLiveChildren))

Expand All @@ -247,21 +309,21 @@ function ENT:CanMakeNPC(ignoreSolidEnts)
local pos = self:GetPos()
local mins = pos - Vector(34, 34, 0)
local maxs = pos + Vector(34, 34, pos.z)

if ignoreSolidEnts == false then
for _, ent in pairs(ents.FindInBox(mins, maxs)) do
if not ent:IsPlayer() and not ent:IsNPC() then continue end

if bit.band(ent:GetSolidFlags(), FSOLID_NOT_SOLID) == 0 then
-- This is used for striders because of the big bounding box,
-- NOTE: This is all based on monstermaker.cpp from the Source SDK
local tr = util.TraceHull({
start = self:GetPos() + Vector(0, 0, 2),
endpos = self:GetPos() - Vector(0, 0, 8192),
mins = HULL_HUMAN_MINS,
maxs = HULL_HUMAN_MAXS,
mask = MASK_NPCSOLID
})
local tr = util.TraceHull(
{
start = self:GetPos() + Vector(0, 0, 2),
endpos = self:GetPos() - Vector(0, 0, 8192),
mins = HULL_HUMAN_MINS,
maxs = HULL_HUMAN_MAXS,
mask = MASK_NPCSOLID
}
)

if not self:HumanHullFits(tr.HitPos + Vector(0, 0, 1)) then return false end
end
Expand All @@ -270,7 +332,6 @@ function ENT:CanMakeNPC(ignoreSolidEnts)

if self:HasSpawnFlags(SF_NPCMAKER_HIDEFROMPLAYER) then
local class = self:GetNPCClass()

-- Make sure we spawn friendlies and enforced npcs.
if ForcedNPCS[class] == nil and IsFriendEntityName(class) == false and util.IsPosVisibleToPlayers(pos) == true then
DbgPrint("Can not make NPC, maker is visible to player")
Expand All @@ -279,12 +340,10 @@ function ENT:CanMakeNPC(ignoreSolidEnts)
end

local closestDist = 999999

if self:ShouldUseDistance() == true then
for _, v in pairs(util.GetAllPlayers()) do
if v:IsFlagSet(FL_NOTARGET) then continue end
local dist = v:GetPos():Distance(pos)

if dist < closestDist then
closestDist = dist
end
Expand All @@ -299,9 +358,9 @@ function ENT:CanMakeNPC(ignoreSolidEnts)
end

function ENT:StubThink()
--DbgPrint(self, "ENT:StubThink", ent)
end

--DbgPrint(self, "ENT:StubThink", ent)
function ENT:MakerThink()
--DbgPrint(self, "ENT:MakerThink", ent)
if self.CachedPlayerCount ~= player.GetCount() then
Expand All @@ -322,18 +381,15 @@ function ENT:DeathNotice(ent)
end

self:SetNWVar("LiveChildren", self:GetNWVar("LiveChildren") - 1)

if self:GetNWVar("SpawnFrequency") == -1 then
self:MakeNPC()
end

if self:GetNWVar("LiveChildren") <= 0 then
self:FireOutputs("OnAllLiveChildrenDead", nil, self)

if self:HasSpawnFlags(SF_NPCMAKER_INF_CHILD) == false and self:IsDepleted() == true then
DbgPrint("All spawned NPCs are dead.")
self:FireOutputs("OnAllSpawnedDead", nil, self)

if self.OnAllSpawnedDead ~= nil then
self:OnAllSpawnedDead()
end
Expand Down Expand Up @@ -361,18 +417,19 @@ end
function ENT:ChildPostSpawn(ent)
-- TODO: Check if ent is stuck and remove it.
local maker = self

-- Usually the entities would do that based on npc_template_maker but we are not C++ the object where it could call it.
ent:CallOnRemove(self, function(npc)
if IsValid(maker) then
DbgPrint("NPC (" .. tostring(npc) .. ") dead, notifying npc_maker: " .. tostring(maker))
self:DeathNotice(npc)
ent:CallOnRemove(
self,
function(npc)
if IsValid(maker) then
DbgPrint("NPC (" .. tostring(npc) .. ") dead, notifying npc_maker: " .. tostring(maker))
self:DeathNotice(npc)
end
end
end)
)

-- HACKHACK: Some of the weapons appear to have EF_NODRAW set, that shouldn't be the case.
local wep = ent:GetActiveWeapon()

if IsValid(wep) then
wep:RemoveEffects(EF_NODRAW)
end
Expand All @@ -383,7 +440,6 @@ end
function ENT:UpdateScaling()
DbgPrint(self, "ENT:UpdateScaling", ent)
local maxCount = self:GetNWVar("MaxNPCCount")

if self:HasSpawnFlags(SF_NPCMAKER_INF_CHILD) == false and maxCount == 1 and self:GetNWVar("CreatedCount") == maxCount then
-- From this point on only spawn when not visible.
DbgPrint("Adjusted flags, hiding from player, CreatedCount == 1 and MaxNPCCount == 1")
Expand Down

0 comments on commit ebfff4c

Please sign in to comment.