The Totally Final and Complete Horde Discussion Thread

This is a massive post, so I have put stuff in spoilers. Basically, while I love the idea of hordes, their current implementation needs a lot of work, and might actually need to be temporarily removed from the settings since they are quite broken at the moment.

What is the current state of hordes?

Ideally the current state of hordes is as follows:

  • Hordes are randomly generated initially on all city tiles. Non-spreading hordes are generated on things like electric substations and swamps
  • Hordes will randomly wander in different directions, if they can’t hear anything.
  • If they hear something (a very loud sound), they will tend to randomly wander towards that direction. Eventually they will give up, or reach the sound.

There are major problems with this implementation, but I will get to them later. First, these conditions are not currently being applied correctly

  • Initially, hordes are attracted (lightly) to a random spot between (-10 to 10), (-10 to 10). These are the ten tiles closest to the automatically generated spot in the top-left, with 29 interest. This is less interest than a gunshot, but not by much.
  • Hordes will move towards that point as fast as possible, never making detours (unless they encounter some other stationary hordes, such as swarms of dragonflies. Then their behavior becomes harder to observe.) They travel at 50 speed (although fast zombies may effect that, that is untested)
  • Hordes will never stop moving towards that point. The interest drops by 1 point every 5 minutes; but when it hits 15, it stops decreasing, and the hordes continue to move.
  • If interest hits 100, they stop moving, and interest never decreases
  • Occasionally, hordes will travel to the wrong tile: They don’t head towards the tile that made the sound, but another tile entirely. I am not sure on the specifics of this anomaly.

I haven’t recorded these under the ‘bugs’ section, because even if the issues outlined above were fixed, Bumpkin makes a major point about a major problem with hordes as they are ideally implemented:

And someone else (who I can’t find the quote for … sorry!) mentioned another major problem with hordes: The only sound that is ever created must happen within your reality bubble. Sound cannot be created elsewhere. So that means that if sound occurs, even if you don’t make the sound, the hordes would (should?) travel towards you

Now there are a few possibilities that I have thought of, but I am aiming to design a standalone horde system that doesn’t require us to simulate NPC’s lives. Thus, I need a design document.

A good implementation of hordes should:

  • Be able to be outrun
  • Be able to be fought
  • Be able to hide and be outlasted (Although random movement may lead the horde to you)
  • Remain a threat until the horde is killed (or a part of it is killed)
  • Travel to you, not you to it.
  • Not require constant checking of the overmap

So here is my suggestion of the ‘radically’ overhauled zombie hordes, that hopefully will not be too difficult.

[size=24pt]What makes a horde?[/size]

A horde has exactly one unique zombie: A zombie overmind. If the zombie overmind is killed, the horde will not function. The zombie overmind is not intellegent, per say, all it does is keep the zombies close to it, and tends to wander in straight lines longer than regular zombies (this does not need to be simulated at the ‘lower map’ level, but explains why it alone travels across multiple overmap tiles). It would probably fight as strongly as a regular Zombie, and be as tough as a zombie brute (not as tough as a hulk, however).

Each horde has a list of the number of zombies of each type in the horde (optional: health level of each zombie).

[size=24pt]What does a horde do?[/size]

I am not 100% sure how this will be implemented in code, so please tell me if any part of this is not possible.

When your reality bubble encounters new tiles that have a horde on it, each new tile discovered has a 1 in 576 (24 * 24) chance to spawn a zombie overmind. If it does, then all the zombies with the horde are spawned around it, as close as possible, in a random orientation. The horde has ‘descended’. Since the reality bubble is 90 tiles in radius (right?) and vision is 54 tiles, even in the worst case scenario, a horde would need to be nearly 1600 strong before zombies would ‘spawn’ in sight range.

Every hour, on the hour, hordes will move one map tile randomly. In addition, any zombie overminds that are not in the reality bubble will ‘ascend’ into a new horde on the overmap, along with any zombies in that overmap tile, or any of the eight adjacent tiles. (One issue with this is that all zombies, regardless of whether they were previously part of the horde, become part of the horde once you approach it. It may be possible to assign each zombie as a member of a certain horde, and if that is the case then do that. But otherwise, it is a reasonable price to pay for richer hordes)

If a horde moves over an overmap tile with zombies already on it, 20% (configurable) of the zombies on that tile will also ‘ascend’, and join that horde. This means zombies ‘trapped’ in certain circumstances will be able to get ‘freed’ by joining a horde, but this is a reasonable issue to allow, in exchange for richer hordes.

If a horde moves onto an overmap tile that is under the reality bubble, there is a chance proportional to the coverage of the reality bubble that the horde will descend on the edge of the reality bubble

