MERVBot Example Code

From ASSS Wiki
Jump to: navigation, search

Here are a few code examples that show how to do common tasks in MERVBot plugins. You should not simply copy and paste this code, but try to understand and emulate it.

Note that some of the examples require the following inline function to be defined:

inline int abs(int n)
{
	return (n < 0) -n : n;
}

Some examples also require the following structs to be defined:

struct playerstats
{
	char name[20];	// pilot name

	int kills, deaths;
	Uint16 points, flagpoints;
	int flagtime, cflagtime;
	int flags;
	int flagkills;
	int cplaying_time;	// time stamp for playing time
	int ship;		// player ship

	int dmgdealt, dmgtaken;
};

struct freqdata
{
	playerstats pilots[100];

	int freqpoints;
	char freqname[20];
	int freqflagpoints;
	Uint16 freqteam;
	int freqflagtime;

	int flags;
	int kills;
	int deaths;
	int flagkills;

	int dmgdealt;
	int dmgtaken;

	int playercount;	// number of pilots on freq counted so far
};

No antiwarp in center of the map

This code demonstrates how to check players for whether their antiwarp is on, and then warn and revoke the prize if the player is in violation of the rules.

Requirements: Bot must have smod+ privilages. abs() function defined.

Let's first implement a generic function which we will need:

bool closeto(Player *p, int x, int y, int tolerance)
{
	int x_dist = abs(p->tile.x - x);
	int y_dist = abs(p->tile.y - y);
	return (x_dist < tolerance) && (y_dist < tolerance);
}

Now we declare a variable in spawn.h for the radius that defines the "center" of the map. For simplicity's sake, we will just define it in the botInfo constructor, but it can also be set via a command, INI, etc.

class botInfo
{
//	...

