11.) MMO From Scratch - Iron Mines & Ground Items

In the last tutorial I complained about how I implemented inventory and coupled it to herbs. Today, we'll make some changes to the inventory and to the Slire Herb system, and we'll also implement two new features: Iron Mines and ground items.

The Server
I decided how I was going to implement every new game object. Basically, m.game.objects will be an object containing, well, game objects. Every game object will have it's own file. We already had one made for herbs, but I also made one for iron mines.In the objects extension, I'll manually add these game objects much like I do with plugins on the client.Update: In the objects extension, the files will be automatically shoved into the m.game.objects object.

Each game object will have 2 special properties: class and instance. The class property is assigned to the class for that object. For example, the herbs game object has a property 'class', that points to the Herbs class. Of course this is JavaScript so technically Herbs isn't a class, but whatever Herbs is, it's assigned to the class property. The instance property is assigned null, and is meant to be a place for an instance of the class property to sit on. I created a helper function named setupGameObj(obj, objClass) to set up these two properties for us./private/extensions/objects.js

There. Now anytime we make a .js file in the /private/extensions/objects directory, it will be automatically shoved into the m.game.objects object. It should be noted that the file doesn't have to end with .js, the match is satisfied if it see's a lower-case letter followed by '.js' anywhere in the filename. So a file named 'herbs0.js' or 'herbs_.js' or 'herbs_OLD.js' will be ignored, and a folder or non-JavaScript file named 'thisIsNot.jsFile' will be imported. The error thrown by the require function from an invalid module will crash the server more/less immediately after it's started, so this isn't a big deal to me. Further, the solution is to name things nicely, so I consider the side effect a feature rather than a known bug.

Moving on.

I coded /private/extensions/objects/IronMines.js very much the same way I coded /private/extensions/objects/herbs.js. I did have different needs though. Herbs are supposed to be created and deleted often, where iron mines are pretty much stationary. A player can't take an iron mine off of the map, nor may a player place one onto the map. This may be a feature for admins later down the road, but I can just as easily open up a mongo shell and create iron mines manually. Iron mines do need to be updated often, though. I decided on a 10 second replenishing time for now. I also decided Iron Mines should have a fixed amount of iron bits they can contain. I put this number at 128 for now. So every 10 seconds, the iron mine accumulates 1 additional iron bit. Once the iron mine accumulates 128 iron bits, it cannot produce any more until it is mined.

I wrote this code in /private/extensions/objects/mines/iron.js. Everytime the iron mine runs an interval that adds an iron bit, it calls a callback function. The IronMines class uses this callback to tell MongoDB to update that mine in the DB, and it also forwards this information to a self.cycleCallback. Like the Herbs class, IronMines requires a callback as the second argument, that is assigned to self.cycleCallback. Also like Herbs, self.cycleCallback may be changed at anytime after instantiation during the lifetime of the program.

I entered a mongo shell and created an iron mine:

Code:

db.mines.insert({name: 'iron', place: [300, 300], bits: 0});

Someday in the future, I'll have more metals/minerals to mine in this game. In the first version I want to release, the only type of mine will be an iron mine. This schema is intended to be future-compatible.

Now that we have an iron mine, I really want to get it on the game canvas! Before we can do that, we need to create a channel to send this mine at (300, 300) to the client. So I created a file named /private/channels/mines.js to communicate with the client.

It didn't take me long to figure out that I was checking if the state was 4 in just about every socket emition. Wherever I wasn't checking if it was state 4, it was most-likely an error. So I went into /private/extensions/socket.js and added a method to the session called state4Broadcast. It will cycle through every connection, check if the state is 4, and if so will emit the packet.

After I got that sorted out, I updated the herbs channel to use state4Broadcast instead of system events, and I feel much better about the style now. Events are cool and all, but I was using them for the wrong reasons in respect to herb operations. Then I went back to my /private/channels/mines.js file and continued working.

The users interact with mines to get iron bits- and that's it. So I added a socket event listener for 'iron-mined', that takes the id of the mine they attempted to mine from. I check that the state is 4 and that the iron mine with that ID actually exists, and I then check if the player is close enough to that mine to interact with it. Finally, I check if it was possible to add iron to the user's inventory, and if so I subtract the iron.bits by 1, and tell the mines instance about the change. Whether the iron bit was added to the inventory successfully or not, I send an update to all of the clients using my fancy new state4Broadcast emitter.