Finally, how is this better?

  • Since hordes do not move towards sound, they are not naturally predisposed to seeking the player out
  • It does not require simulation of NPCs, while allowing the possibility for NPCs to react to hordes at an ‘overmap level’ level of simulation in the distant future.
  • Hordes pose a credible, and increasing, threat as the game progresses
  • There are no ‘cheesy’ ways to defeat a horde. You must either flee, fight, or hide. There are no other options, but many ways to implement those three fundamental methods.
  • It doesn’t break suspension of disbelief any more than the current method does.
  • The redesign of hordes will fix the bugs in the current implementation. (Although they may introduce new ones. Such is the price of computer programming…)

Thoughts? Suggestions? Improvements? Complaints? Accusations that the current system of hordes is entirely perfect as it is now, everything is working as intended, and that I am a n00b for suggesting this? Feel free to say them all :slight_smile:

I don’t like the idea of horde requiring an explicit overmind.

Horde could arise from some other factors, like zombies just making sounds and being attracted to sounds of other zombies.

For example, giving zombies the ability to moan (like shriek, but not loud) whenever they have any target (not just a close one) and sometimes just randomly would result in stumbling zombies naturally forming groups.

Overminds could have bigger hordes or buff them (make them move at 0.75 speed rather than 0.5) or something like that, but small “troops” like what spawns right now would be nice.

Honestly? I hate the idea of a “kill this zombie to win” overmind zed idea. It’s much closer to a triffid or fungal idea than a zombie one. I do, however agree with your basic design goals, so here’s my suggested “final-ish hordes implementation”:

[ul][li]Hordes are handled in smaller units, maybe 100 units max. Larger hordes are represented by several 100 unit “subgroups” stacked in nearby tiles.[/li]
[li]Several hordes start in cities at the start to represent the large amount of zeds at the very beginning of the game. After that they only wander in from off the edges of the overmap, not randomly appearing in the middle of areas.[/li]
[li]Hordes are attracted to a variety of things, sound being only one. These can be either attractive or repulsive, so you might have a zombie horde be attracted by loud noises while a deer herd is repulsed by them. Additionally hordes have a basic amount of “pull” or “push” based on various other horde types directly. For example zombies might have a slight “pull”, to better promote formation of larger hordes, while wolf packs might have a strong “push” against each other to encourage them to spread out. You could even have cross type pulls, like zombie hordes being pulled by living group.[/li]
[li]If a horde moves through a tile that has valid creatures in it, they have a chance to interact with those creatures, and based on a variety of factors will either pull in or leave some of their number behind. This could allow smaller hordes to “roll up” larger numbers when traveling through densely populated areas, while “scattering” members in their wake in lower populated ones. Different types of hordes could have different values for this, and could be defined in the jsons.[/li]
[li]Over time a horde will lose interest in chasing their target. If a horde has totally lost interest (due to no pulls happening near it recently), then it has to start making saves against fragmentation. Fail the saves and a portion of the horde drops out and becomes part of the static population of the area. The longer it sits without something to do and the more likely it becomes to fail.[/li]
[li]Each subgroup processes pulls individually. This means that huge hordes can break up into smaller ones by being pulled apart. Horde subgroups that are on the boundary between two pulls have to roll against being pulled apart and becoming part of the static population.[/li]
[li]If a horde subgroup drops below a certain population, be that through fighting, interacting with the static population, or through losing units due to fragmentation, then it has to make a save against being totally turned into a static monster group. If it falls below an even lower threshold then it is forced to become a static monster group, period.[/li]
[li]Ideally horde move speed should be the average of all the units in the horde, but we’ll probably need to use some heuristic or other to cut down the amount of processing that would take.[/li]
[li]Increased sound thresholds, and “fuzzy” goals based on distance. I might be able to hear a single shot from a few miles away in the woods, and tell you the direction, but I shouldn’t be able to home in on it like a missile, nor have the interest to get all of the way there.[/li][/ul]

The end result should be that cities start with large groups in them. These groups then go about roaming the countryside, scattering zombies in their wake as they go and fragment apart. Should one of the groups stumble upon a city it can potentially gain momentum from the zombies there before wandering out into the countryside again. If the player makes noise they might draw nearby hordes, but they can “go silent” for a while and the hordes will lose interest to be drawn away by other things. If the player goes toe-to-toe with a horde and takes out enough of them, the horde runs the risk of collapsing, and if they do enough damage the horde will be forced to collapse. Lastly the vast majority of calculations can be ran on a daily, seasonally, or yearly basis, which means that at the absolute worst we should be able to process only 1 group every step and still support like 14400 groups without any serious performance impacts.

Stick enough in the JSON and we can handle a lot of other things as well with the same framework. Here’s an example scenario:

