mikeBot client - server communication code version 0.1
© copyright 1997 mike warren contact info
WARNING : heavy construction
download        
Before downloading, it is important to note that this code is not freeware, nor is it GNU; to use it, you must:
  • Notify me before you release any project using this code (wheather with or without source)
  • Give me credit in the documentation.
  • Include all the files (with the possible exception of the source code) contained in the distributions below in your distribution.
  • and, give me 5% of gross revenue if you plan on selling the product, shareware or commercial.
If these terms are unsuitable, don't download anything.
nothing to download, yet. Soon though...
documentation        
Please, mail me at mbwarren@acs.ucalgary.ca with any questions you may have and I will answer them here.

classes/relationships

The above lists are supposed to represent the fact that qsocket, qack and qpacket are "stand-alone classes" while qcs serves as a base class for mbotbase which is a base for mbfire, mbnav, mbears, and mbtalk which all are base classes for mbot. Note that all classes except the first three are simply included as skepetons, as a suggestion of how to organize your own bot development.

mbnav is derived from mbfire so navigation can follow fire's target, and because in the very distant future, nav will need to override fire to hit things like switches.

class descriptions
qsocket

overview

qsocket handles all the outgoing/incomming socket calls, be it though the windows WinSock interface, or the unix one (just file descriptors, thankfully). It will also look up addresses such as quake.mike.com into #.#.#.# form. qsocket throws char * exceptions iff QTHROW is #define'd as non-zero.

Note that if you are using these classes as intended, you should never interract with qsocket

development status : stable, except for proxy issues.

member functions

private:
struct in_addr * atoaddr( char * address );
Returns an internet address structure from the address passed. The address can be in either www.foo.com or 127.0.0.1 form. NULL is returned on error.
public:
qsocket( char * addr=QCS_DEFAULT_SERVER, int port=QCS_DEFAULT_PORT );
The constructor. Obtains a file descriptor for the socket, at the specified address and port, and binds the local address. If QTHROW was defined, then char * exceptions are thrown upon error, otherwise perror() and exit() are used to deal with errors. It may also interest you no know that the socket option O_NDELAY or O_NONBLOCK (depending on OS) is set; this means that calling receive() with no data to be read will cause an error, with errno set to EWOULDBLOCK.

~qsocket();
Destructor; closes socket.

int send( char * buff, int size );
Sends the buffer pointed to by buff, which is of size size, and can be bigger, but better not be smaller. The number of bytes sent is returned, or < 0 on error, at which point errno is set appropriatly.

int receive( char * buff, int maxSize );
Simmilar to send; receives data (if any) from the socket. Returns bytes read, or < 0 on error. Note that a return value of 0 indicates EOF (i.e. the socket shutdown). maxSize is the maximum number of bytes buff can hold.

void changePort( int p )
Changes both the send and receive ports to p.

int getLocalPort();
Returns the current port the socket is receiving on.

int getRemotePort();
The port to which data will be sent.

int getFD();
Returns the file descriptor of the socket. Useful for select()ing in the "main" loop.

member variables

private:

struct sockaddr_in remote
The internet address structure for the remote system.

struct sockaddr_in local
Address of the local system (assigned by OS).

int fd
The file descriptor for the socket.

public:
none.
qpacket

overview

qpacket handles construction and deconstruction of quake packets, both client-server and vice versa. It is more efficient to not create and destroy the actual qpacket objects for each packet, hence the reset() function. qpacket also handles endianess convertion.

development statas : pretty stable, save the ::write() function.

member functions

private:

none.

public:

qpacket( int x=QP_MAX_PACKET_DATA )
Constructor; only allocates data to size specified by x and initializes variables.

void copy( qpacket & )
Copy constructor. Not defined as qpacket( qpacket & ) because, i think, CC wasn't using it; anyone know why?

~qpacket()
deletes data

void reset ()
Readies the packet for writeing. (no need to call this if the qpacket was just constructed).

int write( FILE * f, int x, vector & v )
Writes the packet out to f in valid .DEM format; the vector v is used for the facing info. in the .DEM headers, so send the bot's current facing. TODO: use mFile instead of FILE *(maybe...:) )

int init()
Updates pType and size based on the data in the data buffer. Only call this after filling data from, say, the socket

int update()
Makes the packet ready to send; adds header info. based on the current values of size and pType, so call changeType() before calling this when constructing packets. If you are using qpacket::send(), there is no need, since it calls update() for you.

