Subscription Based Message Router
by Thomas \"Man of Ice\" Lund · 06/28/2004 (8:37 am) · 42 comments
Download Code File
Changelog
27th June 2004
posted resource
28th June 2004
made code handle re-entrancy by queueing messages while system is sending
fixed missing platform import statement
1st of July 2004
Due to request, I rewrote the initial parts of this resource to better explain what this is
1st of August 2004
Added object reference to the callback
7th of August 2004
Added a data field and an example "messaging trigger"
30th of October 2005
Bugfixes to prevent crashes on deleting subscribers during sending
SimObjects can now subscribe and send instead of ShapeBase objects
Rewrote script API
21th of September 2007
Wow - new update!
Fixed spelling mistake in receive vs. recieve
Partially rewrote the code to get rid of the internal delete queue. This fixes a bug where ojects could delete other objects that also were subscribers, and is more pleasing + logical.
Also fixed tiny bug with wrong error message in subscribe method
Subscription what? What the hell is this? What can I use it for?
OK OK - read through this, and you will know that you can use this in your game. Its almost guaranteed.
This resource is all about minimizing overhead - so this saves you from e.g. polling for status/ray casts and similar. it also saves you from coding dependancy into your objects about other objects. In the long run this will save you from errors and extra work.
Example:
A simple pac man game. The pac man eats a power pill and somehow the ghosts need to turn blue for a short period of time.
You can do this at least 3 ways
1) ghosts ask pac man every 50 ms about "did you eat the powerpill?"
2) pac man tells each ghost "I ate the powerpill, and thus you have to turn blue"
3) pac man sends a message to the "power pill eaten" queue, and the ghosts get the message as an event
Problem with method 1: overhead - huuuuuge overhead. It might be OK for 4 ghosts to do this, but in 99.9999% of the cases the answer is negative and thus overhead.
Problem with method 2: pac man needs to know about each and every ghost, so that he can tell them about the power pill. This creates a dependency between code objects, and if you later add a 5th ghost, you have to make sure you run through all your code and add it to where pac man talks to the ghosts = error prone process
Along comes method 3 (this resource). There is no overhead.
Only ghosts that are interested in the "power pill eaten" messages subscribe to the queue. And only when there is a message will they be bothered with it (they have a method onRecieveMessage() that is called when there is a message - and not before)
And pac man doesnt need to know about the ghosts at all - all he needs to do is send the message that he ate the pill. And the ghosts dont need to know if it was mr. pac man or mrs. pac man who ate the pill - all they need to know is that someone ate the pill and now they need to turn blue. This enables you to add more content into the game without having to run through your logic over and over again.
So event driven messaging that is subscription based saves overhead and precious CPU cycles, as well as allows for much cleaner code, as well as more complex AI.
The basic mechanism of this resource is that you (from script) create a message queue. Script objects can now subscribe to these queues and every message send to queue X is then send to a subscriber of queue X.
Some example usages:
Example: exploding rocket
A rocket is fired and it collides with something and explodes. In its onCollision() it sends out a message saying "I, the rocket, exploded at coordinates x,y,z. I have a blast radius of 20 meters, and everyone inside the radius takes 20 hitpoints damage" to the "damage" message queue
All damageable objects have subscribed to this message queue and will now recieve the message send in their onRecieveMessage(). It is now up to them to check if they are invulnerable, in range, protected against rocket damage, have armour points to spend before health etc.etc.
Example: faction system
In an RPG game all player actions are send to a "playeraction" message queue. E.g. during this particular example game the player has succesfully pick pocketed 10 NPC players. Every time a message "I succesfully pickpocketed NPC with id ###" to the queue.
The game has 2 factions. The police faction and the thieves guild faction. Both subscribe to the queue and recieve these pick pocket messages. Based on these actions they recieve each factions can adjust their AI responses to real events. In this example case the players reputation in the thieves guild has preceeded him, and he will be recieved with open arms.
In this case it would be extremely easy to add another faction - without having ANY direct coupling of the player himself and the faction AI.
Example: zoning your game
To save CPU time and get better performance in your game, you divide the world into separate zones/mission regions/trigger areas. When the player enters and exits these areas, a message is send to the "zone information" message queue, saying "player entered/exited zone X". All the NPC AI's in the zone can now go from idle to active, and process input as they have subscribed to this message queue, and been informed of the arrival of a player. While the zone you just left can now be shut down. No need to have the parrots fly around, the NPC guards doing radius checks to see if you are in range or similar if the player is on the other side of the map.
Example: game objectives
You can have a central "game objectives controller" that tracks game events. If your game for example wants you to kill 4 enemies before you win, the controller listens in on the "death queue" and increases a counter. Once counter reaches X, the game is won. This system can be altered in any way needed for game objectives - chained, parallel objectives etc.etc.
Only your imaginations sets the limits for usage of this.
How to add to the engine
Easy to add. The code is not complicated at all.
First of all dump the attached code into your engine\game folder.
Then we need to create a new script callback in the SimObject.
open console/simBase.h and before this
then find this
Now open simBase.cc. In the top after this
And all the way in the end of the file add this
This now means that ALL SimBase objects can override the onReceiveMessage in script. Since not all have datablocks, we have 2 kinds of callbacks. I need the datablock for my game, so thus both are in there.
Compile the engine and thats it.
Script API
In the examples below I have a global variable on the server side holding the MessageRouter object. This might not be what you want.
To create a new Message Router object, you could do the following:
$MessageRouter = new MessageRouter(char* routerName) {};
To create/delete a message queue or to clear/remove all existing subscribers from a queue you use
To subscribe or unsubscribe %simbaseObject from a queue you use
To send a message you use
Lastly there is a debug function that you can use to see all queues and their subscribers. Simply type
Message Trigger
For my own games I've made a special type of generic trigger. What is does is than when a player enters it, a predefined message is sent to all who listen.
The code is here
Its generally useful for everything. For e.g. applying damage to the player, for triggering sounds - for anything you can use triggers for without having to code game functionality into the triggers themselves.
Where to go from here
The code itself still needs just a little more work to be truly generic usable. Currently the message can only hold 2 fields containing information (the message and the data). This is useful for maybe 90% of the uses you can think of, but it should be expanded to "unlimited" data fields. Its easy to add to the message router if your game needs it.
E.g. in the rocket explodes situation the message should contain 4 fields:
"rocket explosion" text as a type you can filter on
x,y,z coordinates
blast radius
damage count
One can do this with the 2 field and then cut it up using the getWord function, but its not as robust and can be error prone.
Another thing the code lacks is security. You might not want clients to be able to listen in on the messages and/or send messages in a multiplayer game to prevent cheating.
Other than that, its fully usable (I hope) for everyone. Enjoy!
Changelog
27th June 2004
posted resource
28th June 2004
made code handle re-entrancy by queueing messages while system is sending
fixed missing platform import statement
1st of July 2004
Due to request, I rewrote the initial parts of this resource to better explain what this is
1st of August 2004
Added object reference to the callback
7th of August 2004
Added a data field and an example "messaging trigger"
30th of October 2005
Bugfixes to prevent crashes on deleting subscribers during sending
SimObjects can now subscribe and send instead of ShapeBase objects
Rewrote script API
21th of September 2007
Wow - new update!
Fixed spelling mistake in receive vs. recieve
Partially rewrote the code to get rid of the internal delete queue. This fixes a bug where ojects could delete other objects that also were subscribers, and is more pleasing + logical.
Also fixed tiny bug with wrong error message in subscribe method
Subscription what? What the hell is this? What can I use it for?
OK OK - read through this, and you will know that you can use this in your game. Its almost guaranteed.
This resource is all about minimizing overhead - so this saves you from e.g. polling for status/ray casts and similar. it also saves you from coding dependancy into your objects about other objects. In the long run this will save you from errors and extra work.
Example:
A simple pac man game. The pac man eats a power pill and somehow the ghosts need to turn blue for a short period of time.
You can do this at least 3 ways
1) ghosts ask pac man every 50 ms about "did you eat the powerpill?"
2) pac man tells each ghost "I ate the powerpill, and thus you have to turn blue"
3) pac man sends a message to the "power pill eaten" queue, and the ghosts get the message as an event
Problem with method 1: overhead - huuuuuge overhead. It might be OK for 4 ghosts to do this, but in 99.9999% of the cases the answer is negative and thus overhead.
Problem with method 2: pac man needs to know about each and every ghost, so that he can tell them about the power pill. This creates a dependency between code objects, and if you later add a 5th ghost, you have to make sure you run through all your code and add it to where pac man talks to the ghosts = error prone process
Along comes method 3 (this resource). There is no overhead.
Only ghosts that are interested in the "power pill eaten" messages subscribe to the queue. And only when there is a message will they be bothered with it (they have a method onRecieveMessage() that is called when there is a message - and not before)
And pac man doesnt need to know about the ghosts at all - all he needs to do is send the message that he ate the pill. And the ghosts dont need to know if it was mr. pac man or mrs. pac man who ate the pill - all they need to know is that someone ate the pill and now they need to turn blue. This enables you to add more content into the game without having to run through your logic over and over again.
So event driven messaging that is subscription based saves overhead and precious CPU cycles, as well as allows for much cleaner code, as well as more complex AI.
The basic mechanism of this resource is that you (from script) create a message queue. Script objects can now subscribe to these queues and every message send to queue X is then send to a subscriber of queue X.
Some example usages:
Example: exploding rocket
A rocket is fired and it collides with something and explodes. In its onCollision() it sends out a message saying "I, the rocket, exploded at coordinates x,y,z. I have a blast radius of 20 meters, and everyone inside the radius takes 20 hitpoints damage" to the "damage" message queue
All damageable objects have subscribed to this message queue and will now recieve the message send in their onRecieveMessage(). It is now up to them to check if they are invulnerable, in range, protected against rocket damage, have armour points to spend before health etc.etc.
Example: faction system
In an RPG game all player actions are send to a "playeraction" message queue. E.g. during this particular example game the player has succesfully pick pocketed 10 NPC players. Every time a message "I succesfully pickpocketed NPC with id ###" to the queue.
The game has 2 factions. The police faction and the thieves guild faction. Both subscribe to the queue and recieve these pick pocket messages. Based on these actions they recieve each factions can adjust their AI responses to real events. In this example case the players reputation in the thieves guild has preceeded him, and he will be recieved with open arms.
In this case it would be extremely easy to add another faction - without having ANY direct coupling of the player himself and the faction AI.
Example: zoning your game
To save CPU time and get better performance in your game, you divide the world into separate zones/mission regions/trigger areas. When the player enters and exits these areas, a message is send to the "zone information" message queue, saying "player entered/exited zone X". All the NPC AI's in the zone can now go from idle to active, and process input as they have subscribed to this message queue, and been informed of the arrival of a player. While the zone you just left can now be shut down. No need to have the parrots fly around, the NPC guards doing radius checks to see if you are in range or similar if the player is on the other side of the map.
Example: game objectives
You can have a central "game objectives controller" that tracks game events. If your game for example wants you to kill 4 enemies before you win, the controller listens in on the "death queue" and increases a counter. Once counter reaches X, the game is won. This system can be altered in any way needed for game objectives - chained, parallel objectives etc.etc.
Only your imaginations sets the limits for usage of this.
How to add to the engine
Easy to add. The code is not complicated at all.
First of all dump the attached code into your engine\game folder.
Then we need to create a new script callback in the SimObject.
open console/simBase.h and before this
class SimEvent; class SimObject;add
struct Message;
then find this
bool isHidden(); void setHidden(bool b);and add this after the above
/// Added for Message Router callback method void onReceiveMessage(char* queueName, Message message); /// End add
Now open simBase.cc. In the top after this
#include "console/consoleInternal.h" #include "console/typeValidators.h"include this
#include "game/messageRouter.h"
And all the way in the end of the file add this
void SimObject::onReceiveMessage(char* queueName, Message message)
{
// If this object has a datablock, then send to that instead of the object itself
GameBase* gameBaseObj = static_cast<GameBase*>(this);
if (gameBaseObj) {
Con::executef(gameBaseObj->getDataBlock(), 6, "onReceiveMessage", gameBaseObj->scriptThis(), queueName, message.mSenderId, message.mMessage, message.mData);
} else {
Con::executef(this, 5, "onReceiveMessage", queueName, message.mSenderId, message.mMessage, message.mData);
}
}This now means that ALL SimBase objects can override the onReceiveMessage in script. Since not all have datablocks, we have 2 kinds of callbacks. I need the datablock for my game, so thus both are in there.
Compile the engine and thats it.
Script API
In the examples below I have a global variable on the server side holding the MessageRouter object. This might not be what you want.
To create a new Message Router object, you could do the following:
$MessageRouter = new MessageRouter(char* routerName) {};
To create/delete a message queue or to clear/remove all existing subscribers from a queue you use
bool $MessageRouter.createMessageQueue(char* queueName); bool $MessageRouter.deleteMessageQueue(char* queueName); bool $MessageRouter.clearMessageQueue(char* queueName);
To subscribe or unsubscribe %simbaseObject from a queue you use
bool $MessageRouter.subscribeMessageQueue(%simbaseObject, char* queueName); bool $MessageRouter.unsubscribeMessageQueue(%simbaseObject, char* queueName);where %simbaseObject is your object that you want to subscribe
To send a message you use
bool $MessageRouter.sendMessage(%simbaseObject, char* queueName, char* message, char* data);where %simbaseObject is the object that sends the message.
Lastly there is a debug function that you can use to see all queues and their subscribers. Simply type
void dumpMessageQueues();on the console.
Message Trigger
For my own games I've made a special type of generic trigger. What is does is than when a player enters it, a predefined message is sent to all who listen.
The code is here
//-----------------------------------------------------------------------------
// Message Trigger
// Sends a message into the "MessageTriggerQueue" when the player enters
// Takes the values from the "message" and "data" fields defined in the
// world editor for this trigger and sends it to the "MessageTriggerQueue"
//-----------------------------------------------------------------------------
datablock TriggerData(MessageTrigger)
{
// The period is value is used to control how often the console
// onTriggerTick callback is called while there are any objects
// in the trigger. The default value is 100 MS.
tickPeriodMS = 100;
};
function MessageTrigger::onEnterTrigger(%this,%trigger,%obj)
{
if (isObject(%obj.client.player))
{
$MessageRouter.sendMessage(%obj, "MessageTriggerQueue", %trigger.message, %trigger.data);
}
}Its generally useful for everything. For e.g. applying damage to the player, for triggering sounds - for anything you can use triggers for without having to code game functionality into the triggers themselves.
Where to go from here
The code itself still needs just a little more work to be truly generic usable. Currently the message can only hold 2 fields containing information (the message and the data). This is useful for maybe 90% of the uses you can think of, but it should be expanded to "unlimited" data fields. Its easy to add to the message router if your game needs it.
E.g. in the rocket explodes situation the message should contain 4 fields:
"rocket explosion" text as a type you can filter on
x,y,z coordinates
blast radius
damage count
One can do this with the 2 field and then cut it up using the getWord function, but its not as robust and can be error prone.
Another thing the code lacks is security. You might not want clients to be able to listen in on the messages and/or send messages in a multiplayer game to prevent cheating.
Other than that, its fully usable (I hope) for everyone. Enjoy!
#22
I will update this resource. So everyone using it already beware! You need to change a few things here, including how to subscribe, send and create the message router. Will be updated within a few hours
10/29/2005 (2:58 am)
Okie dokieI will update this resource. So everyone using it already beware! You need to change a few things here, including how to subscribe, send and create the message router. Will be updated within a few hours
#23
Please note the new script interface - you now manually have to create the queue + sending goes through the router and not on the object
10/30/2005 (4:57 am)
Updated! Feel free to test run this - I might have forgot something.Please note the new script interface - you now manually have to create the queue + sending goes through the router and not on the object
#24
12/08/2005 (1:03 pm)
Hi. I'am newbie and I'll be using message router in my project, but I don't know how override SimBase onRecieveMessage() in my script objects. Everything works fine, but when I try send message to object, that overrides this method in script - game crash. Is possible show short primer how can I overide or use this method in my script objects?
#25
i'm hoping to use it as the base of a somewhat more complex chat system than the stock one.
tiny bug:
in MessageRouter::subscribe(),
this:
should be this:
edit: typos
02/27/2006 (2:46 pm)
nice resource.i'm hoping to use it as the base of a somewhat more complex chat system than the stock one.
tiny bug:
in MessageRouter::subscribe(),
this:
if (queue == NULL) {
Con::errorf("Queue %s already exists", queueName);
return false;
}should be this:
if (queue == NULL) {
Con::errorf("Queue %s does not exist", queueName);
return false;
}edit: typos
#26
but a messageQueue here is either entirely server or client-side, yes ?
that is, if i want a client to "subscribe" to a server-side message queue, that'll be custom code ?
02/27/2006 (2:57 pm)
correct me if i'm wrong,but a messageQueue here is either entirely server or client-side, yes ?
that is, if i want a client to "subscribe" to a server-side message queue, that'll be custom code ?
#27
06/16/2006 (11:08 am)
This is a great resource. The instructions are clear and everything works exactly as you said it would. I set the whole thing up in 10 minutes (minus the rebuild)
#28
@Orion - sorry. I missed your post. Dont know if you are still reading, but I'll answer anyway.
The queue is only really useful server side. So yes - its not a generic messaging system between server and clients, but usable for sending messages between server objects.
With that said, you could always hack things and rework them. But there already exists various other ways to message clients and servers - look at e.g. messageAll() script function used in the chat system of the starter.fps.
06/16/2006 (12:04 pm)
Happy you got it working Elizabeth@Orion - sorry. I missed your post. Dont know if you are still reading, but I'll answer anyway.
The queue is only really useful server side. So yes - its not a generic messaging system between server and clients, but usable for sending messages between server objects.
With that said, you could always hack things and rework them. But there already exists various other ways to message clients and servers - look at e.g. messageAll() script function used in the chat system of the starter.fps.
#29
I'm sorry to sound like a jerk who goes around correcting people's spelling in forums, but this did in fact put us off for a while, and I just wanted to be sure no one else gets into the same situation.
07/20/2006 (1:59 pm)
I was having problems overwriting the onRecieveMessage command for one of my ai units. The problem was that I was writing onReceiveMessage(), while I should have been overwriting onRecieveMessage (ei rather than ie).I'm sorry to sound like a jerk who goes around correcting people's spelling in forums, but this did in fact put us off for a while, and I just wanted to be sure no one else gets into the same situation.
#30
This resource looks to be very useful. I am trying to compile the code into Torque v152. I am getting the following errors:
messageRouter.cc(180) : error C2039: 'onRecieveMessage' : is not a member of 'SimObject'
simBase.cc(2038) : error C2039: 'onRecieveMessage' : is not a member of 'SimObject'
simBase.cc(2041) : error C2673: 'onRecieveMessage' : global functions do not have 'this' pointers
I am still learning the engine, I am not sure how to make onRecieveMessage, a member of 'SimObject'. Would anybody happen to have some insight on resolving this compilation error?
Thanks
09/16/2007 (1:55 pm)
Hello,This resource looks to be very useful. I am trying to compile the code into Torque v152. I am getting the following errors:
messageRouter.cc(180) : error C2039: 'onRecieveMessage' : is not a member of 'SimObject'
simBase.cc(2038) : error C2039: 'onRecieveMessage' : is not a member of 'SimObject'
simBase.cc(2041) : error C2673: 'onRecieveMessage' : global functions do not have 'this' pointers
I am still learning the engine, I am not sure how to make onRecieveMessage, a member of 'SimObject'. Would anybody happen to have some insight on resolving this compilation error?
Thanks
#31
to the end of simbase.cc (check the instructions - they contain a few more lines to be added here)
Then it should work.
I am actually working these days on a new and improved version, that will fix some lingering buggers. Gimme a few more day to test, and I'll update this (including the spelling mistake in recieve vs. receive (sorry - English is not my native language).
Change will be 99% (except the spelling mistake) transparent
09/16/2007 (1:59 pm)
If you added the code as per instructions - specifically thisvoid SimObject::onRecieveMessage(char* queueName, Message message)
{
// If this object has a datablock, then send to that instead of the object itself
GameBase* gameBaseObj = static_cast<GameBase*>(this);
if (gameBaseObj) {
Con::executef(gameBaseObj->getDataBlock(), 6, "onRecieveMessage", gameBaseObj->scriptThis(), queueName, message.mSenderId, message.mMessage, message.mData);
} else {
Con::executef(this, 5, "onRecieveMessage", queueName, message.mSenderId, message.mMessage, message.mData);
}
}to the end of simbase.cc (check the instructions - they contain a few more lines to be added here)
Then it should work.
I am actually working these days on a new and improved version, that will fix some lingering buggers. Gimme a few more day to test, and I'll update this (including the spelling mistake in recieve vs. receive (sorry - English is not my native language).
Change will be 99% (except the spelling mistake) transparent
#32
struct Message;
bool isHidden();
void setHidden(bool b);
/// Added for Message Router callback method
void onRecieveMessage(char* queueName, Message message);
Still getting compile time errors for simbase.cc:
simBase.cc(2050) : error C2039: 'onRecieveMessage' : is not a member of 'SimObject'
../engine\console/simBase.h(446) : see declaration of 'SimObject'
simBase.cc(2053) : error C2673: 'onRecieveMessage' : global functions do not have 'this' pointers
simBase.cc(2057) : error C2673: 'onRecieveMessage' : global functions do not have 'this' pointers
Can you give me some more direction?
09/16/2007 (2:17 pm)
Yes, I have followed the instructions, and I added the code to simbase.cc, I have included the header file per the instructions, and added the following lines to simbase.h:struct Message;
bool isHidden();
void setHidden(bool b);
/// Added for Message Router callback method
void onRecieveMessage(char* queueName, Message message);
Still getting compile time errors for simbase.cc:
simBase.cc(2050) : error C2039: 'onRecieveMessage' : is not a member of 'SimObject'
../engine\console/simBase.h(446) : see declaration of 'SimObject'
simBase.cc(2053) : error C2673: 'onRecieveMessage' : global functions do not have 'this' pointers
simBase.cc(2057) : error C2673: 'onRecieveMessage' : global functions do not have 'this' pointers
Can you give me some more direction?
#33
Got some idea, but not sure.
09/21/2007 (5:04 am)
Took a little while to get back - sorry. Try to send me your simbase.cc and simbase.h files. My mail is in my profile - I'll take a look at themGot some idea, but not sure.
#34
As written in the comments, this release fixes the spelling mistake pointed out by Elizabth, and also fixes an issue with the previous version.
In short - the previous version had an internal queue that registered objects that wanted to be deleted. That code did not take into account, that objects could delete other objects still on the sender list.
By rewriting the code a few places, this should be fixed now. Update is transparent, so just dump in the new files instead of the old ones.
09/21/2007 (5:24 am)
New update is up!As written in the comments, this release fixes the spelling mistake pointed out by Elizabth, and also fixes an issue with the previous version.
In short - the previous version had an internal queue that registered objects that wanted to be deleted. That code did not take into account, that objects could delete other objects still on the sender list.
By rewriting the code a few places, this should be fixed now. Update is transparent, so just dump in the new files instead of the old ones.
#35
09/21/2007 (5:31 am)
Well done Thomas. This is one of my favorite resources!
#36
And thanks Dave! :-) Happy you like it
09/21/2007 (5:49 am)
And just uploaded a new one again. Forgot to fix the little bug mentioned by Orion with a wrong error message (to much cut'n'paste)And thanks Dave! :-) Happy you like it
#37
it took me a few hours to figure out how to make it do what i want though :)
for some reason, I had to change the static_cast in the onReceiveMessage callback of simObject to a dynamic_cast . whenever I was sending messages to non-gamebase derived objects it would still pass true on the if(gameBaseObj) for some reason. changing this to dynamic_cast worked however
i'd be interested if anyone knew why that was or if that is a bad thing... but otherwise, it's working now the way I want it to (communicate between AIPlayer and ScriptObject) :)
Thanks for the resource!
09/25/2007 (1:35 am)
great resource!it took me a few hours to figure out how to make it do what i want though :)
for some reason, I had to change the static_cast in the onReceiveMessage callback of simObject to a dynamic_cast . whenever I was sending messages to non-gamebase derived objects it would still pass true on the if(gameBaseObj) for some reason. changing this to dynamic_cast worked however
i'd be interested if anyone knew why that was or if that is a bad thing... but otherwise, it's working now the way I want it to (communicate between AIPlayer and ScriptObject) :)
Thanks for the resource!
#38
Altough Im new to Torque, its strange to me that something like this, is not native on an engine as TGE.
The observer pattern is the second thing I would program... after the name of the engine! :D
But of course, I know very very little about programming engines...
09/25/2007 (9:42 pm)
Outstanding resource! (I didnt implemented it yet, but this one is a must)Altough Im new to Torque, its strange to me that something like this, is not native on an engine as TGE.
The observer pattern is the second thing I would program... after the name of the engine! :D
But of course, I know very very little about programming engines...
#40
I'm also wondering about how it will perform with a lot of messages and queues going everywhere, but we'll see. This could be very handy for squad AI, as well as scripted events. Compiling now... and it's in. Flawless integration... thank you mightily for this resource.
05/18/2008 (12:02 pm)
This looks pretty brilliant. Your pac-man explanation of this feature makes it seem essential - how did the engine survive without it?!I'm also wondering about how it will perform with a lot of messages and queues going everywhere, but we'll see. This could be very handy for squad AI, as well as scripted events. Compiling now... and it's in. Flawless integration... thank you mightily for this resource.

Torque Owner OneST8