Jump to content
Eternal Lands Official Forums
svk

Questions about starting a bot

Recommended Posts

Hi,

 

I'm a somewhat experienced C++ programmer. I can deal with code. Networking, on the other hand, is simply not my cup of tea.

 

First of all, I read the sticked thread at the top about creating bots. I think I understand the restrictions. I also downloaded the source code of a simple bot that was linked to in the aforementioned thread, ELBot (I didn't find where to download the first one - appears to be no longer available). I also downloaded and installed SDL and SDL_Net (which are used in ELBot), and I even got the project to compile in Visual Studio!

 

Now to completely obliterate any credit I have established in the last paragraph, allow me to enumerate my newbish-but-true questions:

 

1. The sticked thread mentions "You can use the testserver on port 2001 to develop your bot". I don't exactly understand what this means (did I mention I hate networking?). Is this a physical, hardware port on my computer? A hardware port on the EL server? Something software?

 

The source code of the bot I downloaded has a line of code that reads "int port = 2000;", which is later used in the SDL function SDLNet_ResolveHost. I changed this to read "int port = 2001;". Is this OK if I want it to run on the test server, or are there additional steps/complications here?

 

2. If I am able to successfully log on to the testserver, how can *I*, as my main account, log on to the testserver to see my bot?

 

3. Another line in the source code reads: "char *hostname = "eternal-lands.solexine.fr";". Is this correct? Because elsewhere I saw a different address, "eternal-lands.network-studio.com". Which should I use?

 

4. Is there a guide for what I can send/receive from the server? All the networking code looks rather cryptic at the moment, and not so much because of the syntax, but because I have no clue *why* the program is doing what it's doing. I see that it's sending this message and that message to the server, and I understand what the message is supposed to contain, but I have no clue how this system works.

 

(EDIT: new question)

5. How can I actually log a bot in (to the testserver)? Do I have to create a new EL character and password? And then simply change the "username" and "password" variables that my program sends to the server?

 

Oh, I know. I suck.

 

If anyone with experience can help me, I will build a shrine to you and pray every evening. Thanks in advance.

Edited by svk

Share this post


Link to post
Share on other sites
1. The sticked thread mentions "You can use the testserver on port 2001 to develop your bot".

The source code of the bot I downloaded has a line of code that reads "int port = 2000;"

I changed this to read "int port = 2001;". Is this OK if I want it to run on the test server, or are there additional steps/complications here?

nope, that's all
2. If I am able to successfully log on to the testserver, how can *I*, as my main account, log on to the testserver to see my bot?
as above, just change the port to 2001. for the normal client, that'd be in el.ini or the options window in-game
3. Another line in the source code reads: "char *hostname = "eternal-lands.solexine.fr";". Is this correct? Because elsewhere I saw a different address, "eternal-lands.network-studio.com". Which should I use?
either, doesn't matter, goes to the same place
4. Is there a guide for what I can send/receive from the server? All the networking code looks rather cryptic at the moment, and not so much because of the syntax, but because I have no clue *why* the program is doing what it's doing. I see that it's sending this message and that message to the server, and I understand what the message is supposed to contain, but I have no clue how this system works.
nope, you either read the code in elc/other bots and figure out how it works... or not care and just use/adapt it :)
5. How can I actually log a bot in (to the testserver)? Do I have to create a new EL character and password? And then simply change the "username" and "password" variables that my program sends to the server?
create a new one on the test server (ie after changing port number and restarting ELC), yes. you'll probably have to re-create your main account there too, accounts are only copied over every now and then

Share this post


Link to post
Share on other sites
2. If I am able to successfully log on to the testserver, how can *I*, as my main account, log on to the testserver to see my bot?
as above, just change the port to 2001. for the normal client, that'd be in el.ini or the options window in-game
Currently, the normal client is incompatible with the test server, you need to download the RC2 from elsewhere on these forums.

Share this post


Link to post
Share on other sites

Thank you so much, guys. I successfully logged in as a bot, after only a few small modifications to the program to get it to run. Your shrine construction is under way.

 