type getType()
Returns the current packets type (i.e. qpacket::control, etc.)

void dumpHex()
void dumpChar()
If DEBUG has the DQP bit set, then these funcions dump the contents of data (including header) in hexadecimal or character format, respectivly. If not, they do nothing.

void changeType( type x )
Changes the packets type. Only use when constructing packets, when you must use it :)

void setReadPos( int x )
Used to move through the packet. Negative x's are bad; if x > size, nothing is done. You shouldn't find a need for this.

void ffwd()
Zooms you to the end of the packet. Useful when, say, adding things like center-printed messages to received packets before writing them to a .DEM file ;)

int send( qsocket * sock )
Sends the packet through the socket pointed to by sock, which better not be NULL. update() is called in this function, so no need to call it yourself before-hand. TRUE or FALSE is returned, depending on the success of the qsocket::send().

int getSize()
Returns the size of the data portion of the packet only.

int getReadPos()
int getWritePos()
int getMaxSize()
Self-explanatory.

char * getBuffer()
Use this with extreme caution, if at all; this gives you basically public access to the data.

int getNumber()
Returns the number, as defined in the header. Only valid for qpacket::reliableEnd, qpacket::reliableFragment and qpacket::unreliable.

int changeNumber( int x )
Changes the 4 bytes between data[4] and data[8] to the number specified. Same restrictions as getNumber(). This is only used for debugging by me, so it may dissappear in a later release; try not to depend on it. Tell me if you do.

int readLEint()
int readBEint()
short readLEshort()
short readBEshort()
float readLEfloat()
float readBEfloat()
unsigned char readByte()
All these functions are basically the same, except for the amount of data they read. All functions (besides readByte, obvioulsy) use readByte() to obtain their data. readBE<type> functions read some bytes out of data and return a <type>, assuming the <type> was in big-endian form in data. Similar for readLE<type> functions.

float readAngle()
This reads a byte, then multiples it by 360.0 / 256.0, then returns a float.

float readCoord()
Reads a short, and multiplies it by 0.125 before returning it.

char * readString()
Reads bytes from data until a 0-byte is read, at which point the string is returned. It will not exceed Q_MAX_STRING bytes in size. (1024, currently) You must delete the string returned, eventually, or you will be leaking memory

void addLEint( int )
void addBEint( int )
void addLEshort( short )
void addBEshort( short )
void addLEfloat( float )
void addBEfloat( float )
void addByte( char x )
void addData( char * x, int s )
void addAngle( float )
void addCoord( float )
void addString( char * )
These work very similarily to the read functions; addBE<type> will put the type into big-endian order in data.

qpacket & operator += ( qpacket & )
Adds two packets together. Adds to the end of the current packet, but copies from data[8] of the second, thus skipping the header and packet number. Used only for qpacket::reliableFragments by me. The header of the first is not adjusted, but size is.

member variables

private:

int size
Size of the data portion of the packet only. i.e. the 8 bytes for the header are not included in this number.

int maxSize
Size of data allocated.

