I didn’t expect to learn some new tricks while working on Dungeon of Loot. At first I thought I was just putting together some tricks from Golemizer but it turned out as a “real project” with its own challenges.

The first thing that was in my mind for some time was on which server I would run DoL. My experience with Golemizer told me that it requires something like a high-end desktop computer to handle a fair number of players online. That’s not bad as you could almost say I’m not using a “real” server yet. The problem is the $200 per month I need put in it. That’s not a lot BUT with my recent experience with Blimp Wars that makes the situation a bit different.

DoL is currently running on a cheap VPS used to host the forums, wiki and Joomla website of Golemizer. It’s already loaded enough but can handle a small version of the game. I tested the server a bit and created 2,000 monsters in a level just to see how it would do. So I stop the game and start it again to see the loading time … Terrible … It must have taken at least 1 hour before the game became available again. Great another problem.

I did the same test on my machine (as powerful as the server which Golemizer is running on) and exact same thing happened. The time to load all the NPCs is awful but why? It do just fine in Golemizer! Here’s the trick. In Golemizer I only load each zone on demand. When a player first enter a zone I load all items in it and then all NPCs. The zones in Golemizer are 20×10 and there are rarely more than 10 NPCs in a zone so the waiting time is acceptable.

In DoL there is about 40 NPCs in each level and there will be 100 levels (at first). That’s 4,000 AI I need to start at once because if I wait until a player enters a level to start the AI of 40 NPCs then it’s just too long. DoL is based around quick simple action so waiting 20 seconds when entering a zone just doesn’t cut it. But waiting 1 hour for the game to load after an update is also something I need to avoid.

Hopefully I found a solution. See in Golemizer each NPC is unique or must have the possibility to be unique. For example if I want to run a special event and have special needs I can hop in game and code in VB.NET a special AI for a monster. Since the AI of NPCs is in fact dynamically compiled VB.NET code it means that for every single NPC in the game a compilation occurs and that’s extremely time consuming. Well for 1 NPC it isn’t but for 40 it starts being a problem.

So since in DoL a goblin is a goblin and an orc is an orc, I don’t need unique AI for each NPC. I can just compile the AI for orcs once and then apply the same compiled code to all orcs. In fact most monsters have the exact same AI with different values (HP, range, xp, damage, …) so I could in fact just compile the AI code once and then set the variables I need to be different afterward. But since I like the possibility to have different behaviors from one NPC to another I’ll stick with 1 AI per NPC type.

With this simple trick I solved the long time it took to start the game and cut memory use by a lot. A lot … I did a quick test and the game with 4,000 NPCs runs on about 300 MB of RAM. One thing I’m still not sure to understand though is why it requires almost no CPU to run all these AIs. The CPU stays at 0% to 5% at most.

Next problem is that for DoL I want players to use the keyboard to move. Golemizer is a point and click game and it’s fairly easy to code. Since it’s not really action oriented it doesn’t matter too much if what you see on the screen is not exactly what’s happening on the server and the time a character takes to move from one tile to another is usually enough time for the server to send back the next action to take so it’s all fine.

But in DoL if you hit the left arrow you would expect your character to move left right now. That wasn’t the case. Just like in Golemizer all events were going through the server to get back to the client. Add to that the fact that I’m querying the server once every 200 milliseconds and it creates a very bad feeling of lag.

So the day I must give more freedom to the client finally came … Arggg … Making a character move client-side and detecting collision is easy. When a player enters a zone I simply send an array containing the walls with their width and height. I end up with a 2-dimensional array that I query to see if a character can move in a given direction or not. It has basically no performance issue even in Javascript. But that character must also move on the server. Easy? Well yes I just need to send an event when an arrow is pressed. I then send back to the client the actual position of the character to adjust it on the client-side just in case it doesn’t match. Since everything is happening fast enough even if you hit twice the arrows really fast (there is an artificial delay on the client to help) you rarely see your character go backward. In fact I never experience a situation where I felt something was really wrong.