[ol][li]A herd of deer frolics through the forest. Deer have a very strong resistance to fragmentation combined with a strong stickiness, so they pretty much won’t break up the herd as long as there are at least a few left, while any wild deer encountered are immediately added to the herd. Deer have a strong dislike of non-forest tiles, so they pretty much stay within the borders of their forest. Every spring the deer make reproduction rolls, and their group size also grows until it breaks 50 deer (the max herd size) at which point it splits into two herds. Deer herds have a slight repulsion to each other, so they soon go their separate ways.[/li]
[li]Squirrels don’t form groups large enough to count as hordes, so they are just a static population scattered randomly through the forest. Once a year the game looks at each overmap tile and rolls reproduction rolls for the various squirrel populations.[/li]
[li]A pack of wolves have been following the deer. Wolf packs have a fairly strong attraction to deer herds, while the deer have a slight aversion to wolves. Since the wolves are slightly faster this leads in the deer moving slowly around the forest while the wolves chase them and occasionally come into contact. Wolves interact with deer herds by making the occasional kill, thinning the deer population. They also interact with static squirrel populations, eating a handful as they pass by.[/li]
[li]A bear is nearby living in a cave. Bears like caves, and as such tend to stay in them when possible. It slowly lowers the squirrel population and will attack any deer herds that wander nearby, but pretty much ignores everything outside its small area.[/li]
[li]Meanwhile the player wanders in. They’re hungry, so they’ve been tracking this herd of deer down. Sneaking up on them, the player pulls out there gun and manages to take down two the deer while their noise sets the rest of the herd running with the wolf-pack in pursuit.[/li]
[li]The player gets an automated warning that it seems like a hostile group is moving into their area. Blast! A smaller zombie horde appears to have been attracted by the noise! On the other hand, it seems that one of the splinter herds of deer has decided to run straight into it’s waiting jaws. Figuring that that should distract the zeds, the player finds another cave (one luckily bear free!) and decides to hole up there for a few days while the hordes are passing through.[/li]
[li]The large zombie horde makes quick work of the small deer splinter herd, and come closer when the player notices something they missed on the first glance; the zombie horde has a zombie dog pack at the center! Zombie dogs hunt by scent, and have a strong pull on other nearby zombie groups, and without a river nearby or enough smoke to throw off the tail there isn’t much the player can do but prepare for the fight. Luckily this cave will make a good defensive area with just a little work.[/li]
[li]8 zombie dogs and 25 other assorted zombies later the horde is no more. It was a tough fight, and the player took a deep bite that will need some further care, but it was enough to totally fragment the remaining horde. The player might have to deal with a few seeded zeds in the surrounding area as they travel over the next few days, but at least the horde won’t be drawing any more larger groups after them. Maybe they might even be able to salvage some of the meat from the slaughtered deer herd, it’s only been a day or so.[/li][/ol]

Meanwhile the bloody, heat-seeking moose prepares for a surprise attack from it’s invisible animal group, having homed in on the player from all of the way across the map… NOT! :P. (Such a framework could allow for a hypothetical “tracker” unit that could do something similar though, maybe pulling hordes as it went to force you to either confront it or take drastic measures to throw it off of your trail).

@Cool and @i2amroy: I thought that the overmind would make a stark difference between ‘static’ and ‘mobile’ zombies, fit in reasonably well with the lore, and make it easier for the code to determine when and where hordes should be ‘ascended’ and ‘descended’. It would also give two broad ‘sub-options’ for the player to choose when going for the ‘kill’ option: Do I slowly pick off the edges of this horde? Or do I dive straight in to the middle of the fray and hope to get the Overmind? Killing the overmind won’t kill any of the other zombies, so you could still get surrounded … but it would stop them roaming around and bugging you! However, @i2amroy makes a good point in that that mechanic might thematically fit in better with the other types of organisms roaming the wastes.

i2amroy’s suggestion sounds very complicated, in that there is a lot of work to be done before all of those things are added. It would make a very good end-goal though. There is one issue though: When does a horde that has descended, ascend? Specifically, in story ‘7’ your example scenario (which is very, very good, I might add), what if it was just a regular horde of zombies, with a few special zombies? Since they have descended, it would be difficult to make them move in certain directions.

Although, now I think about it a little more, if we can group zombies in hordes as the hordes enter the reality bubble, we can also set a weak ‘weight’ on the random walks of the zombies. This would be set so that, assuming the entire reality bubble is quiet, the horde will appear to move at exactly the same speed. Over time, other zombies not in the horde would join the horde (in name), and the total number of zombies ‘really’ in the horde would be calculated based on the scattering mechanics i2amroy mentioned. Once the total number of zombies ‘really’ in the horde have left the reality bubble (or no zombies that belong to the horde in name exist in the reality bubble), the horde ascends back into the overmap, and travels on it’s merry way. If there are still zombies in the reality bubble (in name), they lose their spot in the horde, and become regular static zombies. If the horde fragments while ‘descended’, nothing special happens aside from the horde ‘direction weight’ disappearing.