	// Put bot data here
	int radius;

public:
	botInfo(CALL_HANDLE given)
	{
//	...
	// Put initial values here
		radius = 35;

Now let's put the actual checking in spawn.cpp under EVENT_PlayerMove. This way, every time a player moves, the bot will check for a violation of the rule.

case EVENT_PlayerMove:
{
	Player *p = (Player*)event.p[0];

	// no anti in center
	if (p->awarp && closeto(p, 512, 512, radius))
	{
		sendPrivate(p, "*warn Antiwarp is not allowed in center.");
		sendPrivate(p, "*prize #-20");
	}
}

Just as a word of caution, players may be flooded with *prize #-20, and *warn statements under certain conditions.

Setting freq size depending on pilot count

This code shows how to change the maximum freq size depending on how many players are in ships.

Requirements: The bot must have Sysop or Arena-Owner privilages. Constructor must initialize countdown[0] to > 0 and freqchange to 0 in spawn.h.

In spawn.cpp:

case EVENT_Tick:
{
//	...
	if (countdown[0] == 0)
	{
		_listnode <Player> *parse = playerlist->head;
		int count = 0;

		while (parse)
		{
			Player *p = parse->item;

			if (p->ship != SHIP_Spectator)
				count++;

			parse = parse->next;
		}

		if ((count > 24) && (freqchange != 4))
		{
			sendPublic("?set Team:MaxPerTeam:4"); //Sysop command to modify arena config.
			freqchange = 4;
		}

		if ((count < 25) && (count > 14) && (freqchange != 3))
		{
			sendPublic("?set Team:MaxPerTeam:3"); //Sysop command to modify arena config.
			freqchange = 3;
	 	}

		String s("Max freq size ");
		s += freqchange;
		s += " (";
		s += count;
		s += " pilots in game)";
		sendPublic(s.msg);

		countdown[0] = 120; // reset timer to 120 seconds.
	 }

Tracking and announcing kill sprees

This code shows how to track the kills of a pilot and then announce when the pilot gets 10 kills in a row (without dying).

Requirements: The bot must have smod+ privilages.

Simply add the following code into spawn.cpp:

EVENT_PlayerDeath:
{
	Player *p = (Player*)event.p[0],
		 *k = (Player*)event.p[1];
	Uint16 bounty = (Uint16)(Uint32)event.p[2];
	Uint16 flags = (Uint16)event.p[3];

	set_tag(p, KILLS, 0);  // Reset KILLS to 0 on death.
	set_tag(k, KILLS, get_tag(k, KILLS) + 1);  // Increment killer's KILLS by 1.

	if (get_tag(k, KILLS) == 10)
		sendPublic("*arena " + (String)k->name + " has gotten 10 kills.");
}

Warp points

This code will warp a pilot to a coord when they are in a certain region.

Requirements: The bot must have smod+ privilages. The abs() function must be defined.

Let's first implement a generic function which we will need:

bool closeto(Player *p, int x, int y, int tolerance)
{
	return (abs(p->tile.x - x) < tolerance) && (abs(p->tile.y - y) < tolerance);
}

Then, in spawn.cpp, we just check every time a player moves to see if he has entered the warp point area. If he has, then warp him with the *warpto command to the warp destination.

case EVENT_PlayerMove: 
{
	Player *p = (Player*)event.p[0];

	if (closeto(p, 509, 509, 2)) // if pilot within 2 tiles of map coord 509,509
		sendPrivate(p, "*warpto 509 504");  // warp to coord 509,504

Of course, this is a very simple example. Real warping plugins, like TM Baw, will have a list of warp points set through an INI file. See its source for an example of this method.

Tracking flag data

This example demonstrates how to track plag data.

Requirements: Example freq/pilot structures must be defined.

Note: case EVENT_FlagDrop: {} gets called anytime theres a teamkill.

First, let's define a function to determine whether a pilot is in the structure list:

bool botInfo::GetPilot(Player *p)
{
	// first find the freq index for the pilot's freq
	for (freq = 0; freq < freqcount; freq++)
	{
		if (p->team == freqs[freq].freqteam)
			break;
	}

	// then find the pilot in the freq struct
	for (pilot = 0; pilot < freqs[freq].playercount; pilot++)
		if (strcmp(p->name,freqs[freq].pilots[pilot].name)==0)
			return true;

	return false;
}

To track flag data using above struct/functions, in spawn.cpp:

case EVENT_FlagGrab:
{
    //...

    if (GetPilot(p))  // function
    {
        freqs[freq].pilots[pilot].flags++;
        freqs[freq].flags++;

        if (freqs[freq].pilots[pilot].flags < 2) // didnt have a flag before, first flag
            freqs[freq].pilots[pilot].cflagtime = GetTickCount();  // time stamp when picked up flag
    }

    //...

To track flag data using built-in get/set tag, in spawn.cpp:

case EVENT_FlagGrab:
{
 //...

  set_tag(p, TAG_STAT_FS, get_tag(p, TAG_STAT_FS) + 1);
  set_tag(p, TAG_FLAGTIMER, GetTickCount());

 //...
}

Source: Catid's flagbot.

Get current flag times using struct format:

void botInfo::SetFlagTimes()
{
	// set current flagtime for pilots/freqs
	_listnode <Player> *parse = playerlist->head;
   
	while (parse)
	{
		Player *p = parse->item;

		if (GetPilot(p) && freqs[freq].pilots[pilot].flags > 0)
		{
			int time = (GetTickCount() - freqs[freq].pilots[pilot].cflagtime)/1000;

			if (PilotOnSquad(p))
				freqs[freq].freqflagtime += time;

			freqs[freq].pilots[pilot].flagtime += time;
			freqs[freq].pilots[pilot].cflagtime = GetTickCount();
		}

		parse = parse->next;
	}
}

A simple !spam feature

This example shows how to implement a !spam command, which allows a standard player to announce something to the zone every 60s. (In reality, the delay might need to be increased.)

First, let's declare a constant and a variable we will need in spawn.h:

#define SPAM_TIME 1	//in minutes
class botInfo
{
	//...

	// Put bot data here
	bool spamready;	//whether bot is ready to make another announcement

	//...

public:
	botInfo(CALL_HANDLE given)
	{
	//...

	// Put initial values here
	spamready = true;

Now, let's create the !spam command in commands.cpp:

case OP_Player: // Player-level commands
{    
	//...
	if (c->check("spam"))
	{
		// zone announcement "Need pilots to duel in ?go [arena] - [pilotname]"
		if (spamready == true)
		{
			char s[128];

			sprintf(s, "*zone Need pilots to duel in ?go %s - %s",
				arena,
				p->name);

			sendPublic(s);
		  
			spamready=false;
			countdown[0] = SPAM_TIME * 60; // set the timer for 60s
		}
		else if (countdown[0] < 0)
		{
		     sendPrivate(p, "Spam ability disabled.");
		}
		else
		{
			char s[128];

			sprintf(s,
				"%d minute timer between announcements. %dm:%2ds before next spam.",
				SPAM_TIME,
				countdown[0] / 60,
				countdown[0] % 60);

			sendPrivate(p, s);
		}
	}

Then, in spawn.cpp, mark spamready as true when countdown[0] hits 0:

case EVENT_Tick:
{
	//...

	if (countdown[0] == 0)
		spamready = true;
}

Implementing a simple queue for several "boxes"

This code demonstrates how to have a queue of players for dueling boxes.

In spawn.h, define a constant and declare a couple variables we'll need:

#define MAX_NEXT 8
class botInfo
{
	//...

	// Put bot data here
	Player *next[99][MAX_NEXT];
	int nextcount[99];

In spawn.cpp, implement a fuction to move up the queue when a person enters a box or simply leaves the queue:

void botInfo::MoveUp(int pos, int box_id)
{
	Player **box = next[box_id];	//define a pointer because we'll be accessing this a lot

	// decrement box's nextcount
	if (nextcount[box_id] > 0)
		nextcount[box_id]--;

	// move up the line for that box
	for (pos; pos < MAX_NEXT - 1; pos++)
		box[pos] = box[pos + 1];

	box[MAX_NEXT] = 0;
}

Reading text from a file and printing it to a player

This example demonstrates how to read text from a file line-by-line and print it to a pilot. It uses a staff text file, but the input file could be anything.

Requirements: Include <fstream.h>.

Simply implement the following code in commands.cpp:

case OP_Player:
{
	//...

	if (c->check("staff"))
	{
		ifstream file("staff.txt");
		char line[256];	//max length 255

		while (file.getline(line, 256))
			sendPrivate(p, line);

		file.close();
	} 

Printing a player stats table

This example demonstrates how to print the statistics stored in the example structures at the beginning of this section to the various freqs.

Requirements: The example structures must be defined. Function sendFreqs() must be defined.

In spawn.cpp:

void botInfo::DisplayPlayers(int freqcount)
{
	// Display Match player/freq stats in this format
	// ---------------------------------------------------
	// Squad: squad_name_1          K  D TK DMG DEALT TAKEN
	// ---------------------------------------------------
	// Player_1                     0  0  0         0     0
	// Player_2                     0  0  0         0     0
	// TOTAL:                       0  0  0         0     0
	// ---------------------------------------------------
	// Squad: squad_name_2          K  D TK DMG DEALT TAKEN
	// ---------------------------------------------------
	// Player_3                     0  0  0         0     0
	// Player_4                     0  0  0         0     0
	// Player_5                     0  0  0         0     0
	// TOTAL:                       0  0  0         0     0
	// ---------------------------------------------------

	char str[255];
	freqdata *freq = freqs;	//pointer for efficiency

	while (freqcount--)
	{
		sendFreqs("---------------------------------------------------");

		sprintf(str, "Squad: %-20s K  D TK DMG DEALT TAKEN", freq->freqname);
		sendFreqs(str);

		sendFreqs("---------------------------------------------------");

		for (pilot = 0; pilot < freq->playercount; pilot++)
		{
		  sprintf(str, "%-20s %8d %2d %2d %9d %5d", //use sprintf() to space output evenly
		           freq->pilots[pilot].name,
		           freq->pilots[pilot].kills,
		           freq->pilots[pilot].deaths,
		           freq->pilots[pilot].teamkills,
		           freq->pilots[pilot].dmgdealt,
		           freq->pilots[pilot].dmgtaken
		         );
		  sendFreqs(str);
		}

		sprintf(str, "TOTAL:                     %2d %2d %2d %9d %5d",
		        freq->kills, freq->deaths,
		        freq->teamkills,
		        freq->dmgdealt,
		        freq->dmgtaken
		       );
		sendFreqs(str);

		freq++;	//increment the pointer to next on the list
	}

	sendFreqs("---------------------------------------------------");
}

Checking for pilots in a region

This example demonstrates how to detect whether any pilot from a specific freq is in a region.

Requirements: User-defined function closeto needs to be implemented. See prior examples.

Just define the following function in spawn.cpp:

bool botInfo::FreqInBox(int freq)
{
	_listnode <Player> *parse = playerlist->head;

	while (parse)	//parse the playerlist
	{
		Player *p = parse->item;

		if (p->team == freq)	//only check players on specified freq
		if (closeto(p, coordX, coordY, 73) && (p->ship != SHIP_Spectator))
			return true;	//return true once player found

		parse = parse->next;
	}

	return false;	//return false if loop exits without finding a player
}

Creating logfiles using date and squad names

This example shows how to name logfiles descriptively, depending on variables such as squad names and the date.

Format: "[yeah]y[month]m[day]d [squadA] vs [squadB] [hour]h[minute]m.txt" (ex: 03y01m27d BLACKDRaGON vs Integral 05h08m.txt).

Note: Assuming squadA and squadB variables exist as char[20] or String.

	String logname;
	char u[100];
	time_t t;
	time(&t);	//get the local time
	tm *tmp = localtime(&t);	//then convert to the tm struct

	strftime(u, 99, "%yy%mm%dd ", tmp);	//"[year]y[month]m[day]d "
	logname = u;
	logname += squadA;
	logname += " vs ";
	logname += squadB;
	strftime(u, 99, "%Ih%Mm", tmp);	//"[hour]h[minute]m"
	logname += u;
	logname += ".txt";

Sending and logging messages to playing freqs or public

This example simply directs a message to freqs or the arena depending on a setting, and logs the messages depending on status.

Requirements: You must include <fstream.h>. You must have String logname declared as a global variable; and teammsgs, gameon, teamA, and teamB as members of the botInfo class.

In spawn.cpp:

void botInfo::sendFreqs(char *msg)
{
	if (teammsgs)
	{
		sendTeamPrivate(8025,msg);
		sendTeamPrivate(teamA,msg);
		sendTeamPrivate(teamB,msg);
	}
	else
	{
		String s(msg);
		s.prepend("*arena ",7);
		sendPublic(s.msg);
	}

	if (gameon)
	{
		ofstream outf(logname.msg, ios::app);
		outf << msg << endl;
		outf.close();
	}
}

Parsing player list and reading into struct data

This example demonstrates how to parse the playerlist and add all pilots and freqs to the example structures.

Requirements: Example structures must be defined.

To get freqs in a game where there are several freqs:

// read pilots into freq struct data from ingame and on playing freqs
void botInfo::GetFreqs()
{
	_listnode <Player> *parse = playerlist->head;
   
	while (parse)
	{
		Player *p = parse->item;

		if (p->ship != SHIP_Spectator && closeto(p, coordX, coordY, 73))
		{
			bool foundfreq = false;

			// look for freq in struct
			for (freq = 0; freq < freqcount && !foundfreq; freq++)
			{
				freqdata *freqd = freqs + freq;
				if (p->team == freqd->freqteam)
				{
					int p_index = freqd->playercount++;	//get player's index
					foundfreq = true;
					strncpy(freqd->pilots[p_index].name, p->name, 20);
				}
			}

			// didnt find freq in struct so add new freq
			if (!foundfreq)
			{
				freqdata *freqd = freqs[freqcount];

				if (manualsquads)
				{
					if (p->team == teamA)
						strncpy(freqd->freqname,squadA,20);
					else if (p->team == teamB)
						strncpy(freqd->freqname,squadB,20);
				}
				else
				{
					strncpy(freqd->freqname, p->squad, 20);

					if (freqcount == 0)
					{
						teamA = p->team;
						strncpy(squadA, p->squad, 20);
					}
					else
					{
						teamB = p->team;
						strncpy(squadB, p->squad, 20);
					}
				}
				 
				freqd->freqteam = p->team;

				strncpy(freqd->pilots[0].name, p->name, 20);

				freqd->playercount++;
				freqcount++;
			}
		}
		parse = parse->next;
	}
}

To get freqs in a game where there are only two teams:

// read pilots into freq struct data from ingame and on playing freqs
void botInfo::GetFreqs()
{
	_listnode <Player> *parse = playerlist->head;
   
	while (parse)
	{
		Player *p = parse->item;

		if ((p->ship != SHIP_Spectator) && ((p->team == teamA) || (p->team == teamB)))
		{
			freqdata *freqd = freqs;	//assume teamA

			if (p->team == teamB)
				freqd++;	//increment to teamB

			//copy player data
			pilot = freqd->playercount++;	//read, then increment
			strncpy(freqd->pilots[pilot].name, p->name, 20);
			freqd->pilots[pilot].cplaying_time = GetTickCount();
			freqd->pilots[pilot].ship = p->ship + 1;

			// slot name
			if (freqd->playercount < NUMBER_PILOTS)
				strncpy(freqd->slotname[pilot], p->name, 20);

			// if freq doesn't already have name, give it player squad name
			if (!manualsquads && (*p->squad != '\0'))
				strncpy(freqd->freqname, p->squad, 20);
		}

		parse = parse->next;
	}
}

Finding MVP from struct data

This example demonstrates how to parse the example structures to find the Most Valuable Player using the formula (2 * kills - deaths).

Requirements: Example structs defined.

int highest = -20;
int mvp = 0;

freqdata *freqd = freqs + mvpteam;	//pointer to save repeated indexing

for (pilot = 0; pilot < freqd->playercount; pilot++)
{
	int score = 2 * freqd->pilots[pilot].kills - freqd->pilots[pilot].deaths;
	if (score > highest)
	{
		mvp = pilot;
		highest = score;
	}
}

Tracking and displaying player weapon damage stats

This example shows how to track weapon damage using the weapon structure for clientprot.h and player tags.

In spawn.cpp:

case EVENT_WatchDamage:
{
//...
	if (PLAYING && p != k)  // if tracking stats and player isn't hurting self
	{
		if (wi.type == PROJ_PBomb)
		{
			set_tag(k, DMG_BOMB_DEALT, get_tag(k, DMG_BOMB_DEALT) + damage);
			set_tag(p, DMG_BOMB_TAKEN, get_tag(p, DMG_BOMB_TAKEN) + damage);
		}
		else if (wi.type == PROJ_BBullet)
		{
			set_tag(k, DMG_BULLET_DEALT, get_tag(k, DMG_BULLET_DEALT) + damage);
			set_tag(p, DMG_BULLET_TAKEN, get_tag(k, DMG_BULLET_TAKEN) + damage);
		}
		set_tag(k, DMG_TOTAL_DEALT, get_tag(k, DMG_TOTAL_DEALT) + damage);
		set_tag(p, DMG_TOTAL_TAKEN, get_tag(p, DMG_TOTAL_TAKEN) + damage);
	}

To print those statistics, just use the sprintf function:

sendPublic("Showing stats:");
		 
_listnode <Player> *parse = playerlist->head;				 
	
while (parse) 
{
	Player *p = parse->item;
	 
	if (get_tag(p, DMG_TOTAL_DEALT) > 0)
	{
		char str[256];
		sendPublic(p->name);
		sprintf(str, "Dmg Dealt: Total %04d, Bomb %04d, Bullet %04d  ",
			get_tag(p,DMG_TOTAL_DEALT),
			get_tag(p,DMG_BOMB_DEALT),
			get_tag(p,DMG_BULLET_DEALT),
		);
		sendPublic(str);
		sprintf(str, "Dmg TAKEN: Total %04d, Bomb %04d, Bullet %04d",
			get_tag(p,DMG_TOTAL_TAKEN),
			get_tag(p,DMG_BOMB_TAKEN),
			get_tag(p,DMG_BULLET_TAKEN)
			);
		sendPublic(str);
	}
	parse = parse->next;
}

Making bot spectate coordinates

This example demonstrates how to make the bot spectate certain coordinates (512, 600 in this case). This will allow the bot to receive more position packets and weapons packets for players in the area.

Warning: Put "tell(makeSendPosition(false));" in the EVENT_PositionHook handling or bad things may happen.

tell(makeFollowing(false));	//make sure bot is not following anyone
tell(makeFlying(true));		//turn over positioning to DLL
me->move(512 * 16, 600 * 16);	//move the bot to specified coordinates
tell(makeSendPosition(true));	//update bot's position on server