Ok, so in this post I will outline how to set up a consumer using the rabbitmq erlang client.
We'll start with a basic chat server in which clients can post and listen for messages.
Continuing from my last post: http://developingthedream.blogspot.com/2009/10/rabbitmq-erlang-client-yay.html
You now have a basic project skeleton to start building your server. We'll call the project chat_server.
I followed this blog post http://mutlix.blogspot.com/2007/10/amqp-in-10-mins-part3-flexible-routing.html for a visual representation of the AMQP specs,
although it's outdated the basics are the same.
So, the project would look like this:
chat_server/
chat_server/db
chat_server/deps
chat_server/ebin
chat_server/include
chat_server/src
Now inside the src/ directory, we'll create these files:
src/master.erl
src/chat_server.erl
src/test_client.erl
I made a mistake in my last blog post regarding how to properly symlink the rabbitmq dependencies.
Unlink any previously created symlinks:
unlink /usr/lib/erlang/lib/rabbitmq_common
unlink /usr/lib/erlang/lib/rabbitmq_erlang_client
Now create some symlinks in the deps folder:
cd deps/
ln -s rabbitmq-server/ rabbit_common
ln -s rabbitmq-erlang-client/ rabbitmq_erlang_client
Here's the content of the Makefile we'll be using.
.SUFFIXES: .erl .beam
.erl.beam:
erlc -W $<
ARGS = -pa ebin \
-pa deps/rabbit_common/ebin \
-pa deps/rabbitmq_erlang_client/ebin \
-boot start_sasl -s rabbit
ERL = erl ${ARGS}
ERLC = erlc -I . -I deps +debug_info -o ebin
MODS = src/master.erl src/chat_server.erl src/test_client.erl
all:
${ERLC} ${MODS}
run:
${ERL}
clean:
rm -rf *.beam ebin/*.beam src/*.beam erl_crash.dump
We'll start with the master.erl file which basically sets up the exchange, queues, etc.
Mainly bootstraps the server.
Here is the content of master.erl:
-module(master).
-include_lib("deps/rabbitmq_erlang_client/include/amqp_client.hrl").
-export([start/0]).
start() ->
RID = amqp_connection:start_direct(),
Channel = amqp_connection:open_channel(RID),
X = <<"global_chat_exchange">>,
Q = <<"global_message_queue">>,
Key = <<"global_message_queue_publish_key">>, %%our routing key, all clients have this
amqp_channel:call(Channel, #'exchange.declare'{exchange = X, type = <<"topic">>, nowait = true}),
amqp_channel:call(Channel, #'queue.declare'{queue = Q}),
amqp_channel:call(Channel, #'queue.bind'{queue = Q, exchange = X, routing_key = Key}),
io:fwrite("bound queue: ~p to exchange: ~p using key: ~p~n", [Q, X, Key]),
amqp_channel:close(Channel),
amqp_connection:close(RID).
Here's the breakdown of the start() function.
I'm not doing any sort of error checking, take a look at deps/rabbitmq_erlang_client/test/test_util.erl
for more complete code.
First, we connect to rabbitmq.
RID = amqp_connection:start_direct(),
We simply call start_direct in the amqp_connection module and receive the connection id. (this is just the id of an erlang process)
Next, we open a channel which is pretty straightforward.
Channel = amqp_connection:open_channel(Pid),
Afterward, we bind the queue to the exchange using a routing key.
So here's our names:
X = <<"global_chat_exchange">>,
Q = <<"global_message_queue">>,
Key = <<"mysecret">>, %%our routing key, all clients have this
amqp_channel:call(Channel, #'exchange.declare'{exchange = X, type = <<"topic">>, nowait = true}),
amqp_channel:call(Channel, #'queue.declare'{queue = Q}),
amqp_channel:call(Channel, #'queue.bind'{queue = Q, exchange = X, routing_key = RoutingKey}),
Here we tell the erlang rabbitmq client to declare the exchange.
You can look at the file: deps/rabbit_common/include/rabbit_framing.hrl for all the record definitions.
You can specify things such as the exchange type and other things.
Now, first we'll start by compiling this module manually to go over the erlang shell.
Run 'make run' to start the rabbitmq server. You'll be greeted with the shell.
Compile the master module by typing:
c('src/master').
You can now run the code like so:
master:setup().
Now we're ready to start consuming messages. So, the content of the first draft of chat_server.erl is:
-module(chat_server).
-include_lib("rabbitmq_erlang_client/include/amqp_client.hrl").
-compile(export_all).
start() ->
RID = amqp_connection:start_direct(),
Channel = amqp_connection:open_channel(RID),
Queue = <<"global_message_queue">>,
spawn( fun() -> consume_loop(RID, Channel, Queue) end ),
self().
consume_loop(RID, Channel, Q) ->
amqp_channel:subscribe(Channel, #'basic.consume'{queue = Q}, self()),
receive
#'basic.consume_ok'{} ->
io:fwrite("subscribed to queue: ~p listening for messages...~n", [Q])
end,
receive
{#'basic.deliver'{delivery_tag=Tag}, Content} ->
amqp_channel:cast(Channel, #'basic.ack'{delivery_tag = Tag}),
handle_message(RID, Channel, Content),
consume_loop(RID, Channel, Q);
Unknown ->
io:fwrite("unknown message: ~p tearing down~n", [Unknown]),
teardown(RID, Channel)
end.
handle_message(RID, Channel, Content) ->
io:fwrite("got message: ~p from pid: ~p on channel: ~p ~n", [RID, Channel, Content]),
todo.
teardown(RID, Channel) ->
amqp_channel:close(Channel),
amqp_connection:close(RID).
Here we open a channel and spawn a process to consume messages from the 'global_message_queue' queue.
You've seen the start of the code in master.erl.
Queue = <<"global_message_queue">>, %%Here we declare the queue name to be passed around
spawn( fun() -> consume_loop(RID, Channel, Queue) end ), %%spawn a process and return immediately
self(). %%return our process id (unused)
Here we spawn a process that loops forever consuming messages from the 'global_message_queue'
consume_loop(RID, Channel, Q) ->
amqp_channel:subscribe(Channel, #'basic.consume'{queue = Q}, self()),
receive
#'basic.consume_ok'{} ->
io:fwrite("subscribed to queue: ~p listening for messages...~n", [Q])
end,
Here, we start the loop by telling the server that we're ready to consume on the queue 'Q' by calling amqp_channel:subscribe.
We immediately recieve a confirmation message and continue.
receive
{#'basic.deliver'{delivery_tag=Tag}, Content} ->
We wait for an erlang type message which contains a valid amqp message.
We save the payload to the variable 'Content' and the delivery tag to 'Tag'.
amqp_channel:cast(Channel, #'basic.ack'{delivery_tag = Tag}),
We immediately acknowledge that we've received the message.
You may wish to move this after you've processed the message.
handle_message(RID, Channel, Content),
consume_loop(RID, Channel, Q);
We send the content to the handle_message() function and then loop again to wait for messages again.
Unknown ->
io:fwrite("unknown message: ~p tearing down~n", [Unknown]),
teardown(RID, Channel)
end.
If we get an unknown message for any reason we exit and tear down the channel and connection.
The handle_message() function simply prints the content of the message to the screen.
Now you can compile the module like so:
c('src/chat_server').
Start the consumer process by typing:
chat_server:start().
Now we can start publishing messages, this is the content of test_client.erl:
-module(test_client).
-include_lib("deps/rabbitmq_erlang_client/include/amqp_client.hrl").
-export([say/1]).
say(Msg) ->
RID = amqp_connection:start_direct(),
Channel = amqp_connection:open_channel(RID),
X = <<"global_chat_exchange">>,
Key = <<"global_message_queue_publish_key">>,
Packet = list_to_binary(Msg),
Publish = #'basic.publish'{exchange = X, routing_key = Key, mandatory=true, immediate=false},
amqp_channel:call(Channel, Publish, #amqp_msg{payload = Packet}),
amqp_channel:close(Channel),
amqp_connection:close(RID).
First we turn the message into a binary for delivery:
Packet = list_to_binary(Msg),
Here we simply turn the message to a binary.
Publish = #'basic.publish'{exchange = X, routing_key = Key, mandatory=true, immediate=false},
amqp_channel:cast(Channel, Publish, #amqp_msg{payload = Packet}),
First, we declare a variable 'Publish' which sets up the publish options.
Then we call 'cast' instead of 'call' simply because we WANT to wait for the message to be published.
The reason we want to wait is because we immediately close the channel.
Now you can compile the module by running:
c('src/test_client').
And test it by running:
test_client:say("hello world!").
You should now see the output from your consumers which have read the message.
This concludes this introduction to using the rabbitmq-erlang client.
You can find the full test project available at: http://github.com/therevoltingx/chat_server
Any comments, questions, etc are welcomed!
7/28/2010
7/22/2010
As The Grind Continues
After releasing a super alpha version of the game so far, I've just been grinding along.
Nothing entirely interesting has happened in the last month, here are some of the things improved and/or added:
Fusing together its old school battle system with my own vision of the game (and with the limitations of man power and working with Android.)
Battling is turn based, like an animated card game.
Each player or monster takes a turn. (Attack, Defend, Special Attack, etc)
Everyone takes turns, according to different stats, damage is dealt and players are chosen for attack. (Level, Speed, Defense, etc)
An animation is played for each attack and players can attack or do things like defend or use items.
At this point many things are finished, but the battle system is still in a rough stage.
I've gone to pains to keep a clean code base though, so I can tweak the settings as I see fit.
So far, for the battle system I've written:
Nothing entirely interesting has happened in the last month, here are some of the things improved and/or added:
- Stabilized name display and tile animation
- Added team functionality
- Added personal messaging functionality
- Battle against monsters [Draft]
Fusing together its old school battle system with my own vision of the game (and with the limitations of man power and working with Android.)
Battling is turn based, like an animated card game.
Each player or monster takes a turn. (Attack, Defend, Special Attack, etc)
Everyone takes turns, according to different stats, damage is dealt and players are chosen for attack. (Level, Speed, Defense, etc)
An animation is played for each attack and players can attack or do things like defend or use items.
At this point many things are finished, but the battle system is still in a rough stage.
I've gone to pains to keep a clean code base though, so I can tweak the settings as I see fit.
So far, for the battle system I've written:
- Erlang:
- A single module/process handles all the battle systems such as creating the message queues.
- Periodically stores to global database and keeps track of handling turns
- Stores and executes monster battling action patterns
- Waits and notifies players when a turn is taken
- (566 lines of code.)
- Java/Android:
- A dialog activity is launched when it's notified a battle has started
- Bootstraps battle UI, downloads resources accordingly
- Listens for events and displays battle animations
- Have the player select a monster and take attack, etc. (TODO)
- The main class is 481 lines of code so far, but a lot of code is in its own class, and I'm lazy.
- Perl/HTML/SQL:
- Add data schemas for new battle system components (attacks, teams, monsters, battles, etc)
- Add to the existing admin functionality the necessary components
- This includes a web UI for managing: monsters, parties, attacks, backdrops, etc.
- The Client->Server Battle module is only 63 lines so far.
- The Admin->Monster Management module is 263 lines, (fairly complete.)
There is still plenty of work to be done, no one said it would be easy. I think the battle system is one of the most challenging aspects of making any game. I hate math :p
6/21/2010
Super Alpha Test (Tech Demo)



UPDATE: Due to major updates in the server, this client no longer works. Stay tuned for further releases!
Hi all,
Today I am releasing a super alpha version of the game so far.
However, it's still not much of a game. This is more like a tech demo.
This is mainly to get some very early feedback on what I've got so far,
to gauge the interest in it.
I also hope to pull in a bunch of testers to help me further develop the multiplayer aspect of the game.
Please note that it's still really buggy and unpolished, I also don't have a production server and am using my development server.
So expect plenty of downtimes and broken server communication.
Also, the game was developed with an Android 1.5 (stock G1) and doesn't fully work with 2.0+ just yet.
Make sure you have TasKiller ready, you'll need it.
So with that said:
You can download the latest alpha apk here.
Use the signup word: 'douchebag'
Features so far:
Signing up for an account
Camera movement via trackball
Touch based movement, click on the tile where you want to move to.
Area to area teleportation, just step over some of the NPCs and you'll get teleported. (not polished)
NPC/Event/Player activation. Long touch a player or npc for the player menu to come up.
Tile animation, check out the water move.
Remote player display and movement (not fully polished)
Character name display (though it hurts FPS, it'll be optional)
Zone chat
All multiplayer enabled
Feedback, Comments, Questions, Testers, etc:
therevoltingx@gmail.com
Labels:
android,
apk,
features,
g1,
mmorpg,
non-technical,
release,
rpg,
screenshot
5/21/2010
Fun Shot!


Pressing forward, I've added a few features to my custom map editor for my game.
Including:
* Better toolbar icons (for the meantime)
* Muti-tile select/paint
* Eraser
* Area/Layer Clear
* Layer VIew Toggling - Alpha blending support in wxWidgets is weak :-(
* Toggle view grid and tile indexing/numbers information
This will help me make maps for the game.
I've also started to use the excellent Inquisitor RMXP tileset.
Here's a couple of shots of a map I designed using that tileset and the new features I've added.
Enjoi!
Labels:
android,
c++,
editor,
g1,
mac os x,
non-technical,
screenshot
5/14/2010
On The Road to an RPG
During the past couple of weeks I've been thinking how I wanted to handle the RPG aspect of the game.
Since I come from an RPG Maker background, I'm familiar with the event/switch system it implements.
However, bringing this into an MMO aspect was a bit of a puzzle. The answer turned out to be simple, and once I got all the details together I set forth into making a scripting engine for the game.
The scripting engine uses JavaScript and it aims to resemble an RPG Maker functionality in script form.
It was also challenging figuring out how it could handle many players at once, from the rough tests it seems that i can handle the load just fine, but only time will tell.
I'm not sure I should even be blogging about this, since it's part of the backend server and the project is not open source.
However, I find it too exciting not to talk about.
So, now that I'm able to sew together an RPG I've started 'prototyping' the game using RPG Maker XP.
The graphics that RMXP uses are pretty much identical to the ones I've designed to be used by my game. This will make finding an artist and transferring ideas much easier.
There is a mountain of work to do with the software, but I'm working on it. However, it would be great to get some help with mapping, scripting, quest designing and story brain storming. I'm also in talks with some artists that have offered to make the art (tilesets, character sets, sprites, etc.) for the game, but nothing too serious yet.
Since I come from an RPG Maker background, I'm familiar with the event/switch system it implements.
However, bringing this into an MMO aspect was a bit of a puzzle. The answer turned out to be simple, and once I got all the details together I set forth into making a scripting engine for the game.
The scripting engine uses JavaScript and it aims to resemble an RPG Maker functionality in script form.
It was also challenging figuring out how it could handle many players at once, from the rough tests it seems that i can handle the load just fine, but only time will tell.
I'm not sure I should even be blogging about this, since it's part of the backend server and the project is not open source.
However, I find it too exciting not to talk about.
So, now that I'm able to sew together an RPG I've started 'prototyping' the game using RPG Maker XP.
The graphics that RMXP uses are pretty much identical to the ones I've designed to be used by my game. This will make finding an artist and transferring ideas much easier.
There is a mountain of work to do with the software, but I'm working on it. However, it would be great to get some help with mapping, scripting, quest designing and story brain storming. I'm also in talks with some artists that have offered to make the art (tilesets, character sets, sprites, etc.) for the game, but nothing too serious yet.
Labels:
features,
java,
javascript,
mmorpg,
non-technical,
overview,
rm2k
4/22/2010
Let's Chat!


After taking a brief vacation, I'm back working on my game.
I was heavily inspired by the Android game 'Pocket Empires' which I think is brilliantly designed.
Integrating the chat involved working with Perl, Erlang, and Java all at once, but I got it mostly done in less than a week.
Most of the time was wasted fiddling with the Android UI toolkit.
So here's a screenshot of the rough results, of course multiplayer enabled!
3/17/2010
New Client Screenshot

After a lot of work on the game's multi-player code, I've seemed to have wrapped up most things regarding real-time movement.
Though it's not perfect, it's working quite well, so I'm posting a screenshot with 3 players on the screen at the same time.
Videos should be coming as soon as I get some time.
Labels:
android,
g1,
mmorpg,
rpg,
screenshot
2/19/2010
Status Report 2/19/10
So, after a good 6 months of development for my game it seems that things are coming along nicely.
So here's a status of how things are going:
Android 2d Engine (Completed Features):
Android 2d Engine (Upcoming Features):
Master Server (Completed Features)
Master Server (Upcoming Features)
So here's a status of how things are going:
Android 2d Engine (Completed Features):
- Loading areas and displaying rendering to screen
- Independent frame-rate animated tiles/sprites
- Pixel based camera movement
- Character/Object loading/rendering
Android 2d Engine (Upcoming Features):
- Pixel based character/movement inside the area (DONE!)
- Character idle animation
- Render strings. (i.e. player names/levels/status)
- Loading previously saved areas.
- Editing and saving areas.
- Editing tileset properties (Tileset Editor).
- Works on Mac OS X, Linux, and Windows.
- No immediate upcoming features.
Master Server (Completed Features)
- Validating users
- Pathfinding character movement
- Track object/character positions/status/collisions
Master Server (Upcoming Features)
- Save player information
12/30/2009
Subscribe to:
Posts (Atom)