Unfortunately, after having tried to modify the program more extensively, mainly to convert it to a more C++ oriented style using STL, I screwed something up really bad. I'm currently getting 100 very cryptic errors, over half of which are in the standard include files (not mine). I banged my head on the desk to the point of bleeding, but I still have not a clue what's wrong. I give up for today, but I'm still glad I got a bot to run at all :( .

 

By the way, what would happen if I create an account that I have on the normal game server on the testserver? Will its stats and inventory be copied from the normal server when I create it? Or will it be a fresh character? If the latter, what would happen when the data is copied between the servers?

 

I'm worried about those things because 1) if I recreate my main account on the test server, will I lose all my stats? and, more pertinent to the subject at hand, 2) since I just created my bot with the desired username on the test server, what happens when I want to move it to the main server? Will it not let me since the account exists?

Share this post


Link to post
Share on other sites

Accounts on the test server are separate from those on the productive one. If you level/delete/recreate accounts on the test server, the main accounts on the port 2000 server are not affected!

 

The "main" accounts are copied from prod to test from time to time, so dont expect a very long lifetime for accounts on the test server or for their inventory or levels.

 

Piper

Share this post


Link to post
Share on other sites

OK, thanks, then I guess I'll have to reserve the bot's name on the main server.

 

Oh, and the cryptic errors were solved. Apparently, even though the project was set up to compile as a C++ project, the .c extensions of the source files caused the compiler to treat the project as a C project. Of course, headers such as cstdlib and cstdio are C++ headers, not C. The problem was solved by simply changing the source files' extension from .c to .cpp, and then re-adding them to the project (in case anyone runs into a similar problem).

 

Thanks all!

Share this post


Link to post
Share on other sites
Apparently, even though the project was set up to compile as a C++ project, the .c extensions of the source files caused the compiler to treat the project as a C project.
that should only really happen if there isn't a setting for the project/files... are you using a development environment that allows settigns per file? (MSVC is one) if so, check them to see if the individual files are set to be compiled as C. if the project is set to compile as C++, then only a per-file setting should be able to override it

 

one of these days I should get my bot code working :blush: (C++ w/ STL stuff, and some wrapping around SDL, which is all sorts of fun)

Share this post


Link to post
Share on other sites

Hi again!

 

Today, after roughly a week, I finished giving the source code a major C++/STL face lift, and was able to get it more or less working again. It's still in the preliminary stage, though, since it's littered with "TO DO" and "how the hell does this work?" comments, and the class structure isn't ideal yet. And, of course, no new functionality was actually added. And I have yet to get rid of the dreaded ellipses construct, to which I have unfortunately become addicted while working with the code (it's so handy for log functions).

 

Anyways, there's a small problem, though, which raised a couple questions for me. The problem is the server does not send the log in confirmation anymore. I read here about how you need to make sure that the login string is null terminated. So,

 

1. Does the null terminator actually have to be sent along with the login string? Do I have to increment the size of the login string by 1 to make sure it's included in the message that gets sent to the server?

 

Also, from the aforementioned link, it seems that I need to send a heartbeat immediately after I send the login request. Check. Still no login confirmation from the server, though.

 