So it’s all good? Not quite. While I load all AIs when the game starts I only start the AIs in a zone when a character is there to keep the pressure off the server. No need to run 4,000 AIs in 100 levels if there are players in only half of them. So when a character enters a zone I start all AIs. That creates a small delay that’s a bit annoying. I tweaked it a lot and now it’s much more better but when entering a zone you can sometime see your character going back to previous tiles. That only last for 1 second so it’s limited but the effect is there.

One thing I needed to learn a long time ago was to deal with asynchronous code execution. That means that when a character enters a zone I can just give an order to start the AI without the code having to wait for all AIs to be actually started. That should normally create no lag at all since I’m running the parts of the code in parallel. Most of the time it works without any problem but I recently discovered that in .NET I can start a maximum of 25 threads per CPU (each thread is code that runs at the same time) so that explained some of my problems. Even if I thought I was starting code that should execute at the same time I was reaching the 25 threads limit. When all the threads are taken all new threads are put in a queue and are waiting execution. That’s creating some weird problems sometime and that’s not the kind of stuff easy to debug. There are a lot of things I don’t quite fully understand when it comes to low level behaviors of processors and the .NET CLR but hopefully this time I’ve been able to improve the code to get around these new issues I’m aware of.

Enough of technical babbles. What about XP curve. Ah! Easy stuff isn’t it? Well I found this to be a bit more trouble than what I thought it would be. Finding the right combination of XP awarded by NPCs with the right XP progression is tricky. Well it’s not if you want your game to be a grind fest but if you’re looking to provide a nice feeling of progression you need to be careful about it. You also need to think about those people that might want to farm over and over the same monsters for XP. How do you handle XP awarded based on each monster and the level of players? If you like to play with Excel files then it’s probably you have no problem with. I don’t so much. I like to imagine systems but I hate to find formulas. That’s boring, tedious and kills the fun of systems. Too bad but you need those formulas.

For example here’s my formula I use to know how many XP are needed to reach the next level in DoL:

(([next level] * 25 / 1.2) * ([next level] * 0.6)) * ((([next level]-2) * 0.05) + 1)

Now if you think this formula is complicated just check the formula for WoW … Of course this formula doesn’t matter if I don’t have a nice way to fix how much XP I want each monsters to award on each level. On level 1 the monsters encountered award 5 XP. On each +2 level (level 3, 5, 7, …) the monsters award +5XP (so 10, 15, 20, …). With these values I end up with an XP curve that gets a bit more demanding on each level without having huge gaps. I might make the first few levels easier to get to provide a faster feeling of progression but it looks fine as it is. Not perfect but good enough to start with.

What happens when you kill a monster you are expected to defeat on level 1 but you are in fact level 2? Of course you don’t get the same amount of XP otherwise you might as well farm the easy monsters over and over until you are powerful enough to get through the harder content without trying. So the formula for XP awarded for each monster is:

ROUNDUP([base XP value] – ([current level]*1.5),0)

So the tougher the monsters are the more XP they will provide you once you have past the “expected level of encounter”. For example if you count on farming rats (expected level of encounter level 1) to get to level 100 you won’t be able. At most you will get to level 3 and then you will have to move to tougher monsters. Each monster will give you XP for a longer time (but less once you past the expected level of encounter) but sooner or later you will have to move to tougher monsters if you don’t want to kill 1,000 monsters to get to the next level.

Not sure that last part about XP made much sense but that’s just to tell that formulas are just brain masturbation that you have to get through so your idea makes sense. Hmm … Something like that.

So as you can see, even if DoL seems like a simple project there are quite some stuff there to have fun with. That’s actually fun because it’s a new challenge and that’s new stuff I wasn’t familiar with. I know that all of that have been done a hundred times both in computer games and paper games but I wanted to test myself to see if I was able to come up with a solution. So far so good.

I’m almost done with levels and monsters so the next step will be to complete all the loot you can get in there. That will be the fun part because if I feel I’m making the players too powerful I will then have a nice reason to make everything tougher :) Now that I have nice tools to work with updating stats of monsters can be done in a matter of a few minutes. And if I stick to my formulas I can have a good sense of if I’m right or wrong.

Now back to work. It will make a lot more sense once the game is finally available in its final form.

Share