


Code: Select all
#include <sourcemod>
#include <sdktools>
static String:ZombieSpawn[1001][128];
new Handle:CanSpawn;
new Handle:Population;
new Handle:Frequency;
public Plugin:myinfo =
{
name = "Dynamic NPC Spawner",
author = "Alm",
description = "Spawns NPCs randomly around the map.",
version = "1.1",
url = "http://www.roflservers.com/"
};
stock PrepFile(const String:FileName[])
{
if(!FileExists(FileName))
{
decl Handle:File;
File = OpenFile(FileName, "w+");
CloseHandle(File);
}
}
public OnPluginStart()
{
PrepFile("cfg/zombiepopulations.cfg");
PrepFile("cfg/zombiefrequencies.cfg");
RegAdminCmd("z_addspawn", AddSpawn, ADMFLAG_CHEATS, "Creates a new zombie spawn where you stand.");
CanSpawn = CreateConVar("z_enabled", "1", "Determines if zombies will be spawned.");
Population = CreateConVar("z_population", "default", "Determines which zombie population to spawn.");
Frequency = CreateConVar("z_frequency", "default", "Determines how often zombies will spawn.");
new Refire = GetRefire();
CreateTimer(float(Refire), PluginLifeTimer);
HookEntityOutput("npc_zombie", "OnDeath", ZombieDeath);
}
public ZombieDeath(const String:output[], NPC, Killer, Float:Delay)
{
CreateTimer(0.1, HeadcrabAI);
}
public Action:HeadcrabAI(Handle:Timer)
{
decl String:Class[128];
for(new NPC = 1; NPC < 3000; NPC++)
{
if(IsValidEdict(NPC) && IsValidEntity(NPC))
{
GetEdictClassname(NPC, Class, 128);
if(StrEqual(Class, "npc_headcrab", false))
{
SetVariantString("player D_HT");
AcceptEntityInput(NPC, "setrelationship");
}
}
}
}
enum dirMode
{
o=777,
g=777,
u=777
}
public OnMapStart()
{
for(new Reset = 1; Reset <= 1000; Reset++)
{
ZombieSpawn[Reset] = "null";
}
decl String:DirMap[128];
GetCurrentMap(DirMap, 128);
decl String:TryDir[255];
Format(TryDir, 255, "cfg/%s", DirMap);
if(!DirExists(TryDir))
{
CreateDirectory(TryDir, dirMode);
}
new GotCount = 0;
decl String:EntClass[128];
decl Float:EntOrg[3];
for(new Ents = 1; Ents < 3000; Ents++)
{
if(IsValidEdict(Ents) && IsValidEntity(Ents) && GotCount < 1000)
{
GetEdictClassname(Ents, EntClass, 128);
if(StrEqual(EntClass, "info_npc_spawn_destination", false))
{
GotCount++;
GetEntPropVector(Ents, Prop_Data, "m_vecOrigin", EntOrg);
Format(ZombieSpawn[GotCount], 128, "%f %f %f", EntOrg[0], EntOrg[1], EntOrg[2]);
RemoveEdict(Ents);
}
}
}
if(GotCount < 1000)
{
decl String:MapName[128];
GetCurrentMap(MapName, 128);
decl String:WatFile[128];
Format(WatFile, 128, "cfg/%s/zombiespawns.cfg", MapName);
PrepFile(WatFile);
AddSpawnsFromFile(MapName);
}
}
stock GetSpawnCount()
{
new ValidSpawns = 0;
for(new Count = 1; Count <= 1000; Count++)
{
if(!StrEqual(ZombieSpawn[Count], "null", false))
{
ValidSpawns++;
}
}
return ValidSpawns;
}
public GetRefire()
{
decl Handle:File;
File = OpenFile("cfg/zombiefrequencies.cfg", "r");
decl String:SectionName[128];
GetConVarString(Frequency, SectionName, 128);
new bool:RightSection = false;
new bool:FoundLine = false;
decl String:FileLine[128];
FileSeek(File, 0, SEEK_SET);
while(!IsEndOfFile(File) && ReadFileLine(File, FileLine, 128))
{
TrimString(FileLine);
if(StrEqual(FileLine, "}", false))
{
RightSection = false;
continue;
}
if(StrEqual(FileLine, "{", false) || StrEqual(FileLine, "", false) || StrEqual(FileLine, " ", false))
{
continue;
}
if(!RightSection && StrEqual(FileLine, SectionName, false))
{
RightSection = true;
continue;
}
if(RightSection && StrContains(FileLine, "refire", false) == 0)
{
FoundLine = true;
break;
}
}
CloseHandle(File);
if(!FoundLine)
{
return 60;
}
decl String:Exploded[2][128];
ExplodeString(FileLine, "=", Exploded, 2, 128);
if(StrContains(Exploded[1], "-", false) == -1)
{
return StringToInt(Exploded[1]);
}
decl String:RandomPicks[2][128];
ExplodeString(Exploded[1], "-", RandomPicks, 2, 128);
return GetRandomInt(StringToInt(RandomPicks[0]), StringToInt(RandomPicks[1]));
}
public AddSpawnsFromFile(const String:MapName[])
{
new SpawnCount = GetSpawnCount();
decl String:WatFile[128];
Format(WatFile, 128, "cfg/%s/zombiespawns.cfg", MapName);
decl Handle:File;
File = OpenFile(WatFile, "r");
decl String:FileLine[128];
FileSeek(File, 0, SEEK_SET);
while(!IsEndOfFile(File) && ReadFileLine(File, FileLine, 128))
{
TrimString(FileLine);
SpawnCount++;
strcopy(ZombieSpawn[SpawnCount], 128, FileLine);
if(SpawnCount >= 1000)
{
CloseHandle(File);
return;
}
}
CloseHandle(File);
return;
}
public Action:AddSpawn(Client, Args)
{
if(Client == 0)
{
ReplyToCommand(Client, "[SM] Can't create new spawns from RCON.");
return Plugin_Handled;
}
if(GetSpawnCount() >= 1000)
{
ReplyToCommand(Client, "[SM] Spawn limit has been reached. (1000)");
return Plugin_Handled;
}
decl Float:ClientPos[3];
GetClientAbsOrigin(Client, ClientPos);
decl String:MapName[128];
GetCurrentMap(MapName, 128);
decl String:WatFile[128];
Format(WatFile, 128, "cfg/%s/zombiespawns.cfg", MapName);
new SpawnCount = GetSpawnCount();
SpawnCount++;
Format(ZombieSpawn[SpawnCount], 128, "%f %f %f", ClientPos[0], ClientPos[1], ClientPos[2]);
decl Handle:File;
File = OpenFile(WatFile, "a");
FileSeek(File, 0, SEEK_END);
WriteFileLine(File, "%f %f %f", ClientPos[0], ClientPos[1], ClientPos[2]);
CloseHandle(File);
ReplyToCommand(Client, "[SM] Added new zombie spawn. (%s)", ZombieSpawn[SpawnCount]);
return Plugin_Handled;
}
public GetSpawnAmount()
{
decl Handle:File;
File = OpenFile("cfg/zombiefrequencies.cfg", "r");
decl String:SectionName[128];
GetConVarString(Frequency, SectionName, 128);
new bool:RightSection = false;
new bool:FoundLine = false;
decl String:FileLine[128];
FileSeek(File, 0, SEEK_SET);
while(!IsEndOfFile(File) && ReadFileLine(File, FileLine, 128))
{
TrimString(FileLine);
if(StrEqual(FileLine, "}", false))
{
RightSection = false;
continue;
}
if(StrEqual(FileLine, "{", false) || StrEqual(FileLine, "", false) || StrEqual(FileLine, " ", false))
{
continue;
}
if(!RightSection && StrEqual(FileLine, SectionName, false))
{
RightSection = true;
continue;
}
if(RightSection && StrContains(FileLine, "spawns", false) == 0)
{
FoundLine = true;
break;
}
}
CloseHandle(File);
if(!FoundLine)
{
return 1;
}
decl String:Exploded[2][128];
ExplodeString(FileLine, "=", Exploded, 2, 128);
if(StrContains(Exploded[1], "-", false) == -1)
{
return StringToInt(Exploded[1]);
}
decl String:RandomPicks[2][128];
ExplodeString(Exploded[1], "-", RandomPicks, 2, 128);
return GetRandomInt(StringToInt(RandomPicks[0]), StringToInt(RandomPicks[1]));
}
public GetMaxNPCS()
{
decl Handle:File;
File = OpenFile("cfg/zombiefrequencies.cfg", "r");
decl String:SectionName[128];
GetConVarString(Frequency, SectionName, 128);
new bool:RightSection = false;
new bool:FoundLine = false;
decl String:FileLine[128];
FileSeek(File, 0, SEEK_SET);
while(!IsEndOfFile(File) && ReadFileLine(File, FileLine, 128))
{
TrimString(FileLine);
if(StrEqual(FileLine, "}", false))
{
RightSection = false;
continue;
}
if(StrEqual(FileLine, "{", false) || StrEqual(FileLine, "", false) || StrEqual(FileLine, " ", false))
{
continue;
}
if(!RightSection && StrEqual(FileLine, SectionName, false))
{
RightSection = true;
continue;
}
if(RightSection && StrContains(FileLine, "max", false) == 0)
{
FoundLine = true;
break;
}
}
CloseHandle(File);
if(!FoundLine)
{
return 1000;
}
decl String:Exploded[2][128];
ExplodeString(FileLine, "=", Exploded, 2, 128);
return StringToInt(Exploded[1]);
}
public GetNPCCount()
{
decl String:CurPop[128];
GetConVarString(Population, CurPop, 128);
decl Handle:File;
File = OpenFile("cfg/zombiepopulations.cfg", "r");
new bool:RightSection = false;
new TypeCount = 0;
decl String:Exploded[2][128];
decl String:FileLine[128];
FileSeek(File, 0, SEEK_SET);
while(!IsEndOfFile(File) && ReadFileLine(File, FileLine, 128))
{
TrimString(FileLine);
if(StrEqual(FileLine, "}", false))
{
RightSection = false;
continue;
}
if(StrEqual(FileLine, "{", false) || StrEqual(FileLine, "", false) || StrEqual(FileLine, " ", false))
{
continue;
}
if(!RightSection && StrEqual(FileLine, CurPop, false))
{
RightSection = true;
continue;
}
if(RightSection)
{
TypeCount++;
}
}
if(TypeCount == 0)
{
return 0;
}
decl String:NPCType[TypeCount+1][128];
new TypeCount2 = 0;
FileSeek(File, 0, SEEK_SET);
while(!IsEndOfFile(File) && ReadFileLine(File, FileLine, 128))
{
TrimString(FileLine);
if(StrEqual(FileLine, "}", false))
{
RightSection = false;
continue;
}
if(StrEqual(FileLine, "{", false) || StrEqual(FileLine, "", false) || StrEqual(FileLine, " ", false))
{
continue;
}
if(!RightSection && StrEqual(FileLine, CurPop, false))
{
RightSection = true;
continue;
}
if(RightSection)
{
TypeCount2++;
ExplodeString(FileLine, "=", Exploded, 2, 128);
strcopy(NPCType[TypeCount2], 128, Exploded[0]);
}
}
CloseHandle(File);
new FinalCount = 0;
new TestClass = 0;
decl String:EntClass[128];
for(new Ents = 1; Ents < 3000; Ents++)
{
if(IsValidEdict(Ents) && IsValidEntity(Ents))
{
GetEdictClassname(Ents, EntClass, 128);
if(StrContains(EntClass, "npc_", false) == 0)
{
ReplaceString(EntClass, 128, "npc_", " ", false);
TrimString(EntClass);
for(TestClass = 1; TestClass <= TypeCount; TestClass++)
{
if(StrEqual(EntClass, NPCType[TestClass], false))
{
FinalCount++;
}
}
}
}
}
return FinalCount;
}
public GetRandomZombieType(String:ZombieType[], stringlength)
{
decl String:CurPop[128];
GetConVarString(Population, CurPop, 128);
decl Handle:File;
File = OpenFile("cfg/zombiepopulations.cfg", "r");
new bool:RightSection = false;
new TypeCount = 0;
decl String:FileLine[128];
FileSeek(File, 0, SEEK_SET);
while(!IsEndOfFile(File) && ReadFileLine(File, FileLine, 128))
{
TrimString(FileLine);
if(StrEqual(FileLine, "}", false))
{
RightSection = false;
continue;
}
if(StrEqual(FileLine, "{", false) || StrEqual(FileLine, "", false) || StrEqual(FileLine, " ", false))
{
continue;
}
if(!RightSection && StrEqual(FileLine, CurPop, false))
{
RightSection = true;
continue;
}
if(RightSection)
{
TypeCount++;
}
}
if(TypeCount == 0)
{
return;
}
decl String:NPCType[TypeCount+1][128];
new TypeCount2 = 0;
FileSeek(File, 0, SEEK_SET);
while(!IsEndOfFile(File) && ReadFileLine(File, FileLine, 128))
{
TrimString(FileLine);
if(StrEqual(FileLine, "}", false))
{
RightSection = false;
continue;
}
if(StrEqual(FileLine, "{", false) || StrEqual(FileLine, "", false) || StrEqual(FileLine, " ", false))
{
continue;
}
if(!RightSection && StrEqual(FileLine, CurPop, false))
{
RightSection = true;
continue;
}
if(RightSection)
{
TypeCount2++;
strcopy(NPCType[TypeCount2], 128, FileLine);
}
}
CloseHandle(File);
new GotType = GetRandomInt(1, TypeCount);
strcopy(ZombieType, stringlength, NPCType[GotType]);
return;
}
public Occupied(Node)
{
decl String:NodePoints[3][128];
ExplodeString(ZombieSpawn[Node], " ", NodePoints, 3, 128);
decl Float:NodeOrg[3];
NodeOrg[0] = StringToFloat(NodePoints[0]);
NodeOrg[1] = StringToFloat(NodePoints[1]);
NodeOrg[2] = StringToFloat(NodePoints[2]);
decl Float:EntOrg[3];
decl String:EntClass[128];
for(new Ents = 1; Ents < 3000; Ents++)
{
if(IsValidEdict(Ents) && IsValidEntity(Ents))
{
GetEdictClassname(Ents, EntClass, 128);
if(StrContains(EntClass, "npc_", false) == 0)
{
GetEntPropVector(Ents, Prop_Send, "m_vecOrigin", EntOrg);
}
else
{
GetEntPropVector(Ents, Prop_Data, "m_vecOrigin", EntOrg);
}
if(Ents <= GetMaxClients())
{
if(GetVectorDistance(EntOrg, NodeOrg) <= 200)
{
return true;
}
}
else
{
if(GetVectorDistance(EntOrg, NodeOrg) <= 100)
{
return true;
}
}
}
}
return false;
}
public SpawnZombie()
{
decl String:ZombieType[128];
GetRandomZombieType(ZombieType, 128);
decl String:SpawnZombieType[128];
Format(SpawnZombieType, 128, "npc_%s", ZombieType);
new NodeCount = 0;
new CurrentNode = 1;
while(CurrentNode <= GetSpawnCount())
{
if(!Occupied(CurrentNode))
{
NodeCount++;
}
CurrentNode++;
}
if(NodeCount == 0)
{
return;
}
decl ChooseNode[NodeCount+1];
NodeCount = 0;
CurrentNode = 1;
while(CurrentNode <= GetSpawnCount())
{
if(!Occupied(CurrentNode))
{
NodeCount++;
ChooseNode[NodeCount] = CurrentNode;
}
CurrentNode++;
}
new RandomNode = ChooseNode[GetRandomInt(1,NodeCount)];
new Zombie = CreateEntityByName(SpawnZombieType);
decl String:NodePoints[3][128];
ExplodeString(ZombieSpawn[RandomNode], " ", NodePoints, 3, 128);
decl Float:NodeOrg[3];
NodeOrg[0] = StringToFloat(NodePoints[0]);
NodeOrg[1] = StringToFloat(NodePoints[1]);
NodeOrg[2] = StringToFloat(NodePoints[2]);
NodeOrg[2] += 15.0;
decl String:OrgString[128];
Format(OrgString, 128, "%f %f %f", NodeOrg[0], NodeOrg[1], NodeOrg[2]);
DispatchKeyValue(Zombie, "origin", OrgString);
new Float:Angle = GetRandomFloat(0.0, 359.9);
decl String:AngleString[128];
Format(AngleString, 128, "0 %f 0", Angle);
DispatchKeyValue(Zombie, "angles", AngleString);
DispatchSpawn(Zombie);
SetVariantString("player D_HT");
AcceptEntityInput(Zombie, "setrelationship");
return;
}
public Action:PluginLifeTimer(Handle:Timer)
{
if(GetConVarBool(CanSpawn))
{
new NewSpawns = GetSpawnAmount();
while((NewSpawns+GetNPCCount()) > GetMaxNPCS())
{
NewSpawns--;
if(NewSpawns == 0)
{
break;
}
}
if(NewSpawns > 0)
{
for(new DoSpawn = 1; DoSpawn <= NewSpawns; DoSpawn++)
{
SpawnZombie();
}
}
}
new Refire = GetRefire();
CreateTimer(float(Refire), PluginLifeTimer);
}
Heres the setup
ZOMBIES
The Dynamic NPC Spawner
Cvars
z_enabled : 0/1 Enabled or disable the plugin.
z_population: Chooses the population from which NPCs are spawned.
z_frequency: Chooses the frequency of which NPCs are spawned.
Admin Commands
z_addspawn: Adds a spawnpoint for an NPC to be spawned at. There can be a maximum of 1000 spawnpoints.
Info
Do you love spawning NPC's for your players to fight against, but hate the time it takes to spawn them, and turn on their AI? Don't you wish there was an easier way? Wish no more! With this plugin, you have the power to choose where NPCs spawn, when NPCs spawn, and which NPCs spawn, all without lifting a finger! (After the initial setup.) This plugin let's you create specific profiles for NPC populations and NPC spawning frequencies, then choose which profiles should be active. After that, the NPCs spawn at random spawnpoints, and turn on their AI automatically, creating surprise attacks for the players where they least expect them.
Setup
1. Add this plugin to your server, then make sure it activates, either by a server restart or a map change.
2. There should now be 2 cfg files in your server's FTP: cfg/zombiefrequencies.cfg, and cfg/zombiepopulations.cfg
3. Let's work on the populations first, so open zombiepopulations.cfg
4. To create a new profile, simply write the name of it on a new line. Let's begin with "default", as this is the profile the plugin looks for by default.
5. Once you have the profile name on it's own line, go down a line, and add a "{"
6. Go down one line further. Now you're ready to start adding NPCs that you want to spawn. Simply type an npc name (without the npc_) on it's own line to add that NPC to the population! Let's make our default profile spawn headcrabs and manhacks, so we make one line say "headcrab" and the next line say "manhack".
7. Let's close our profile with a "}" on the next line. Now we have our default profile done! Let's add another though. So go to the next line.
8. Let's name our next profile "headcrabs", because with this one, we'll spawn all 3 types of headcrabs!
9. So now that the profile name is "headcrabs", go to the next line, and put a "{", same as before. Now go down another line, and let's start adding in the NPC names.
10. We should add "headcrab", "headcrab_black", and "headcrab_fast", all with new lines in between.
11. Now close the profile with another "}". Great, we've made 2 population profiles! We can switch between the 2 with the cvar z_population, followed by either "default", or "headcrabs", without the quotes! Let's see if we've made the profiles correctly. Do they look like this?
Code:
default
{
headcrab
manhack
}
headcrabs
{
headcrab
headcrab_black
headcrab_fast
}
If yours looks like that, then you've got the hang of it!
12. Now let's work on setting up the frequency file, so open zombiefrequencies.cfg now.
13. Let's make another default profile, so make a line that says "default", then on the next line, put a "{".
14. Now each frequency profile has 3 values that need to be set. Refire, which is how often the timer fires that spawns the NPCs. Spawns, which is how many NPCs should be spawned every time the timer fires. Max, which is the maximum amount of NPCs that can be spawned with this plugin.
15. First, let's take care of refire. For our default profile, let's make the timer refire once every minute. So we would write this on a new line: "refire=60". Easy huh?
16. Next, let's take care of the spawns. Let's only make 1 NPC spawn every time the timer fires (every minute), so we would write this on a new line: "spawns=1".
17. Last, let's put the max. Servers have a way of crashing when there are thousands of NPCs spawned, so for the sake of the server, let's make the maximum amount of NPCs that are able to be spawned relatively low. Write this on a new line: "max=100". When the plugin realizes that 100 NPCs exist on the map, it won't spawn any new ones until some of the existing ones are removed or killed.
18. Finally close the profile with a "}". Now we have a frequency profile! But let's add another, so we can switch between them. Let's make this one a crazy one!
19. Go down a line, and name your new profile "apocolypse"! Add a "{" after like before.
20. This time, for refire, put it as: "refire=10-20". Notice how we have a hyphen now. This means that the timer will refire anywhere in between 10 seconds and 20 seconds. It could refire at 18 seconds, who knows? This is good for adding extra randomness to your NPC spawns, so the players don't know when the next NPC will be spawned.
21. Now go down a line, and this time for spawns, put it as: "spawns=2-5". Again with the hyphen! This means that any amount in between 2 and 5 can be spawned. At one timer fire, 3 could be spawned. At the next, 5. It's all random!
22. Now finally, let's do the max. Put it as: "max=300". Max can't use a hyphen, for obvious reasons. But this new maximum will let way more NPCs spawn!
23. Finally, close the profile with a "}". Let's recap, does it look like this?
Code:
default
{
refire=60
spawns=1
max=100
}
apocolypse
{
refire=10-20
spawns=2-5
max=300
}
If it does, then great work! You've finished setting up your config files! If you ever want to add more profiles, you know exactly how to now! You can also switch your frequency with the cvar z_frequency, followed by either "default", or "apocolypse", without the quotes!
24. Now that we have profiles ready, it doesn't mean NPCs can be spawned. Why you ask? They have nowhere to spawn! So what this means, is any admin with the cheats flag, has to go around the map setting spawnpoints. (NOTE: The entity: info_npc_spawn_destination acts as a spawnpoint.)
25. Simply type the command: z_addspawn into your console, and a spawnpoint is automatically created right where you are standing! (NOTE: If you want to use flying or hovering NPCs, it's smart to create the spawnpoints a little off the ground, so the NPCs don't spawn in the ground.)
26. So run or noclip around the map, using z_addspawn to create spawns where you want them!
27. Finally, if the cvar z_enabled is off, turn it on, and your NPCs will begin to spawn!
28. To change the NPC attack damage or health, use the built in sk_ commands.
Hope someone will try. Thank you,you guys do great jobs

