OK, I’ll try to put it plainly.
Currently, the way NPCs work, the server just loops constantly, for absolutely every NPC, and just randomly moves them if no logic is required. If logic is required, then we update constantly with archaic pathfinding techniques.
As you can imagine, this is absolutely wasted power, to loop through EVERY NPC on EVERY map, as you can imagine, as more players go on different maps, processing power increases more and more. When we require pathfinding, that increases even more. Eventually, we end up with a huge portion of processing onto a part that doesn’t even need to be handled by the server about 90% of the time.
So, what can we do? Our options range from letting the client do all of the work, but obviously, we can’t do that. People will cheat. NPCs will never function properly. Peoples’ gameplay experiences will differ.
My solution is to generate and buffer a path for each NPC beforehand. When it’s needed, (how I handled it) a queue of about 100 numbers is generated, the numbers range from 0 to 3, 0 for left, 1 for up, 2 for right, 3 for down, so we can literally send a byte array, which is -tiny-.
Instead of sending when an NPC moves repetitively, we send the path to the client, and the NPCs initial position, and we let the client do all of the walking work.
OK, we have a sound theory, but the problem now is, different people will walk onto maps at different times, their NPCs will act differently! Where one person might be at the 45th step, a person joining starts from 1.
This is where the server comes in, alongside the path, the server also increments a single byte counter (or more, if you want varying walking speeds for NPCs). This counter keeps track of where we are in each and every NPCs step. So, we can send this out, meaning that clients that just join will be able to pick up where they were left off, and carry on with a relatively similar experience.
When we hit 100 (or however long your byte array is), we roll back to 0, and we generate a new path, and send it out. This means that the only processing for idle NPCs that the server does at this point is a single counter in a simple timer, and a basic procedure to just create a path
OK. Pretty good, but this doesn’t work for cases where players directly influence the NPC (ie. attacking/being sighted).
When this happens, I have a flag that determines whenever an NPC needs to be taken care of by the server or not. If it’s being attacked, or it’s staring down a player it wants to kill. At that point, I flag this true, and then the server sends out to all clients that their typical behaviour is being overridden. At this point, we process anything that needs to be processed, exactly as you have it now (well, it could be better, but more on that.).
When an NPC is done, we generate a new path for it, and it carries on as normal, sending out the new path and its now current position.
At this point, we require the following to be done by the server:
- Increment a single variable, and loop back to 0 every now and then. When we do loop, generate a new path.
- Check whether an NPC based on it’s interpolated position needs to chase a player. Set an override flag if this is the case.
- Check whether a player has attacked an NPC. Set an override flag if this is the case.
- If the override flag is on, process pathfinding to chase players, and attack as you normally would.
It’s a fairly complex model, but I’m sure it can be simplified, and I’m willing to bet that Stein will either comment on whether some of these actions are necessary/improve on them, but should you choose to to go ahead, you will see a notable decrease in what’s being flung back and forth by your server, and a respectable increase in your CPS (which of course, means everything. :p)