Surprisingly a lot of that behavior can be done with just a handful of smaller tweaks to how hordes move (though, yes, some of it would take a little more work to get set up, and a lot of it would need to be finely tuned balance-wise, lest your hordes take a few steps and explode into static zombie groups, or become the roving bands of innumerability they are today).

Honestly I wasn’t thinking much of having hordes “ascend” based off of much. Zombies don’t reproduce, so pretty much the only new zed hordes you are going to be seeing are ones that wander in from off of the edge of the overmap (or that are in newly generated cities). Animals, on the other hand, go for more of a reproduction+fragmentation method, where groups grow in size over time until they split into two groups instead of one. Fungaloid and triffid groups could probably spawn occasionally from spires and triffid nurseries or whatnot, and it would probably be easy to make it so that it only happened when the population was over a certain threshold and to decrement the nearby static population accordingly. Then the idea is that as a horde moves it might “pick up” members from nearby static populations, which lets smaller hordes grow accordingly in population rich areas and works as a function to let “descended” hordes be picked up again and start moving; they just need somebody else to jump start them.

Honestly in-reality bubble calculations could be done pretty simply even without much work (though with a bit more we could do cool stuff like you mentioned). Just put a “horde id” number on each creature, then make each horde have an id. When the horde moves into the tile (be that a non-reality tile or a reality one) it handles any zombies joining or leaving the horde then, just working straight with the populations, no processing beyond simple lists needed. Then just weight all of the zombie random walks in the direction of the next horde goal space, and stash them back into the appropriate horde when they unload. The result would be the player would see a group of zombies that was kinda spread-out (though that could also be tweaked fairly easily) and that all travel together in the sam-ish direction across the map before disappearing.

Current notes for new hordes implementation https://gist.github.com/kevingranade/c57295f234bc9212e387
I’ve been really busy recently, so progress has been very slow, but this is my current big project.
I don’t see a good reason to remove the existing hordes implementation until it has a replacement, if you don’t think it’s good enough just don’t turn it on.
The most fundamental thing about the approach I’m following is that there are no ‘horde’ entities in the game, just individual monsters moving around based on various stimuli. There are no ‘horde zombies’ and ‘stationary zombies’, just zombies that are close, or zombies that are far away. The gist I posted outlines how I plan to handle AI and position updating for millions of creatures, in short, changing the scale of actions so that monsters act an order of magnitude less often than they do now, or less, and aggressively batching actions together so that when we look up map data, we get as much benefit out of it as possible, along with optimizing data structures to make lookups cheap.
In general outline, hordes just act like individual zombies, but on a larger scale. When a loud (by which I mean LOUD, no more attracting hordes by walking) noise occurs, the code will signal to all nearby zombies on the overmap , the zombies will get a volume and a direction, and will decide for themselves what to do from there.
Some behaviors I’d like to add include attraction to other zombies, attraction to human habitations, avoidance of large numbers of zombies, attraction to noises, avoidance of water, attraction to specific zombies (to clump up around necros and masters), attraction to light (only if pretty strong though), attraction to scent trails, and the opposite of most of the things I mentioned.
One major change is the transition from map to over map, right now if you get ~60 squares away from a zombie it is unloaded and you can just stop running, with this system I plan to keep the zombies moving in the same direction for some time or until distracted, so simply opening a gap between you and an enemy will not guarantee you escape. On the other hand this means once you break los, you can double back around your pursuers, making leading enemies away from somewhere easier.

That is actually a pretty clever system. Good luck with it.

I was not aware of that file, which seems pretty good. The only concern I have is that sounds on the overmap need to be more than flavor reasons. Other sounds need to be made to make hiding a viable strategy.

And it seems my idea of the overmind zombie won’t be needed if every zombie has the ability to move even outside of the reality bubble.

However … if a zombie is trapped, how are you going to manage that case? It would be impossible to trap a zombie in a certain position, as as soon as the zombie is unloaded, it would be able to teleport out (I assume you are not going to keep track of the movements of each zombie to the tile, as that would cause massive performance issues…)

That in particular is a very difficult problem, and one I don’t have an answer to.
Things we could trivially support:
Tiles explicitly marked as preventing exit, that could apply an immobilized condition to the monster that prevents it from moving on the overmap.
Prison cells within a single submap, on unload of the submap we could path from each monster to the border of the submap, since they are only 12x12 it’s easy and fast. Again if pathing failed we could apply an immobilized or ‘trapped’ condition.
Things it would be hard to support:
Prisons with building-sized cells, we’d need to examine adjacent submaps to decide if the monster can get out, which might not load and might not exist.
Things we probably can’t fully support:
Wall enclosures larger than the reality bubble, it becomes an intractable search problem if we have to follow every wall to decide if it encloses an area.

Everything I’m saying about prisons applies to walls that are overmap-scale as well, it’ll be tricky to let the player enclose a large area to either keep zombies in or out.