Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to limit scaled max live children and max npcs for spawners #257

Merged
merged 1 commit into from
Jan 21, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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