int readPos
How far through the data the read calls are. This does not include the header (well, it does, but you don't have to read the header off)

int writePos
Same as readPos, but for write calls. Don't try to write the header on; use update()

char * data
Allocated in the constructor, deleted in the descructor.

type pType
The type of packet this is; uses the "nice" enum type defined below (in member variables).

char t1[ 4 ]
char t2[ 4 ]
Temporary storage for endianess convertion.

public:

enum type { control, reliableFragment, reliableEnd, acknowledge, unreliable, unknown }
The "nice" packet types. Self-explanatory.
qcs

overview

qcs handles all communications between the client (the bot) and the server. As much as possible, virtual functions are used to change things; this allows derived classes like mbotbase to override them and keep as much code as possible out of this already huge class.

Please tell me if you find any bugs in the decoding of messages. I would also welcome your suggestions for the class. Mail me or use the handy feedback form.

member functions

private:
qcs( qcs & ){}
NULL copy ctor, 'cause copying qcs is a Bad Thing.

protected:
int decodeControl( qpacket & )
int decodeReliable( qpacket & )
int decodeUnreliable( qpacket & )
int decodeAcknowledge( qpacket & )
int decodeQPacket( qpacket & )
int decodeMessages( qpacket & )
These decode various types of qpacket's. decodeMessages is for the unreliable game updates and is therefore huge :)

int send( qpacket & )
Sends a packet. Correctly handels waiting for acknowledges from reliable packets on the ack queue.

void initTables()
initialized each entry in modeltable and soundtable to NULL.

void deleteTables()
deletes any non-NULL entries in modeltable and soundtable.

void updateTableTypes()
This [will] update the modeltypes and soundtypes. unimplemented

public:
qcs()
constructor. Doens't allocate a qsocket; that is done by qcs::server(), which must be called.

virtual ~qcs()
desctructor. Sends a disconnect packet if connected.

int sendKeepalive()
int sendDisconnect()
virtual int sendMovement()
int sendConsole( char * )
Send various packets. You should only find a use for sendConsole().

int getSignonLevel()

int update()
Only call this if data is waiting on the socket! Use select() to determine this.

int getFD()
Gets the file descriptor used by the socket. Returns 0 if there is no valid socket. This is then used by select(). See above.

char * connect()
Sends a connection request to the server. Returns NULL if connection was accepted or a pointer to the rejection message if not.

void disconnect()
Hmmm

int recordDemo( char *)
Returns TRUE if the filename passed was opened succesfully and demo recording can now commence. Note that you can not [yet] start recording demos partway through levels.

int stopDemo()

int waitForPacket()
This waits for a packet to be received by the socket. It does block and is intended to wait for qpacket::control packets (only). (i.e. when connecting).

int server( char * ip, int port = 26000 )
If currently connected, disconnects, destroys sock and tries to reconstruct it with the ip passed. It will not attempt to connect and returns TRUE or FALSE.

int connected()
int recording()
Self-explanatory

void printInfo()
Queries server for its info and then prints it. Won't work if already connected.

void printPlayerInfo( int )
Queries the server for info on the specified player and prints it. Not valid when connected.

void printLevel()
Will only work if connected.

int isProxyConnected()
void waitForProxy()
Proxy stuff; unimplemented

virtual void changedLevel( char * x )
Passes the file name for the new level.
void rankings()
prints out players and frags (and colors).
virtual void entityUpdated( int x )
virtual void entityChanged( int x )
This is called if something major happeded to the entity (i.e. it got a new modeltable index)
virtual void timestampChanged( float x )
virtual void playerstateUpdate( int i,int v )
i is the index (0 <= i < 32) and v is the new value
virtual void gotSayMessage( char * x )
virtual void receivedDamage( int amt, vector & v )
The vector is a position on where the damage came from.
virtual void centerPrint( char * x )
virtual void updateHealth( int x )
virtual void updateArmour( int x )
virtual void updateCells( int x )
virtual void updateShells( int x )
virtual void updateRockets( int x )
virtual void updateNails( int x )
virtual void updateWeapon( int x )
These are fairly self-explanatory, but should be over-ridden by a derived class like mbotbase. They are called whenever the event they describe happens. There will (maybe) be more added to this list, but these won't change.

member variables

private:
qsocket * sock
Current socket, bound to current server. All communications go through here.

qproxy * proxy
unimplemented; for proxy-server, when it's ready.

char currentServerIP[ Q_MAX_STRING ]
String of the current server's address, either #.#.#.# or quake.spode.com mode.

protected:
FILE * demoFP
If a demo is being recorded, this is a valid file pointer. If no demo recording, this [must] be NULL. warning : this will likely be made private.

char qcs_message[ Q_MAX_STRING ]
This will be written at the back of all .DEM packets written (if any)

vector facing
Used to write .DEM packets; the facing for the camera (the bot's facing)

int fireTarget, navTarget
For adding particles in the .DEM packets; these are indexs into entities.

qpacket inPacket
qpacket outPacket
qpacket * fragments
Used for incoming, outgoing and reliable packets, respectivly. reliableFragments are copied into *fragments, then decoded when a reliableEnd packet arrives.

int outgoingUnreliable
int outgoingReliable
int incomingReliable
int incomingUnreliable
Packet counts. Don't reset on level changes. If a packet with a lower number than the "current" one, it is rejected (not decoded).

int haveConnection
TRUE iff the server is talking to me. (i.e. accepted connection request)

qack ackQueue
The queue of reliable packets to send

serverInfo info
Contains nifty info like maxPlayers; both qcs::printServerInfo() and qcs::decodeMessges() mess with this.

char * result
The reject messge from last connect request. NULL iff the connection was accepted.

float newTimestamp
float oldTimestamp
The timestamps sent by the server. They increment by 0.1 second, by default. (Each unreliable update packet contains one of these; if they go up by about 0.1, the server is sending updates every 0.1 second...this can be changed by the serverop)

int signonLevel
Signon level for level changes; if signonLevel >= 3, then the game has begun. Go kill stuff.

int myEntityNumber
An index into entities. Note that the index into players will be myEntityNumber-1 (this is true for all players; if they are playerNumber 3, their entity will be 3+1=4. Conversly, an entity ( <= info.maxPlayers +1 ) has playerNumber entityNumber-1) Watch out for illegal array indexs (i.e. < 0)

char * modeltable[ QCS_MAX_MODELS ]
char * soundtable[ QCS_MAX_SOUNDS ]
These contain the filenames of the prechached models and sounds.

int modeltypes[ QCS_MAX_MODELS ]
int soundtypes[ QCS_MAX_SOUNDS ]
These are "nice" model/sound types. unimplemented

qentity entities[ QCS_MAX_ENTITIES ]
Entity info.

qplayer players[ QCS_MAX_PLAYERS ]
Contains frags, hate, etc. for each player. QCS_MAX_PLAYERS is the theoretical limit, not the number the actual server allows. Use qcs::info.maxPlayers.

mbotbase

overview

member functions

member variables

qack

overview

qack handles remembering and resending (if not acknowleded) any client to server reliable packets (like console commands, through qcs::sendConsole()). Up to QACK_MAX_PACKETS packets can be in the queue at once, and packets are resent approx. every .3 seconds if not acknowledged (every three calls to qack::update(), which will be approximatly every third timestamp (server-client update) which occur every tenth of a second)

member functions

private:
int deletePacket()
Removes a packet from the queue

public:
qack()
~qack()
Ctor, dtor.

int addPacket( qpacket &, qsocket * )
Inserts (if possible) a packet into the queue, and sends it to the socket if the queue is empty already. TRUE/FALSE returned.

int gotAck( int, qsocket * )
Should be called when an acknowledge is received (duh); deletes the appropriate packet, if any.

int resend( qsocket * )
Call this whenever qcs::update() is called; only actually resends packets after QACK_MAX_WAIT calls.

void clear()
Forces the queue to be emptied. This does not mean the packets are send; any unsent (unacknowledged) packets will never be delivered

member variables

private:
qpacket packets[ QACK_MAX ]
int packetNumbers[ QACK_MAX ]
These are the actual elements in the queue.

int size
Number of elements in queue.

int tail
Index of the tail of the queue. (insertion point)

int head
Index of head of queue. (deletion point)

int lastSend
int current
Info so resend knows when to actually resend the next packet.

public:

none.
structures/auxillary classes

qplayer

int index
Index into qcs::entities

int frame
Frame can be used to determine if the (player) is living or dead.

int skin
Helpfull in determining armour type

vector origin
Where it is.

vector facing
Where it's looking.

vector velocity
How fast (and which direction) it's moving.

float lastTime
Last timestamp this entity was updated during.

qentity()
Ctor

void updateOrigin( vector & v, float t1, float t2 )
this will change. Currently send the (new) position, and new and old timestamps.
qentity
int index
into qcs::modeltable array.

int frags
char * name
int hate
int shirt
0 <= shirt < 13
int pants
0 <= pants < 13
qplayer()
~qplayer()
preprocessor definitions
QTHROW
When TRUE, classes which potentially throw exceptions do. When FALSE, errors simply print a message and exit()

UNIX
When TRUE, unix code is uncluded. Mutually exclusive with WIN

AIX
When TRUE, AIX-specific code is included. Should not be TRUE if UNIX is FALSE.

WIN
When TRUE, windows95 code is used; currently not implemented.

QPROXY
Turns proxy code on/off. Should currently always be FALSE 'cause the proxy code doesn't work yet.

DEBUG
Turns debugging code on, depending on the following values. (bitwise OR the DEBUG values you desire into DEBUG, like so: #define DEBUG (DQCS | DQX), or just #define DEBUG (0xFFFFFFFF) to turn it all on).
valuedebugged class/functionality
DQCSqcs class (warning:lotsa output!)
DQSqsocket class
DQPqpacket class
DQACKqack class; the reliable acknowledge queue