Play the game at www.RainingChain.com.
Check the source code on Github.

Monday, 9 March 2015

Modularity

Modularity is a very important concept when creating a large-scale project. It is the main reason why so many projects fail as they reach a considerable size.

Modularity means to minimize the relations between modules (aka files) and to make sure modules perform a precise job. Doing so makes it easier to add new features but more importantly, makes it easier to remove or alter them.

Modularity is by no mean absolute. Making everything 100% modular is impossible and impacts negatively the performance. Classes need to interact between each others but you need to make sure they interact correctly. It is important when refactoring a project to first dress the list of what needs to be modular and what doesn't need to.

The first question to ask is what will grow in size as the HTML5 MMORPG grows and what is the most likely to change. Those need to be as modular and independent as possible to make changes as easy as possible. Most of the time in a game, the maps and quests are the main concerned elements.

Typical Quest and Map Structure:

File item
info about item for quest 1
info about item for quest 2

File npc
info about actor for map 1 quest 1
info about actor for map 1 quest 2
info about actor for map 2 quest 1
info about actor for map 2 quest 2

File map1:
actor1
modify questVariable of quest 1 when talked with
actor2
modify questVariable of quest 2 when talked with

File map2
actor1
modify questVariable of quest 1 when talked with
actor2
modify questVariable of quest 2 when talked with

File quest1
logic concerning questVariable

File quest2
logic concerning questVariable

Note: A real structure for a HTML5 MMORPG is a lot more complex than this.

This structure seems modular. The file item handles items. The file actor handles actors. The map files handle maps and quest handles quests. But there's still a big problem in this structure. There is no way to know what elements a quest depends on other than checking in every map files, every item files, every actor files etc...

When the game has less than 10 quests, it's not a big deal. But when the game has 100+ maps, 100+ quests, 100+ npcs, 200+ items, and you want to remove a quest, the task becomes really time consuming and most likely, some parts will be forgotten.

There is also a big naming issue. If you don't use prefix for quests, you may name an actor with the same name than another actor, causing very hard to detect errors. If you want to create a new quest that uses the questVariable killMonsterCount, you would need to check in every quest file and make sure it's not used yet.

In Raining Chain, I opted to put all the data needed by a quest in the same file. Every element (item, equip, ability, dialogue, npc, map, variable, highscore, challenge) created comes automatically with a prefix to prevent collision. Adding an element in an external map is also done within the quest files.

quest1:
actor quest1-1
map quest1-1
item quest1-1
item quest1-2

quest2:
actor quest2-1
map quest2-1
item quest2-1
item quest2-2
add actor to map quest1-1

Using this system makes it very easy to remove a specific quest. All you need to do is remove the file and everything related to it is gone. However, doing it that way creates a relation between the quest file and every part of the game engine. So in theory, it is not modular... A modification about game engine may end up forcing you to go through every quest file. This means a solid Quest API is required in order to minimize the impact of future game engine changes. I will cover the Quest API in another post.

Static Class vs Prototype

There are many ways to create object constructors in a project. The most conventional way is to use prototypes. Even though they are the most performant, they also have disadvantages and restrictions. For example, the use of "this" keyword in prototypes prevents multi-hierarchic function names such as Actor.prototype.status.update(). This is normally not a big deal though.

Like I said in another post, my project used to be entirely made with global variables. Obviously, I wasn't using a proper constructors system back then and when I made the transition to make the project module-based, I simply converted all my global functions as the equivalent of public static functions that always take the concerned object as the first parameter.

//Old (Global Variables)
updateActorStatus = function(actor){ actor.hp++; }

//New (Module-Based)
Actor.status.update = function(actor){ actor.hp++; }

//With prototypes it would look like:
Actor.prototype.updateStatus = function(){ this.hp++; }

The main inconvenient with static functions is that it complicates heritages. But considering Javascript is not strictly typed and the game was built with minimal heritage in the first place
makes this not really worth the trouble.

Then there's performance, but the constructors are not a big part of the CPU usage in the game.
I'd rather spend my time optimizing the collision, movement and boost systems.

In the other hand, static functions have a considerable advantage. It makes it very easy to search for a specific function. For example, if I want to know what files are impacted by the modification of the function Actor.remove, I can simply search for Actor.remove(.

However, if using prototype, the function is Actor.prototype.remove. Searching for .remove( will returns results concerning Actor but also drop.remove, bullet.remove, boost.remove, ability.remove, equip.remove, item.remove, map.remove etc...

Another advatange of static functions is that it enforces a proper dependencies system since you absolutely need the module Actor to call Actor.status.update. In the other hand, calling actor.updateStatus can be done without the module since the function comes with the object.

Preventing Socket.io DDOS

Raining Chain HTML5 MMORPG uses Socket io library for websockets. Even though it is a great library to handle websockets, it is very vulnerable to DDOS.

A player could open the console with F12, type
while(true) socket.emit('eventName',bigObject);
and crash your server (or at least slow it down).


This means you need to implement a system to disconnect a player that sends too much data.

On the server, instead of using:

io.on('connection', function (socket) {
 socket.on('eventId', function (data) {
  //stuff   
 });
 socket.on('eventId2', function (data) {
  //stuff2 
 });
});

Use

handleSocket = function(socket,eventId,data){
 if(Date.now() - socket.lastEventTimestamp < 5){
  socket.disconnect(); //optional
  return;
 }
 socket.lastEventTimestamp = Date.now();
 
 if(eventDb[eventId]){
  eventDb[eventId](socket,data);
 }
 
}
eventDb = {
 stuff:function(socket,data){
  //stuff
 }
 stuff2:function(socket,data){
  //stuff2
 }
}

This is the most simple system. One could implement different thresholds for every event and take into consideration the size of the data sent. Instead of disconnecting the socket when sending too fast, one could keep track of how many times it has happened and only disconnect if it happened more than 100 times in the last minute.