MERVBot Example Code
Contents
- 1 Example Code
- 1.1 No antiwarp in center of the map
- 1.2 Setting freq size depending on pilot count
- 1.3 Tracking and announcing kill sprees
- 1.4 Warp points
- 1.5 Tracking flag data
- 1.6 A simple !spam feature
- 1.7 Implementing a simple queue for several "boxes"
- 1.8 Reading text from a file and printing it to a player
- 1.9 Printing a player stats table
- 1.10 Checking for pilots in a region
- 1.11 Creating logfiles using date and squad names
- 1.12 Sending and logging messages to playing freqs or public
- 1.13 Parsing player list and reading into struct data
- 1.14 Finding MVP from struct data
- 1.15 Tracking and displaying player weapon damage stats
- 1.16 Making bot spectate coordinates
Example Code
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
Example A: No antiwarp in center of map. Warn the player and revoke the prize.
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