To ship the iron mines to the clients, I listen on the session's 'game-ready' event, and send the client all of the mines, tagged 'mines-init' . To tell the client when an iron mine's amount of iron bits changes, I send an 'iron-updated' emit using state4Broadcast.

Then, I was compelled to move the inventory to it's very own channel. So I created /private/channels/inventory.js. In the last tutorial I put the inventory communications in the herbs channel, but now I needed to generalize items a little bit. So I created 2 socket listeners- 'item-placed' and 'item-picked'. I also created a server-side groundItems object. For now, I decided not to store groundItems in the DB, but I will almost certainly use the DB for them later. A ground item has a schema like this:

The switch to a database will rely on the exact same schema, so little code should need to be changed. There is 1 condition at the moment, that will most certainly need to be changed once I add more herbs. I check if the name is 'slire', and if so I run the 'herb-planted' system event. When an herb is placed on the map, it needs to became an Herb instance so it can reproduce and be alive. Every other item at the moment isn't alive once it's placed.. Well, we only have iron bits and herbs right now.. Anyway, at some point I need to have a more educated mapping system that will determine what items should do when they are placed.

When a ground item is picked up, we check if the user is close enough to the item, is state 4, if the item really exists, if the user has inventory space, etc etc- then delete the ground item from the server's list and use our fancy state4Broadcast function to tell all the other users the item was removed. If the item couldn't be removed, then we tell the client who tried to pick it up that the item was created- as the client will optimistically destroy the ground item before it receives confirmation from the server.

That about sums it up for the server changes this round. Now let's see what I did to get mining and ground items working on the client!

The Client
On the client we update /public/js/inventory.js to work for more general items, rather than only slire herbs. I then create a file, /public/js/objects/mines.js. It works very much the same as /public/objects/herbs.js does in the last tutorial. The difference is, instead of removing the sprite from the game canvas when adding iron to the inventory, we just leave the sprite alone. I did make one condition for the appearance of the sprite, though. If there are 0 iron bits in the mine, I tint the sprite to a darker color to indicate that the mine has been exhausted. As soon as bits are available, the sprite is restored to a normal tint.

I used clever naming of files to get the inventory working for iron bits. The sprite for an iron mine is in /public/assets/game/ironrock.png. In the items folder- which is where the inventory grabs the image for items in the inventory, the name of the image is iron.png. Since the name of the mine is also iron, it works great. In order for ground items to work, though, we must create a plugin for them. So I created a file named /public/js/objects/groundItems.js.

Ground items must be initialized when the user first enters the game, and then may be placed/picked up by any player by clicking on them. Like anything else, the player must be close to a ground item in order to pick it up. The same rules do not apply for placing items- and that is a bug in the game we will soon fix.

Re: 11.) MMO From Scratch - Iron Mines & Ground Items

Hm. It appears the server never accepted your move. I'll start working on a better way to log things so I can figure out what happened next time. Right now I'm just baffled. It could be a "moving too fast" error, but that's purely speculation.

Well.. 50% of the users (not counting me) can move. And according to both of the logs posted here, people are trying to move and the server is rejecting the move and responding with the entry position- 250, 250. The reason for this is the server gets invalid lag data.

I calculate the lag using Date.now() on the client, and subtracting Date.now() on the server from the time passed by the client. So (psuedo code: server.epochTime - client.epochTime).

It occurs to me that this logic should work in theory, but is flawed in practice. Date.now() returns the Epoch time- that is, the amount of seconds since 1/1/1970. Now, according to the log above, your computer is almost 7 seconds ahead of my server in Epoch Time.

In theory that's impossible- but in practice, there are a lot of reasons why this is possible. I assume you're not living 7 seconds in the future, and I'll assume your connection to my server isn't 7 seconds faster than the speed of time. So I'm gonna take a shot in the dark here, and guess that either the server or the 50% of clients that cannot move, is a computer that is out of sync with Epoch Time.

The solution is... Well to be honest, I'm not using the lag data for anything anyway, so I'm checking it for no reason. I'll just delete some code and we'll see if that works.

I won't be uploading any code (this includes the fix for the issues with moving) until the next tutorial.

Originally Posted by Theo

Oh Man! i want give to you Beer! :-) this is realy perfect help for developers :) I inspired from it to our MMORPG Legend of Azulgar. Is here chance for any cooperation with you on our SCI-FI project?

Thanks, tell me more about the SCI-FI project- you can PM me so we don't drift off topic in this thread ;)