However, it's plainly visible that I actually did log on to the server. From the bot's log file, I see that I get about a dozen other messages that I guess you're supposed to get when you log on (like HERE_YOUR_STATS, and GET_YOUR_SIGILS). I even get a couple that I don't know how to decode (one of them bears the value 60, which matches only ATT_EXP_NEXT in client_serv.h... am I supposed to get this message? And what about 71, which matches only CRA_EXP?

 

In essence, the server recognizes my presence and sends me several messages. Unfortunately, none of them are the login confirmation.

 

2.

Now on to my second big question. I downloaded Ethereal, which allows you examine what gets sent and received to and from your computer. I hope some of you use it, as I did hear about it on these forums. Now, every time I send something to the server, Ethereal reports that there is actually some extra stuff that gets sent before every message. Is this fine? Is this a part of the TCP protocol, which SDL_Net adds and handles for me? If so, is there a way I can tell Ethereal not to display it every time?

 

That's it for now. I hope you guys can help!

Share this post


Link to post
Share on other sites

However, it's plainly visible that I actually did log on to the server. From the bot's log file, I see that I get about a dozen other messages that I guess you're supposed to get when you log on (like HERE_YOUR_STATS, and GET_YOUR_SIGILS). I even get a couple that I don't know how to decode (one of them bears the value 60, which matches only ATT_EXP_NEXT in client_serv.h... am I supposed to get this message? And what about 71, which matches only CRA_EXP?

 

In essence, the server recognizes my presence and sends me several messages. Unfortunately, none of them are the login confirmation.

 

That likely means that the packet size is being mangled somewhere, causing the bot to buffer the login confirmation with another packet, or split it up, etc. (On login, you'll likely receive all of those packets at once.) That would likely also explain the protocols you can't decode.

Share this post


Link to post
Share on other sites

However, it's plainly visible that I actually did log on to the server. From the bot's log file, I see that I get about a dozen other messages that I guess you're supposed to get when you log on (like HERE_YOUR_STATS, and GET_YOUR_SIGILS). I even get a couple that I don't know how to decode (one of them bears the value 60, which matches only ATT_EXP_NEXT in client_serv.h... am I supposed to get this message? And what about 71, which matches only CRA_EXP?

 

In essence, the server recognizes my presence and sends me several messages. Unfortunately, none of them are the login confirmation.

 

That likely means that the packet size is being mangled somewhere, causing the bot to buffer the login confirmation with another packet, or split it up, etc. (On login, you'll likely receive all of those packets at once.) That would likely also explain the protocols you can't decode.

 

I think my code takes care of multiple server messages per packet. I'll post it below for you guys to examine. It's a little difficult to check because of the extra overhead that Ethereal reports was sent. Wading through a hex dump is tough enough as is, and another one of Ethereal's features is making it even harder. When you try to put your cursor in the packet contents on the bottom, it doesn't select a single byte, but a whole group of (apparently related) bytes. This is convenient when there is one message in a packet, but when there are multiple messages, Ethereal selects them all. Without a blinking cursor, it's a bit tough to keep track of where I am. I tried to export the packet data to a text file, but I'm confronted with an error message that reads <<The path to the file "" doesn't exist.>>.

 

In any case, it appears that even when there are multiple messages per packet, the program handles it fine. Here's a snippet of the log of the messages received:

 

=============================================

Logging in as Calcy

Received message from server: [0]=0, [1,2]=15, msg="?Test server."

?Test server.

Received message from server: [0]=60, [1,2]=5, msg=""à†"

Received message from server: [0]=42, [1,2]=5, msg=""

Received message from server: [0]=45, [1,2]=11, msg="ÿÿÿÿÿÿÿÿÿÿ"

Received message from server: [0]=5, [1,2]=3, msg="P"

Received message from server: [0]=4, [1,2]=5, msg="9à†"

Received message from server: [0]=3, [1,2]=3, msg=""

Received message from server: [0]=7, [1,2]=25, msg="./maps/map2_insides.elm"

Received message from server: [0]=-36, [1,2]=3, msg=""

Received message from server: [0]=12, [1,2]=5, msg="ò"

Received message from server: [0]=19, [1,2]=2, msg=""

Received message from server: [0]=18, [1,2]=191, msg=" "

Received message from server: [0]=55, [1,2]=26, msg=""

Received message from server: [0]=60, [1,2]=5, msg="9à†"

Received message from server: [0]=-6, [1,2]=1, msg=""

Received message from server: [0]=71, [1,2]=14, msg=""

Received message from server: [0]=51, [1,2]=35, msg=""

I see: Calcy

 

ERROR at Sat Jul 08 22:28:01 2006

: Unable to get confirmation if login was successful

 

ERROR at Sat Jul 08 22:28:01 2006

: Unable to log in as Calcy

Closing down the connection, and exiting

=============================================

 

Some of the messages have the first byte equal to a negative number (oddly enough, even after I casted them to an unsigned int). Perhaps not being able to get a login confirmation is a signed/unsigned error? (I would doubt it, since the other server messages won't make much sense then).

 

I would also post the packet info, but Ethereal won't let me export it (anyone know how to fix this?).

 

Here's the code for receiving the server messages:

int Connection::getPacket()
{
int len, size, total;

// Clear the buffer if this is a new packet
if( buf_in_use == 0 )
	memset( msgbuf, 0, BUF_SIZE );

// Get the header if necessary
if( buf_in_use < 3 )
{
	len = SDLNet_TCP_Recv( thesock, msgbuf+buf_in_use, 3-buf_in_use );

	// If error, or if connection lost, return that there were 0 bytes received
	if (len < 0) return 0;

	// Increase the indicator of how much of the buffer is in use by the amount that was
	// received in the previous packet retrieval call
	buf_in_use += len;

	if (buf_in_use < 3)
	{
		if (buf_in_use > 0) return -1;
		log.error ("Packet underrun, len = %d, buf_in_use = %d", len,
			 buf_in_use);
		return 0;
	}
}

// Retrieve the size bytes, which are immediately after the identifier byte
size = (*((short *) (msgbuf+1)));

// Accomodate for the 2 size bytes themselves
size += 2;

// If the amount of data that wants to be read exceeds the size
// of the buffer that can receive it, we... don't read it at all...?
if (size >= BUF_SIZE-3)
{
	/* uh oh */
	log.error ("Packet overrun ... data lost");
	return 0;
}

// Read the data chunk by chunk until there is no more to read.
//
// We request to read all of the remaining data at once (size-buf_in_use,
// which is basically "the size of the data" - "how much of it we
// have already read", which equates to "the amount of data left to be
// received"); however, if we did not receive all the data at once,
// we accomodate for it using a while loop.
//
// We error check every iteration, except... we return -1 here, whereas
// just above we returned 0 for the same breed of error. Hmm.
//
// Finally, we update buf_in_use, or the amount of data we read total, by
// the amount we read in the current iteration.
while (buf_in_use < size)
{
	len = SDLNet_TCP_Recv (thesock, msgbuf+buf_in_use, size-buf_in_use);
	if (len <= 0) return -1;
	buf_in_use += len;
}

// Set total to represent the total number of bytes of data received
total = buf_in_use;

// Reset buf_in_use for next time (indicating that we are finished with this
// message, and it will be a new one next time)
buf_in_use = 0;

// Return the total number of bytes of data received
return total;
}

int Connection::getServerMessage()
{
// to do: ?? What's the purpose of startptr??
int nr_active_sock, reclen, startptr = 0, msglen;

// to do: it doesn't appear that startptr is ever changed. Get rid of it?

try_again:

// Check the socket for activity.
nr_active_sock = SDLNet_CheckSockets( theset, 1 );

// If no sockets are active, no need to get messages
if (nr_active_sock == 0)
{
	return 0;
}

// to do: the preceding function returns -1 on error. We should probably error check.
// http://jonatkins.org/SDL_net/SDL_net.html#SEC45

if (!SDLNet_SocketReady (thesock)) return 0;

// Read the packet
reclen = getPacket();

// Error checks
if (reclen == -1) return -1;
if (reclen <= 0)
{
	log.error ("Disconnected by the server!");
	exitConnection (EXIT_ALL);
	exit (1);
}

// Retrieve the size of the message, and include the two size bytes themselves
msglen = *((short *) (msgbuf + startptr + 1));
msglen += 2;

return msglen;

// to do: there used to be a call to processMessage() here, but it was moved to a different class. As such, processing the message is now to be done outside of this function. As such, there is no longer a need for this:
//goto try_again;
}

// Here is how the bot currently logs in, using the above functions:
int EL::logIn (const char *name, const char *passwd)
{
// This is what we send to sendToServer()
char str[40];

// The message we send should be big enough to include the identifier,
// a space between the name and the password, and the null terminator,
// which accounts for the +3.
int i, len = strlen (name) + strlen (passwd) + 3;

// Name and password too long for the buffer
if (len >= 40-2)
{
	log.error ("Name or password too long!");
	return 0;
}

// Set the identifier byte
str[0] = LOG_IN;

// Just after the identifier byte, write the name, followed by a space, and finally the password (the size bytes after the identifier byte will be taken care of by sendToServer()
StringCbPrintf( (char *) (str+1), 40-1, "%s %s", name, passwd);

if (bDebug == true)
{
	  log.info ("Logging in as %s", name);
}

// http://www.eternal-lands.com/forum/index.php?s=&showtopic=24328&view=findpost&p=257675
// According to ^, we may need to manually null-terminate the log in string before sending
// and also send a heartbeat immediately afterwards
str[ len ] = '\0';

// Try to log in (note: this function takes care of the size bytes which
// we omitted here but are needed to complete the protocol).
connection.sendToServer (str, len);

// Send the heartbeat
char heartbeat = HEART_BEAT;
if( connection.sendToServer(&heartbeat, 1) == error )
{
	log.error( "Failed to send heartbeat immediately after sending login info" );
	return error;
}

logged_in = 0;		// Note: this is a global

// Keep checking for the login confirmation or denial from the server
for (i = 0; i < 100; i++)
{
	// Get any server messages
	int msglen = connection.getServerMessage();
	// to do: error check (and similar calls, too)

	// Process the server message
	// This call takes care of setting the global "logged_in"
	// appropriately if we receive the proper server message
	if( msglen > 0 )
	{
		processMessage( msgbuf, msglen );
	}

	if (logged_in == LOG_IN_OK)
	{
		if (bDebug == true)
		{
			  log.info ("Login was successful");
		}
		return 1;
	} else if (logged_in == LOG_IN_NOT_OK)
	{
		log.error ("Unable to log in, wrong password?");
		return 0;
	}

	// If we are here, then we did not receive a message from the server yet. (NOT TRUE: means we did not get the *login confirmation* message from the server; as it happens, we got a dozen other messages already)
	// Wait 10 milliseconds, and try again
	SDL_Delay (10);
}

// And if we are here, that means we tried 100 times, and we are still unable
// to receive a confirmation.
log.error ("Unable to get confirmation if login was successful");
return 0;
}

 

Sorry, some comments sound confused. This is not a finished project yet :P

 

If anyone can spot any problems, please help me! I'll be examining the Ethereal dump a bit closer...

 

EDIT: Also, why does the log report some negative numbers for the first bytes of some of the messages received from the server? I though I took care of that:

unsigned int msg0 = (unsigned int)msg[ 0 ];
log.info( "Received message from server: [0]=%d, [1,2]=%d, msg=\"%s\"", msg0,
	(int)(*( (short*)( msg+1 ) ) ), msg+3 );

Edited by svk

Share this post


Link to post
Share on other sites

I would also post the packet info, but Ethereal won't let me export it (anyone know how to fix this?).

 

I don't know what version of Ethereal you have, but the Windows version I use (0.99.0) has an Export function in the Files menu. You can export to plain text, PostScript, CSV, or a couple of XML formats.

 

I ran into the same problem you mention about multiple messages in a packet. I ended up printing out packets of interest and marking them with a pencil to show the start and end of each message in the packet data field. It's not too bad since each message has a length embedded in it. as long as you can count in hexadecimal.

 

Dave

Share this post


Link to post
Share on other sites

A few things for endian/possible issues: msg0 should be an unsigned char/Uint8/_u8 (whatever type you use most often), and the message length should be unsigned. Still, I can't find any reason why that would be dumping a negative.

Share this post


Link to post
Share on other sites

Some happy news:

 

It really did turn out to be a signed/unsigned issue. Anytime the server sent me a message with the first byte greater than 127, since the variable that received it was signed, it became negative (the first bit was set).

 

It was still logged as signed even after I cast it as unsigned. This is because it's not the variable itself that determines how it will be logged, but the format string. Observe that I used %d when I was supposed to use %u, for an unsigned integer.

 

Finally, the function that was at fault was the one I thought I didn't need to show, processMessage(). In there, there is a switch statement that tests a signed char. I changed this to Uint8, and the problem was solved. Now, the login confirmation, which was actually sent all along, was interpreted correctly.

 

As for the weird messages that the server sent: apparently, my copy of client_serv.h (which came with the original package of the bot code) was either outdated or edited. I replaced it with the client_serv.h that is shipped with the up-to-date client source code. 60 actually represents a PING_REQUEST, to which I simply reply with the same message.

 

So, after battling with a couple more problems (such as forgetting to check that the length of the message received from the server is greater than 0 before attempting to process it - this resulted in me sending a PING_REPLY a bazzilion times because I thought the server kept sending me a PING_REQUEST, when in fact, the PING_REQUEST was just a remnant of the last message that was sent and was already processed. Sorry for spamming you, EL :omg: ), I finally got my code working again.

 

Thanks to everyone!

Edited by svk

Share this post


Link to post
Share on other sites

Just a quick question about chat messages. Apparently, the protocol changed slightly since the original bot was written. When raw text is sent, I think the protocol is what is below. Just verify that my guesswork is correct, please:

 

[0] = RAW_TEXT

[1,2] = size of the data

[3]: From the client source, I see this is the channel number, or 0 if it's local chat. And yet, not entirely... when I open the connection, I receive the "Test server." message. In this case, [3]=3. Surely, that message did not intend to go on channel 3, did it? So why is this different?

[4]: This is apparently a color code? And I'm supposed to interpret it by subtracting 127, and *then* comparing it to the color codes? It looks like 133 is the color for normal chat, since 133-127=6, which equates to the color code for gray.

Share this post


Link to post
Share on other sites
[0] = RAW_TEXT

[1,2] = size of the data

yes and yes (but beware of endian-ness)

[3]: From the client source, I see this is the channel number, or 0 if it's local chat. And yet, not entirely... when I open the connection, I receive the "Test server." message. In this case, [3]=3. Surely, that message did not intend to go on channel 3, did it? So why is this different?

not channel number, buffer number. eg the tabs are 5-7 IIRC (there are defines for all the buffers too)
[4]: This is apparently a color code? And I'm supposed to interpret it by subtracting 127, and *then* comparing it to the color codes? It looks like 133 is the color for normal chat, since 133-127=6, which equates to the color code for gray.
yeah, grep elc for c_red1, that's the first colour number. of course, a bot would probably ignore colour codes.

Share this post


Link to post
Share on other sites

Thanks, I just found the defines in client_serv.h. I think I see how this works.

 

Another quickie: whenever someone speaks, here's what a segment of what it looks like byte by byte:

playername: []hello!

 

The [] comes right after the space but before the actual message. It doesn't look like a color code (since it would equate to yellow). What is it?

Edited by svk

Share this post


Link to post
Share on other sites

The [] comes right after the space but before the actual message. It doesn't look like a color code (since it would equate to yellow). What is it?

 

It's a yellow color code. Just such a light yellow that it only serves to distinguish names from messages.

Share this post


Link to post
Share on other sites

I need some help with network code structure.

 

I want to implement some sort of system that can handle "longer conversations" with the server. Currently, if the server sends me a message, I process it in a single pass and return happily with the message forgotten. However, there may exist cases in which the server would send me a message, and I would wish to reply to that message, and wait for an answer back.

 

For example, my bot is able to report someone's health (not that this is useful, but it serves for the purpose of an example). If someone says "my health" in local chat, my bot would answer with the current HP of whoever said the message. However, in order to do that, the following messages have to be sent between the server and the client:

 

Server: RAW_TEXT (<playername>: my health)

Client: SEND_ME_MY_ACTORS

Server: ADD_NEW_ENHANCED_ACTOR (info on <playername>)

Client: RAW_TEXT (report <playername>'s health)

 

The dilemma is that within my ProcessLocalChat() function, I want to be able to return control back to the event loop so that the program can continue running as usual (processing other messages, sending heartbeats, etc.), but at the same time "remember" that we are still waiting for the server to send us an ADD_NEW_ENHANCED_ACTOR message, and again "remember" what to do with that message.

 

I'm hoping that this is a common-enough problem that a more-or-less-standard solution already exists. Please post!

 

Otherwise, I'll be designing a system that may end up being a bit too complex for the task: a global vector of structures which contain at least this much information: 1) the identifier of the message we are waiting for, e.g. "ADD_NEW_ENHANCED_ACTOR"; 2) a function pointer which points to where program flow should go in case this message is received - which brings up a C++ question: is it legal to have a function pointer point to a member function of a particular instance of a class?; 3) extra information, the contents of which depends on the message we are waiting for; in the case of ADD_NEW_ENHANCED_ACTOR in my health-reporting bot, this would be the name of the player who requested to have his/her health reported.

 

The system I described is feasible, but if a better system already exists, I will be glad to hear it :icon13:

Edited by svk

Share this post


Link to post
Share on other sites

I need some help with network code structure.

 

I want to implement some sort of system that can handle "longer conversations" with the server. Currently, if the server sends me a message, I process it in a single pass and return happily with the message forgotten. However, there may exist cases in which the server would send me a message, and I would wish to reply to that message, and wait for an answer back.

 

For example, my bot is able to report someone's health (not that this is useful, but it serves for the purpose of an example). If someone says "my health" in local chat, my bot would answer with the current HP of whoever said the message. However, in order to do that, the following messages have to be sent between the server and the client:

 

Server: RAW_TEXT (<playername>: my health)

Client: SEND_ME_MY_ACTORS

Server: ADD_NEW_ENHANCED_ACTOR (info on <playername>)

Client: RAW_TEXT (report <playername>'s health)

 

The dilemma is that within my ProcessLocalChat() function, I want to be able to return control back to the event loop so that the program can continue running as usual (processing other messages, sending heartbeats, etc.), but at the same time "remember" that we are still waiting for the server to send us an ADD_NEW_ENHANCED_ACTOR message, and again "remember" what to do with that message.

 

I'm hoping that this is a common-enough problem that a more-or-less-standard solution already exists. Please post!

 

Otherwise, I'll be designing a system that may end up being a bit too complex for the task: a global vector of structures which contain at least this much information: 1) the identifier of the message we are waiting for, e.g. "ADD_NEW_ENHANCED_ACTOR"; 2) a function pointer which points to where program flow should go in case this message is received - which brings up a C++ question: is it legal to have a function pointer point to a member function of a particular instance of a class?; 3) extra information, the contents of which depends on the message we are waiting for; in the case of ADD_NEW_ENHANCED_ACTOR in my health-reporting bot, this would be the name of the player who requested to have his/her health reported.

 

The system I described is feasible, but if a better system already exists, I will be glad to hear it :wub:

 

Since for the most part you aren't really desperate to return a particular piece of information, I would make the code fairly task specific, rather than general.

In other words, instead of marking that you are looking for a message, mark that you are looking to complete a particular task.

In a simplistic pseudocode I'm thinking of something like

 

While ('exit)
{
 Message = ReadFromSocket(Message,nonBlocking,bytesRcvd)
 If bytesRcvd>0
 {
MessageStruct=ConstructStruct(Message)
I (MessageStruct.Type="RAW_TEXT")&contains(MessageStruct.Text,"my health")
{
   addToArray(getHealthPlayers,MessageStruct.Speaker)
   sendMessage(SEND_ME_MY_ACTORS)
}
I (MessageStruct.Type="ADD_NEW_ENHANCED_ACTOR")&(arrayHas(getHealthPlayers,MessageStruct.NewActorID))
{
  Actor=getActorDef(MessageStruct)
  sendMessage(RAW_TEXT,"/"_Actor.Name_" your health is "_Actor.Health)
  removeFromArray(getHealthPlayers,Actor.Name)
}
 }

}

 

Edit: Closed my code tag.

Edited by Kalach

Share this post


Link to post
Share on other sites

You may find that it's worth the effort to code the more general case in the long run.

The general issue is that you (often) need to build a consistant view of the 'EL World'

from the individual messages you get, it is rare that a single message will contain

everything you need.

 

I use the messages to build and remember information about the actors that

are seen and about 'myself'

 

just my 2cents worth

 

I need some help with network code structure.

 

I want to implement some sort of system that can handle "longer conversations" with the server. Currently, if the server sends me a message, I process it in a single pass and return happily with the message forgotten. However, there may exist cases in which the server would send me a message, and I would wish to reply to that message, and wait for an answer back.

 

For example, my bot is able to report someone's health (not that this is useful, but it serves for the purpose of an example). If someone says "my health" in local chat, my bot would answer with the current HP of whoever said the message. However, in order to do that, the following messages have to be sent between the server and the client:

 

Server: RAW_TEXT (<playername>: my health)

Client: SEND_ME_MY_ACTORS

Server: ADD_NEW_ENHANCED_ACTOR (info on <playername>)

Client: RAW_TEXT (report <playername>'s health)

 

The dilemma is that within my ProcessLocalChat() function, I want to be able to return control back to the event loop so that the program can continue running as usual (processing other messages, sending heartbeats, etc.), but at the same time "remember" that we are still waiting for the server to send us an ADD_NEW_ENHANCED_ACTOR message, and again "remember" what to do with that message.

 

I'm hoping that this is a common-enough problem that a more-or-less-standard solution already exists. Please post!

 

Otherwise, I'll be designing a system that may end up being a bit too complex for the task: a global vector of structures which contain at least this much information: 1) the identifier of the message we are waiting for, e.g. "ADD_NEW_ENHANCED_ACTOR"; 2) a function pointer which points to where program flow should go in case this message is received - which brings up a C++ question: is it legal to have a function pointer point to a member function of a particular instance of a class?; 3) extra information, the contents of which depends on the message we are waiting for; in the case of ADD_NEW_ENHANCED_ACTOR in my health-reporting bot, this would be the name of the player who requested to have his/her health reported.

 

The system I described is feasible, but if a better system already exists, I will be glad to hear it :P

Share this post


Link to post
Share on other sites

There are a fiew aproaches used in the network code of my bot (written in java).

 

The java aproach to doing most things is by useing a listener/event model for programming.

Useing this aproach there are a list of 'eventlistener' classes, these are implemented by a class to capture the event.

For example i have a 'ChatListener' class that captures chat events. Then i wrote a class that implements this interface, for example the interface includes public abstract void onPm(String person, String message); to process pm messages.

 

I have a main Connection class that handles the network code, just create the object and register your class to recieve the events.

 

 

Now more specifically for your bot example, you should store actors in memory (the client has an array of 1000 but i would just use a linked list or vector), when the bot recieves the command it checks the list of actors and sends the response back.

 

Another thing you can do (my bot does this) is store 'Account' information, this is stored in a database some user specific information such as how many times they have found the joker, when they last walked past the bot etc. There is also a vector of accounts in memory, in this there can be a reference to a 'Session' interface which i can use to have a command be processed over a series of messages. when you process commands i load the object (from memory or build from database) and if it has a reference to a session object i call a method in the session interface to do the processing. i'm not currently useing this for much but it was there for my mail system (but i haven't finished it in 6 months working on it) but it does allow multiple line messages to be recieved.

Share this post


Link to post
Share on other sites
Server: RAW_TEXT (<playername>: my health)

Client: SEND_ME_MY_ACTORS

Server: ADD_NEW_ENHANCED_ACTOR (info on <playername>)

Client: RAW_TEXT (report <playername>'s health)

I have something similar in my bot code, with the first queued command being for checking guild ranks.

the bot keeps track of them (it checks the ranks on startup and for people it hasn't got a rank for, or when it's told to recheck), but may want to recheck with the server. currently it only handles one delayed message at a time, but it could be redone with a queue.

I record: name of the actor who requested something; and what they requested (as an ENUM, currently, but there are many ways to do this).

when a message comes from the server, it's checked to see how it's handled. in the case of a "rank is" message, it checks if someone was waiting for that info. if so, it's sent off.

right now, the bot says "I'm busy, try again in a second" if someone requests something that needs a server check, and if the waiting-for variable isn't set to nothing... but that can be replaced with a queue if need be (but then, if the ping is 1/3 sec, unless the bot is really busy with asynch data checks, how many rejections would happen? few, I suspect)

of course... anything the bot knows itself can be sent on demand (subject to priveledge checks) even if a request is out :P

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×