MERVBot Tutorial
Here is the ever-popular MERVBot Tutorial by Underlord:
Setup a MERVBot bot and project in visual c++
0) <a href="#0">Setting up a MERVBot bot</a>
Command.cpp
1) <a href="#1">Player commands</a> (!play, !squadA vs squadB)
Spawn.cpp
2) <a href="#2">Event descriptions</a> (describe events in spawn.cpp)
2) <a href="#2">Messaging </a>("*arena hi", ":player:*scorereset)
3) <a href="#3">MervBot Timer</a> (do this in 10 seconds)(countdown[n])
4) <a href="#4">Writing Functions</a> (bool IsInCenter(Player *p))
Useful operations
6) <a href="#6">Cycling players</a>
7) <a href="#7">Check if pilot is in safe zone</a>
8) <a href="#8">Random numbers</a>
9) <a href="#9">Time without using countdown[n]</a>
10) <a href="#10">Storing data for pilots</a>
11) <a href="#11">Output data in messages</a>
12) <a href="#12">Input/Output to files</a>
13) <a href="#13">Programming commands</a>
14) <a href="#14">Useful player data</a>
15) <a href="#15">Bot built in functions</a>
Example Code
a) <a href="#15a">No antiwarp in center of map</a>
b) <a href="#15b">Setting freq size depending on how many pilots in game</a>
c) <a href="#15c">Tracking kills and announcing when pilot gets 10 kills in a row without dying </a>
d) <a href="#15d">Warp pilot to coord when they are in a certain region</a>
e) <a href="#15e">Structures within structures</a>
f) <a href="#15f">Tracking flag data</a>
g) <a href="#15g">Way to do simple /!spam feature</a>
h) <a href="#15h">Implementing a simple stack to do "next in line for several 'boxes' at once"</a>
i) <a href="#15i">Reading any text from a .txt and printing it to pilot line by line</a>
j) <a href="#15j">Example of printing player stats grid</a>
k) <a href="#15k">Example of checking if any pilots are within a region</a>
l) <a href="#15l">Example of functions to get a pilot's struct id info from a name or *player info</a>
m) <a href="#15m">Example of creating a logfile name using date and squad names</a>
n) <a href="#15n">Sending messages to playing freqs or public and logging depending on status</a>
o) <a href="#15o">Example of reading in all player/freqs to struct data</a>
p) <a href="#15p">Example of finding MVP from struct data</a>
q) <a href="#15q">Print time stamp of event</a>
r) <a href="#15r">Simple way to track player bomb/bullet damage stats</a>
s) <a href="#15s">Simple way to print those stats</a>
t) <a href="#15t">Make bot spectate specific coordinates</a>
Contents
- 1 Setting up a MERVBot (plugin)
- 2 Player Commands - (command.cpp)
- 3 Event Calls
- 4 Messaging - How to use the messaging system
- 5 Timer - How to use the timing function
- 6 Writing Functions
- 7 Cycling players- How to search through the players in the arena
- 8 Checking if pilot is in a safe zone
- 9 Random number
- 10 Tracking time not using countdown[n]
- 11 Storing data for pilots
- 12 Output of data/messages
- 13 Input/Output to files
- 14 Simple programming commands
- 15 Useful Player data
- 16 Bot built in functions
- 17 Example Code
Setting up a MERVBot (plugin)
MERVBot download site: http://catid.sscentral.com/
MERVBot Forum: http://www.ssforum.net/ (Development - MervBot forum)
Download MERVBot Build 37 (DLL 6.5)
Unzip MERVBot.zip into a new folder. (example c:\program files\continuum\mervbot)
Unzip src.zip into "src" subfolder of that new folder (example c:\program files\continuum\mervbot\src)
Download DLL-plugin Tutorial
Note: if you only want to execute someone's premade plugin (.dll), skip to step 4, otherwise continue to learn how to make your own bot
Unzip Tutorial.zip (containing spawn.h, spawn.cpp, and command.cpp) into "/tutorial" subfolder of that new folder. (example c:\program files\continuum\mervbot\src\tutorial).
File descriptions:
- spawn.h = declare/initialize globals
- command.cpp = code for commands coming into bot (ie /!help, /!play, etc)
- spawn.cpp = code for main part of bot
Microsoft Visual c++
- Start Visual Studios 6.0.
- Click the Drop Down Menu labeled "File" at the top left of your screen.
- Click "New".
- On the next screen that comes up, choose from the Project tab, then Win32 Dynamic-Link Library
- Select the "/src" folder as the base folder (example c:\program files\continuum\mervbot\src)
- Name your project "mybot". This will make a "mybot" subfolder in your "src" folder. Click OK. (example creates c:\program files\continuum\mervbot\src\mybot)
- Choose to create an "Empty DLL project".
- Click "Finish".
- Click the Drop Down Menu labbled "Project".
- Click "Add To Project Files"
- Copy only spawn.h, spawn.cpp, and command.cpp from the "tutorial" folder into the this new folder. (example from c:\program files\continuum\mervbot\src\tutorial to c:\program files\continuum\mervbot\src\mybot)
- Click the Drop Down Menu labelled "Build".
- Click "Build (dll name)" - where (dll name) is "mybot"
- Go into your "mybot" folder and look for a folder named "Debug" (example c:\program files\continuum\mervbot\src\mybot\debug)
- Your new DLL will be in that folder. (example mybot.dll)
- Copy mybot.dll to your base folder that has mervbot.exe in it (example c:\program files\continuum\mervbot)
Run your bot dll
To run your bot you need your DLL (mybot.dll), Commands.txt, MERVBot.exe, MERVBot.ini, Operators.txt, Spawns.txt, subspace.bin, and zlib.dll all in one folder (example c:\program files\continuum\mervbot).
- Edit spawns.txt (only one line of text in file needed)
bot_name : pw_for_bot_name : arena: dll_plugin : optional_staff_password
Example:2v2-Bot-League : botpw : 2v2a : 2v2league : staffpw
- You should create your bot name and pw using your Continuum client as you would create any new name.
- There is no * in front of staffpw.
- Edit MERVBot.ini
[Login] Zone=216.33.98.254:21000 // make that your zone IP:PORT available from zone.dat in Continuum dir
- Edit operators.txt
access_level : name :
Example:4:my_name: 4:another_sysop: 3:other_person:
- Run MERVBot.exe
Double click MERVBot.exe, the bot should now enter the zone. Note: Bot needs to be on vip.txt or have moderator+ access to enter a zone.
- Modify your MERVBot code
You can open your MERVBot project by opening "mybot.dsw" file with visual c++. (example c:\program files\continuum\mervbot\src\mybot\mybot.dsw) Edit the spawn.h, spawn.cpp, and command.cpp to create your plugin, then build, copy your updated DLL to your MERVBot.exe folder and then execute the bot. Use the tutorial to get ideas on how to implement certain types of features into the bot.
Player Commands - (command.cpp)
This section describes how to implement player commands into your plugin. Commands are sent to the botInfo::gotCommand function in command.cpp.
Example (makes bot reply to !test with "hi"):
void botInfo::gotCommand(Player *p, Command *c) { switch (p->access) { case OP_Moderator: { // handle moderator-operator commands here. } case OP_Player: //appropriate staff rank here. { if (c->check("test")) //replace "test" with whatever command you want { //put your command code here sendPrivate(p,"hi"); //example } }
How to have commands with numerical parameters
Example (!test #):
if (c->check("test")) { // reads in test #, default to 1 if invalid number input int temp = 1; if (isNumeric(c->final)) temp = atoi(c->final);
How to have player name as input
Example (!rank player):
if (c->check("rank")) { String player_name = c->final; if (player_name.IsEmpty()) // default name to self if invalid name player_name = p->name;
How to have multi-parameter input
Use the CRT function sscanf() to scan the string for the values.
Example (!squads squadA vs squadB or !squads teamA:squadA:teamB:squadB):
else if (c->check("squads")) { char squadA[20], squadB[20]; int teamA, teamB; strncpy(squadA, "", 20); strncpy(squadB, "", 20); int n_found; //Note: %[A-Za-z ] is equivalent to %s, but allows an internal space. //scan the string for the two squads separated by " vs " n_found = sscanf(c->final, "%[A-Za-z ] vs %[A-Za-z ]", squadA, squadB); //if that fails, scan the string for freqA:squadA:freqB:squadB if (n_found < 2) sscanf(c->final, "%d:%[A-Za-z ]:%d:%[A-Za-z ]", &teamA, squadA, &teamB, squadB); }
Help Menu
When a player sends !help to the bot, MERVBot calls botInfo::gotHelp() in each plugin loaded.
void botInfo::gotHelp(Player *p, Command *c) { if (!*c->final) { sendPrivate(p, "4v4 Bot General Commands:"); sendPrivate(p, "------------------------"); sendPrivate(p, "!caps - get captain names"); sendPrivate(p, "!roster <squad> - get roster of a squad"); sendPrivate(p, "!schedule- get current schedule"); sendPrivate(p, "!score - get current score");
Event Calls
MERVBot is event based, so when making a bot you need to decide what will happen at certain events. Normal plugins need to consider what happens when bot enters arena, player enters arena, player leaves arena, player events like kill, shipchange, teamchange, spec, move then any other relevant events to your bot. Just worry about events that are relevant to the tasks your bot is doing.
MERVBot sends events to botInfo::gotEvent() in spawn.cpp. Each supported event is already present and categorized in gotEvent(), along with the paramters that MERVBot sends with the event.
See dllcore.h for a list of current events and their descriptions.
Messaging - How to use the messaging system
Private message - void sendPrivate(Player *player, char *msg);
Examples:
sendPrivate(p,"hi"); String s="test"; sendPrivate(p,s); String s="test"; s += "ing"; sendPrivate(p,s); char captain1[20]; char captain2[20]; strncpy(captain1,"",20); strncpy(captain2,"",20); sendPrivate(p,(String) captain1 + " and " + (String) captain2 + " are the captains.");
Team message - void sendTeamPrivate(Uint16 team, char *msg);
Examples:
a) sendTeamPrivate(8025,"hi spec freq");
b) Uint16 test=0; sendTeamPrivate(test,"hi freq 0");
Public message - void sendPublic(char *msg);
Example: sendPublic("*arena " + (String) p->name + " is now a captain");
Chat channel message - void sendChannel(char *msg);
Example: sendChannel("hi chat channel");
Remote private message - void sendRemotePrivate(char *name, char *msg);
Example: sendRemotePrivate("Player01", "hi");
Note: to have bot print several lines of text fast it needs sysop in the
arena (sysop in arena bot first spawns to also) otherwise it'll print slow to avoid being
kicked for spam
Timer - How to use the timing function
Each time MERVBot sends an EVENT_Tick to a plugin (once a second), the default handler code decrements each value in an array of countdowns. You can modify the number of countdowns and add code to occur at a specific value for one of the countdowns.
Setup number of timers and initialize in spawn.h:
class botInfo { #define COUNTDOWNS 10 // how many countdowns you want int countdown[COUNTDOWNS]; // this gives you 10 timers // unrelated code public: botInfo(CALL_HANDLE given) { countdown[0] = 0; countdown[1] = 60; // 60 seconds // // initialize values // countdown[9] = 5*60; // 5 minutes
Using timer functions in spawn.cpp:
case EVENT_Tick: { for (int i = 0; i < COUNTDOWNS; ++i) //cycles through each countdown you have --countdown[i]; //note that countdowns will continue decrementing past 0. if (countdown[1] == 2) // when timer #1 hits two seconds { // do stuff here when timer #1 hits 2 seconds // example: sendPublic("two seconds left, setting timer to 1 minute"); // example: countdown[1] = 60; // change timer #1 value }
You can then have events (such as EVENT_PlayerDeath) change the value of a countdown to make the bot do something a set time after an event occurs.
Writing Functions
For this example, we will take the function called closeto, which determins if a player exists in an specific radius around a point.
bool closeto(Player *p, int x, int y, int tolerance) { // Requires the function abs() to be declared elsewhere. // Return if player p is in area of square with center x, y // and radius = tolerance return (abs((p->tile.x) - x) - tolerance) && (abs((p->tile.y) - y) - tolerance); }
Now to apply this function to a MervBot plugin, you need to write it into the spawn.cpp - at the top of the file in the //////// DLL "import" //////// setion, like as below:
//////// DLL "import" //////// bool closeto(Player *p, int x, int y, int tolerance) { // Requires the function abs() to be declared elsewhere. // Return if player p is in area of square with center x, y // and radius = tolerance return (abs((p->tile.x) - x) - tolerance) && (abs((p->tile.y) - y) - tolerance); }
If you want your function to have access to the data from spawn.h botInfo class, you make the function apart of it. Notice, we add the botInfo:: infront of the function name, in spawn.cpp.
bool botInfo::closeto(Player *p, int x, int y, int tolerance) { ... }
In spawn.h, add your method's prototype without botInfo::, it will look like this:
... botInfo(CALL_HANDLE given) { ... } bool closeto(Player *p, int x, int y, int tolerance); // Your function prototype. void clear_objects(); //provided by Catid, and already exists. void object_target(Player *p); //provided by Catid, and already exists. ... };
Notice it is similar to that in your spawn.cpp, but without the botInfo::, and a trailing ;.
As a side-note, with C++ we are able to pass variables by reference. We'd modify our example function accordingly. If you are unfamiliar with this concept, I suggest you read up on some C++ resources.
bool botInfo::closeto(Player *p, int& x, int& y, int& tolerance) // note the &
To use our original example we'd implement it accordingly:
bool is_close = closeto(p,x,y,tolerance); // if x is changed in closeto() its saved here
From time-to-time you will need to pass an array to a function. An example illustrating this is:
int freqs[5]; // declare our example data. my_function(freqs); // call function - notice freqs and not freqs[5] or freqs[] void my_function(int freqs[]) {} // function - notice freqs[] and not freqs[5] or freqs
Cycling players- How to search through the players in the arena
With MervBot all player related data is stored in a linked-list. To search through the players in the arena, use the following example:
_listnode <Player> *parse = playerlist->head; while (parse) { Player *p = parse->item; // do functionality here // Example 1: sendPrivate(p,"*watchdamage"); // turns on all pilot's watchdamage // Example 2: if (p->safety != 0) sendPrivate(p,"*spec"); // spec all pilots in safe zone parse = parse->next }
As an example of use, assuming our bot has smod+ privilages, the following code will set all non-spectator players to a specific ship. First begin by adding the following function prototype to the spawn.h in the botInfo class.
void handleCmdSetShip(BYTE ship);
In spawn.cpp add:
void botInfo::handleCmdSetShip(BYTE ship) { // This is NOT an efficient piece of source code by ANY means. // Note, a suggestion I would recommend is to add validation // to the variable ship, keeping it in the range of 0 to 9, // where 0 - Warbird, and 9 - Spectator. _listnode <Player> *parse = playerlist->head; while (parse) { Player *p = parse->item; if ( p->ship != ship && p->ship != SHIP_Spectator ) { sendPrivate(p, 0x00, "*setship " + (String)ship); } parse = parse->next; } }
To use:
handleCmdSetShip(SHIP_Warbird); //SHIP constants are in clientprot.h
Checking if pilot is in a safe zone
MervBot keeps track of whether a player is in safe or not by accessing a member of the Player class.
- if (p->safety != 0) // pilot is in a safe zone
- if (p->safety == 0) // pilot is NOT in a safe zone
An example goes as follows, in spawn.cpp:
... EVENT_PlayerMove: { Player *p = (Player*)event.p[0]; if ( p->safety ) // player is in safe zone. { // do something. } if ( !p->safety ) // player NOT in safe zone. { // do something. } } ...
Random number
To use these examples, two required include statements must be used:
#include "time.h" //provides time() function. #include "stdlib.h" //provides srand() and rand() functions.
Example 1: Generate a completely random number.
Use:
srand(time(NULL)); // seed random number generator. rand(); // randomize. int temp = (int) (51 * ((float)rand()/RAND_MAX)); // the above line returns a random integer between 0 and 51. // Note: RAND_MAX is a global constant defined in stdlib.h
Example 2: Pick a random pilot.
Note: A required user-defined function, getInGame(), must be created and declared for this example to work.
int temp = GetTickCount() % getInGame(); // getInGame() = how many pilots in arena Player *rabbit = NULL; _listnode <Player> *parse = playerlist->head; while (parse) { Player *p = parse->item; if (p->ship != SHIP_Spectator) // if player is not a spectator. { if ( !(--temp) ) // pointer-arithmetic. decrement temp, if its 0, this is the rnd pilot. { rabbit = p; break; } } parse = parse->next; }
Tracking time not using countdown[n]
This is a solution to a common problem of determining the amount of time it takes for something to occur. Using basic math, we record a start-time B, and an end-time E, both in the unit of seconds, we calculate the time elapsed by E-B.
Lucky for us, C++ provides a function called GetTickCount() that is a measurement of time (milliseconds) that we can use for such cases.
So:
int begin = GetTickCount(); // do some code here. int end = GetTickCount(); int delta = end - begin; // the change of time in milliseconds int delta_seconds = delta / 1000; // the change of time in seconds.
Storing data for pilots
There are several ways to store data for pilots (ie tracking flagtime or kills in a period of time)
1) get/setTag - use if you only want to track data until pilot leaves arena then its erased
built in tags track by an ID that is reset when pilot leaves/enters arena, so loses track of data once they leave arena
2) modified perm get/setTag - use if you want to track all pilots even if they leave (advantage - easier to sort by player)
also can track near unlimited amount of pilots
3) structs - use to track all pilots even if they leave, (advantage - easier to sort by freqs), have to specify bound of players
note: 2 and 3 are similar in effect, mostly the difference is in how you are able to search through data
you need to decide which method of storing data is best for each bot depending on what it does
beware using modified perm get/setTag if bot is in an arena for long periods of time, data is not reset so the linkedlist could get huge
// initialize values in spawn.h at very top
1) Built in get/setTag method
#define DMG_DEALT 02) Modified permanent get/setTag method
#define DMG_TAKEN 1
// in spawn.cpp initialize the values on arena-enter and player-enter
case EVENT_ArenaEnter: {
_listnode <Player> *parse = playerlist->head;
while (parse) // do for all pilots in arena when bot enters
{
Player *p = parse->item; // get pilot
set_tag(p, DMG_DEALT, 0); // initialize to 0
set_tag(p, DMG_TAKEN, 0);
sendPrivate(p,"*watchdamage"); // optionally turn on player *watchdamage
parse = parse->next; // get next pilot
}
}
case EVENT_PlayerEntering: {
set_tag(p, DMG_DEALT, 0); // initialize to 0
set_tag(p, DMG_TAKEN, 0);
sendPrivate(p,"*watchdamage");
// then somewhere edit the tag values
case EVENT_WatchDamage: {
// sets tag for k (shooter) to be old value plus damage dealt currently
set_tag(k, DMG_BOMB_DEALT, get_tag(k, DMG_BOMB_DEALT) + damage);
// how to retrieve the tag values
// as a command in spawn.h
else if (c->check("showstats")) {
int temp = get_tag(p, DMG_TOTAL_DEALT);
String s = "You've done ";
s += temp;
s += " damage so far!";
sendPrivate(p,s);
// kill tags when player leaves arena
case EVENT_PlayerLeaving: {
killTags(p);
// same as get/setTag with some modifications to the tag code, then can use tags exactly as above
// spawn.h, add char name[20]; into struct PlayerTag
struct PlayerTag
{
Player *p;
char name[20];
int index;
int data;
};
// spawn.cpp, modifications
case EVENT_PlayerLeaving:
{
Player *p = (Player*)event.p[0];
// killTags(p); // remove so tag not deleted on arena exit
int botInfo::get_tag(Player *p, int index)
{
}
_listnode <PlayerTag> *parse = taglist.head;
PlayerTag *tag;
while (parse)
{
tag = parse->item;
// if (tag->p == p)
if (strcmp(tag->name,p->name)==0) // now tracking by player name, not ID
if (tag->index == index)
return tag->data;
parse = parse->next;
}
return 0;
}
void botInfo::set_tag(Player *p, int index, int data)
{
_listnode <PlayerTag> *parse = taglist.head;
PlayerTag *tag;
while (parse)
{
tag = parse->item;
//if (tag->p == p)
if (strcmp(tag->name,p->name)==0) // now tracking by player name, not ID
if (tag->index == index)
{
tag->data = data;
return;
}
parse = parse->next;
}
tag = new PlayerTag;
// tag->p = p; // not tracking by ID anymore
strncpy(tag->name, p->name, 20); // tracking by player name
tag->index = index;
tag->data = data;
taglist.append(tag);
3) Using Struct's
struct name
{
// variables
};
// Example: (in spawn.h)
class botInfo
{
struct freqdata {
int kills;
int deaths;
};
freqdata freqs[100]; // 100 of those structs
// then access it in spawn.cpp using
freqs[56].kills = 1;
Output of data/messages
using normal Strings
// does *arena X pilots left in the game.
String s = "*arena ";
s += temp; // some variable (int)
s += " pilots left in the game.";
sendPublic(s)
OR
sendPublic("*arena " + (String) temp + " pilots left in the game.");
example using sprintf to align/space data
// output data
will be in this approximate format (not lined up perfectly because of html)
//
// Squad: squadname
PTS FPTS K D DMG DEALT
TAKEN F FK FLT
// ----------------------------------------------------------------------------------------
//
PlayerA
10000 500 116 101 9999 99999 10 150 980:55
// PlayerB
500
200 7 5
9999 99999 5
3 0:04
char str[255];
sendPublic("*arena----------------------------------------------------------------------------------");
sprintf(str, "*arena
Squad: %-20s PTS FPTS K
D DMG DEALT TAKEN F FK FLT",freqs[freq].freqname);
sendPublic(str);
sendPublic("*arena----------------------------------------------------------------------------------");
// assuming existing freqs struct with data
for (pilot=freqs[freq].playercount-1; pilot>=0; pilot--)
{
// on freq squad so print stats
char outString[255];
sprintf(outString,
"*arena %-20s %12d %8d %3d %3d %10d %6d %2d %3d %3d:%02d",freqs[freq].pilots[pilot].name,
freqs[freq].pilots[pilot].points,
freqs[freq].pilots[pilot].flagpoints, freqs[freq].pilots[pilot].kills,
freqs[freq].pilots[pilot].deaths,
freqs[freq].pilots[pilot].dmgdealt, freqs[freq].pilots[pilot].dmgtaken,freqs[freq].pilots[pilot].flags,
freqs[freq].pilots[pilot].flagkills, freqs[freq].pilots[pilot].flagtime
/60, freqs[freq].pilots[pilot].flagtime %60);
sendPublic(outString);
}
// Notes: sprintf
format = sprintf(output char string, spacing, variables)
// Notes: s = chars, d = integer, - = left align, right align default
// Notes: doing
%02d = put 0 in front if not 2 digits, %3d:%02d makes 0:04 format
<a name="12"></a>
Input/Output to files
Input to file
// example reading from duel.ini looking for line that starts with MaxBoxes= then taking the next char as value to store as
// MAX_BOXES (ie duel.ini = MaxBoxes=5)
- include <fstream>
using namespace std;
ifstream file("duel.ini");
char line[256];
// read in MaxBoxes=X
while (file.getline(line, 256))
{
if (CMPSTART("MaxBoxes=", line))
{
MAX_BOXES = atoi(&(line[9]));
break;
}
}
Output to file
// normal char output
#include <fstream>
using namespace std;
ofstream file("duelleaguestat.inc", ios::app); // app = put all data at end of file
file << squad1<< endl; // squad1 = char[20]
file << " vs "<< endl;
file << squad2<< endl; // squad2 = char[20]
// how to output String'sto file (key is converting String to (char*) to file write)
String str = freqs[freq].slotname[slot];
str += ", Repels: " + (String)(int) t->repel;
outf << endl;
outf << (char*) str;
// date and time stamp
#include "time.h"
char u[100];
time_t t=time(NULL);
tm *tmp = localtime(&t);
strftime(u,99,"%c",tmp);
sendPublic("Date and time: " + (String) u);
Example reading input from file using "GetPrivateProfileString" (from rampage plugin)
format of rampage.ini
7=is on a killing spree! (6:0)
10=is opening a can of whoop-ass! (9:0)
read input
rampageini.h
#pragma once
- ifndef RAMPAGEINI_H
- define RAMPAGEINI_H
- define NUM_RANKS 10
- define BUFFER_LEN 256
struct RampageSettings
{
char quotes[NUM_RANKS][BUFFER_LEN];
};
void LoadSettings(RampageSettings &setts);
- endif // RAMPAGEINI_H
rampageini.cpp
#include "rampageini.h"
static char buffer[BUFFER_LEN];
static char path[BUFFER_LEN];
- include "../algorithms.h"
- define WIN32_LEAN_AND_MEAN
- include <windows.h>
char *rank_type[10] = {
"7",
"10",
};
void LoadSettings(RampageSettings &setts)
{
GetCurrentDirectory(BUFFER_LEN - 64, path);
strcat(path, "\rampage.ini");
for (int i = 0; i < NUM_RANKS; ++i)
{
GetPrivateProfileString("Comments", rank_type[i], "-ERROR-", setts.quotes[i], BUFFER_LEN, path);
}
}
Simple programming commands
The following are some brief C++ tid-bits.
Simple Commands:
- declare / initialize variables
- Example: int r = 10; int q; q = 5;
- if ( condition ) { }
- Example: if (a > b) { a++; }
- if ( condition ) { } else { }
- Example: if (b <= 0) { b--; } else { a++; }
- while ( condition ) { }
- Example: while (b > 0) { b--; }
- for ( initialize ; condition ; increment/decrement ) { }
- Example: for (a=1; a < 10; a++) { a = a + b; }
Arrays:
- single dimension:
- int teams[100]; // create 100 hundred teams 0-99
- teams[50] = 1;
- multi-dimensional:
- int teams[100][50]; // multidimensional array
- teams[99][49] = 2;
- variable (or dynamic) size :
- String *list = new String[amount+1]; // string array with size amount (variable) + 1;
- list[amount-1] = "hi";
- Note: Remember to delete []list;
Structures
struct name { ... // structure variables and functions. }; // NOTE: trailing ;
Example:
struct freqdata { int kills; int deaths; }; freqdata freqs[100]; // 100 of those structs freqs[56].kills = 1; // access struct
Switches:
switch (variable) { case n: { } break; case m: { } break; default: break; }
Example:
switch (p->ship) { case SHIP_Warbird: { sendPrivate(p, "You're in a warbird"); } break; default: break; }
Useful Player data
As stated earlier in the tutorial, MervBot stores useful player data internally as Player objects, see player.h for implementation details.
- p->name = player name stored as char[20]
- Note: SubSpace protocol allows for usernames to be 19+ in length, do not rely on this for player-name comparisions.
- p->squad = player squad stored as char[20]
- p->ship = ship (0-9) enumerated as SHIP_Warbird, SHIP_Spectator, etc..
- p->safety = if ship is in safety zone (boolean)
- p->bounty = player bounty
- p->energy = player energy (have bot with *energy on to get accurate readings)
- p->flagCount = how many flags player is holding
- p->team = player frequency
- p->(burst, repel, thor, brick, decoy, rocket, portal) = how many items of that type player has
- p->(stealth, cloak, xradar, awarp, ufo, flash, safety, shields, supers) = if player has that item on (boolean)
- p->score.killPoints = player kill points
- p->score.flagPoints = player flag points
- p->score.wins = player kills from f2
- p->score.losses = player deaths from f2
Bot built in functions
// useful MervBot commands to control what the bot is doing
// player.cpp
Player::move(Sint32 x, Sint32 y) // example me->move(512,512) - bot moves to coord 512 512
Player::clone(Player *p) // example me->clone(p)
// dllcore.cpp (descriptions of functions in dllcore.h)
BotEvent makeEcho (char *m);
BotEvent makeSay (int t, int s, int i, char *m);
BotEvent makeShip (int s);
BotEvent makeTeam (int t);
BotEvent makeGrabFlag (int f);
BotEvent makeSendPosition (bool reliable);
BotEvent makeDropFlags ();
BotEvent makeDeath (Player *p);
BotEvent makeAttach (Player *p);
BotEvent makeDetach ();
BotEvent makeFollowing (bool f);
BotEvent makeFlying (bool f);
BotEvent makeBanner (BYTE *b);
BotEvent makeDropBrick ();
BotEvent makeFireWeapon (void *weapon_info);
BotEvent makeToggleObjects (Uint16 player, Uint16 *objects, int num_objects);
BotEvent makeSpawnBot (char *name, char *password, char *staff, char *arena);
BotEvent makeChangeArena (char *name);
BotEvent makeChangeSettings (_linkedlist <String> *settings);
// example how to use them:
tell(makeFollowing(false));
// look in Commands.txt , command.cpp (core), or /!help to bot to see all bot external commands (example /!go <arena>)
Example Code
Example A: No antiwarp in center of map. Warn the player, and revoke the prize.
In order for this code to work correctly, the bot must have smod+ privilages.
Lets first implement two functions which we will need to accomplish this task:
bool closeto(Player *p, int x, int y, int tolerance) { return (abs((p->tile.x) - x) < tolerance) && (abs((p->tile.y) - y) < tolerance); } inline int abs(int n) { if (n < 0) return -n; else return n; }
We should define the radius of antiwarp checking, this can be done several ways, for sake of simplicity, here is a quick-plop-in for spawn.h:
class botInfo { bool CONNECTION_DENIED; ... // Put bot data here int radius; public: botInfo(CALL_HANDLE given) { ... // Put initial values here radius = 35; ...
Locate and add into spawn.cpp accordingly:
case EVENT_PlayerMove: { Player *p = (Player*)event.p[0]; // no anti in center if ((p->ship != SHIP_Spectator) && (p->awarp)) { if (closeto(p, 512, 512, radius)){ sendPrivate(p, "*prize #-20"); sendPrivate(p, "*warn Antiwarp is not allowed in center."); } } ...
Just as a word of caution, players may at times be flooded with *prize #-20, and *warn statements under certain conditions.
<a name="15b"></a>
b) Setting freq size depending on how many pilots in game
case EVENT_Tick: {
if (countdown[0] == 0) { // assuming countdown[0] initialized to > 0 in spawn.h, freqchange=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");
String s;
s = "Max freq size 4 (";
s += count;
s += " pilots in game)";
sendPublic(s);
freqchange = 4;
}
if ((count < 25) && (count > 14) && (freqchange != 3))
{
sendPublic("?set team:maxperteam:3");
String s;
s = "Max freq size 3 (";
s += count;
s += " pilots in game)";
sendPublic(s);
freqchange = 3;
countdown[0] = 120;
}
Example C: Tracking kills and announcing when pilot gets 10 kills in a row without dying.
In order for this code to work correctly, the bot must have smod+ privilages.
Locate EVENT_PlayerDeath in spawn.cpp (see note.):
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]; // NOTE: assuming tags are setup (see storing data section). set_tag(p, KILLS, 0); // pilot died, reset to 0 kills in a row set_tag(k, KILLS, get_tag(k, KILLS) + 1); // pilot killed someone, increment kills in a row by 1 if (get_tag(k, KILLS) == 10) sendPublic("*arena (String) k->name + " has gotten 10 kills."); ... }
Example D: Warp pilot to coord when they are in a certain region.
In order for this code to work correctly, the bot must have smod+ privilages.
Lets first implement two functions which we will need to accomplish this task:
bool closeto(Player *p, int x, int y, int tolerance) { return (abs((p->tile.x) - x) < tolerance) && (abs((p->tile.y) - y) < tolerance); } inline int abs(int n) { if (n < 0) return -n; else return n; }
In spawn.cpp, EVENT_PlayerMove:
case EVENT_PlayerMove: { Player *p = (Player*)event.p[0]; if (closeto(p, 509, 509, 2)) // if pilot within 2 of map coord 509,509 { sendPrivate(p, "*warpto 509 504"); // warp to coord 509,504 } ...
<a name="15e"></a>
e) Structures within structures (spawn.h botinfo)
// Declare in spawn.hstruct playerstats// Initialize in spawn.cpp
{
freqdata freqs[100];
char name[20];
int kills;
int deaths;
Uint16 points;
Uint16 flagpoints;
int flagtime;
int cflagtime;
int flags;
int flagkills;
int dmgdealt;
int 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;
};
void botInfo::Clear()
{
}
// initialize/clear struct data
for (int n=99; n>=0; n--)
{
freqs[n].freqteam=-1;
freqs[n].freqpoints=0;
freqs[n].freqflagpoints=0;
freqs[n].playercount=0;
freqs[n].flags=0;
freqs[n].kills=0;
freqs[n].deaths=0;
freqs[n].freqflagtime=0;
freqs[n].flagkills=0;
freqs[n].dmgdealt=0;
freqs[n].dmgtaken=0;
for (int m = 99; m>=0; m--)
{
freqs[n].pilots[m].deaths=0;
freqs[n].pilots[m].kills=0;
freqs[n].pilots[m].points=0;
freqs[n].pilots[m].flagpoints=0;
freqs[n].pilots[m].flagtime=0;
freqs[n].pilots[m].cflagtime=0;
freqs[n].pilots[m].flags=0;
freqs[n].pilots[m].flagkills=0;
freqs[n].pilots[m].dmgdealt=0;
freqs[n].pilots[m].dmgtaken=0;
}
}
// Access data in spawn.cpp
int freq = p->team;
freqs[1].pilots[2].kills++;
freqs[freq].deaths++;
OR
<a name="15f"></a>
f) Tracking flag data
Example GetPilot() function (using structs from example e)bool botInfo::GetPilot(Player *p)
{
// get a pilots freq/pilot id from struct
for (freq=freqcount-1; freq>=0; freq--)
if (p->team == freqs[freq].freqteam)
for (pilot = freqs[freq].playercount-1; pilot>=0; pilot--)
if (strcmp(p->name,freqs[freq].pilots[pilot].name)==0)
return true;
return false;
}
Example way to track flag data using above struct/functionscase 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
Example way to track flag data using built in get/set tag (from catid flagbot)
case EVENT_FlagGrab:
{
set_tag(p, TAG_STAT_FS, get_tag(p, TAG_STAT_FS) + 1);
set_tag(p, TAG_FLAGTIMER, GetTickCount());
}
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))
if (freqs[freq].pilots[pilot].flags > 0)
{
if (PilotOnSquad(p))
freqs[freq].freqflagtime += (GetTickCount() - freqs[freq].pilots[pilot].cflagtime)/1000;
freqs[freq].pilots[pilot].flagtime += (GetTickCount() - freqs[freq].pilots[pilot].cflagtime)/1000;
freqs[freq].pilots[pilot].cflagtime = GetTickCount();
}
parse = parse->next;
}
}
// side note: case EVENT_FlagDrop: {} gets called anytime theres a teamkill
<a name="15g"></a>
g) Example way to do simple /!spam feature (allowed 1x/60s)
declare and initialize variables in spawn.h
class botInfo
{
bool spamready;
int SPAM_TIME;
public:
botInfo(CALL_HANDLE given)
{
spamready = true;
SPAM_TIME = 60;
spawn.cpp - mark as spamready=true when 60 seconds up
case EVENT_Tick:
{
if (countdown[0] == 1) {
spamready = true; // ready to spam again
}
command.cpp - handle !spam command
case OP_Player:
{ // Player-level commands
else if (c->check("spam"))
{
// zone announcement "Need pilots to duel in ?go arena -pilotname"
if (spamready == true)
{
String s;
s += "*zone Need pilots to duel in ?go ";
s += arena;
s += " - ";
s += p->name;
sendPublic(s);
spamready=false;
countdown[0] = SPAM_TIME * 60; // next spam time limit
}
else if (countdown[0] < 0)
{
sendPrivate(p,"Spam ability disabled.");
}
else
{
String s;
s += SPAM_TIME;
s += " Minute timer between announcements. ";
s += countdown[0] / 60;
s += ":";
if (countdown[0] % 60 < 10)
s += "0";
s += countdown[0] % 60;
s += " minutes left before next spam allowed.";
sendPrivate(p, s);
}
}
<a name="15h"></a>
h) Example of implementing a simple stack to do "next in line for several 'boxes' at once"
//spawn.h declare variables
class botInfo
{
Player *next[99][99];
int MAX_NEXT;
int nextcount[99];
public:
botInfo(CALL_HANDLE given)
{
MAX_NEXT = 8;
// spawn.cpp MoveUp function
void botInfo::MoveUp(int pos, int box)
{
// moves up the next line for that box and decrement box's nextcount
if (nextcount[box] > 0)
nextcount[box]--;
for (pos = pos; pos < MAX_NEXT - 1; pos++)
{
next[box][pos] = next[box][pos + 1];
}
next[box][MAX_NEXT] = 0;
}
<a name="15i"></a>
i) Example of reading any text from a .txt and printing it to pilot line by line
#include <fstream>
using namespace std;
case OP_Player:
{
if (c->check("schedule"))
{
// read in schedule from schedule.txt
ifstream file("schedule.txt");
char line[256];
while (file.getline(line, 256))
{
sendPrivate(p, line);
}
file.close();
}
<a name="15j"></a>
j) Example of printing player stats grid
// spawn.cpp (see "structures within structures" example for variable declarations, varibale freqcount = # of freqs)
void botInfo::DisplayPlayers()
{
// Display Match player/freq stats in this format (not aligned b/c of html but aligned in bot)
// ---------------------------------------------------
// 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
// ---------------------------------------------------
for (freq=freqcount-1; freq>=0; freq--)
{
char str[255];
sendFreqs("---------------------------------------------------");
sprintf(str, "Squad: %-20s K D TK DMG DEALT TAKEN", freqs[freq].freqname);
sendFreqs(str);
sendFreqs("---------------------------------------------------");
for (pilot=freqs[freq].playercount-1; pilot >= 0; pilot--)
{
sprintf(str, "%-20s %8d %2d %2d %9d %5d", freqs[freq].pilots[pilot].name, freqs[freq].pilots[pilot].kills,
freqs[freq].pilots[pilot].deaths, freqs[freq].pilots[pilot].teamkills, freqs[freq].pilots[pilot].dmgdealt,
freqs[freq].pilots[pilot].dmgtaken);
sendFreqs(str);
}
sprintf(str, "TOTAL: %2d %2d %2d %9d %5d", freqs[freq].kills, freqs[freq].deaths,
freqs[freq].teamkills, freqs[freq].dmgdealt, freqs[freq].dmgtaken);
sendFreqs(str);
}
sendFreqs("---------------------------------------------------");
}
<a name="15k"></a> k) Example of checking if any pilots are
within a region
// see GetPilotName(name) function in other example, returns *player as TempPlayer from name
// closeto() function from several previous examples
bool botInfo::FreqAInBox()
{
// return true if teamA has a pilot in the box, otherwise false
for (int tempplayercount = freqs[0].playercount-1; tempplayercount >= 0; tempplayercount--)
if (GetPilotName(freqs[0].pilots[tempplayercount].name))
if (closeto(TempPlayer, coordX, coordY, 73) && (TempPlayer->ship != SHIP_Spectator))
return true;
return false;
}
<a name="15l"></a>
l) Example of functions to get a pilot's struct id info from a name or *player info
// see struct examples for variable info
// return struct freq/pilot id from *player info
bool botInfo::GetPilot(Player *p)
{
// return freq, pilot of a player p
for (freq=freqcount-1; freq>=0; freq--)
if (p->team == freqs[freq].freqteam)
for (pilot = freqs[freq].playercount-1; pilot>=0; pilot--)
if (strcmp(p->name,freqs[freq].pilots[pilot].name)==0)
return true;
return false;
}
// return *player as TempPlayer info from p->name info
bool botInfo::GetPilotName(char *name)
{
// get pilot from a name, return as TempPlayer
_listnode <Player> *parse = playerlist->head;
while (parse)
{
Player *p = parse->item;
// convert both to lowercase to compare
char pname[20];
strncpy(pname,p->name,20);
char nname[20];
strncpy(nname,name,20);
tolower(pname);
tolower(nname);
if (strcmp(pname,nname)==0)
{
TempPlayer = p;
return true;
}
parse = parse->next;
}
return false;
}
// note: better to implement these functions as passing values by reference instead of using global variables
// just easier to not have to be declaring different int freq, int pilot all the time
<a name="15m"></a>
m) Example of creating a logfile name using date and squad names
// create log file name (squadA and squadB external char[20] variables)
char u[100];
time_t t=time(NULL);
tm *tmp = localtime(&t);
strftime(u,99,"%y",tmp);
logname = "c:\Program Files\Continuum\logs\";
logname += u;
logname += "y";
strftime(u,99,"%m",tmp);
logname += u;
logname += "m";
strftime(u,99,"%d",tmp);
logname += u;
logname += "d";
logname += squadA;
logname += " vs ";
logname += squadB;
strftime(u,99,"%I",tmp);
logname += u;
logname += "h";
strftime(u,99,"%M",tmp);
logname += u;
logname += "m";
logname += ".txt";
// example name created: 03y01m27dBLACKDRaGON vs Integral05h08m.txt
// format year, month, day, squadA vs squadB, hour, minute
<a name="15n"></a>
n) Example of sending messages to playing freqs or public and logging depending on status
// teamA, teamB, logname global variables
void botInfo::sendFreqs(char *msg)
{
char *mmsg = "*arena";
String s = msg;
if (teammsgs == false)
{
s.prepend("*arena ",7);
sendPublic(s);
}
else
{
sendTeamPrivate(8025,msg);
sendTeamPrivate(teamA,msg);
sendTeamPrivate(teamB,msg);
}
if (gameon == true)
{
ofstream outf(logname, ios::app);
outf << msg << endl;
outf.close();
}
}
<a name="15o"></a>
o) Example of reading in all player/freqs to struct data
// see structures within structures example for freqs[] declaration
// to get freqs in a game where there are several freqs
void botInfo::GetFreqs()
{
// read pilots into freq struct data from ingame and on playing freqs
_listnode <Player> *parse = playerlist->head;
while (parse)
{
Player *p = parse->item;
if (p->ship != SHIP_Spectator)
if (closeto(p, coordX, coordY, 73))
{
// look for freq in struct
bool foundfreq=false;
freq=freqcount-1;
while ((freq>=0) && (foundfreq==false))
{
if (p->team == freqs[freq].freqteam)
{
foundfreq=true;
strncpy(freqs[freq].pilots[freqs[freq].playercount].name, p->name, 20);
freqs[freq].playercount++;
}
freq--;
}
// didnt find freq in struct so add new freq
if (foundfreq == false)
{
if (manualsquads == false)
{
strncpy(freqs[freqcount].freqname, p->squad, 20);
if (freqcount == 0)
{
teamA = p->team;
strncpy(squadA, p->squad, 20);
}
else
{
teamB = p->team;
strncpy(squadB, p->squad, 20);
}
}
else
{
if (p->team == teamA)
strncpy(freqs[freqcount].freqname,squadA,20);
else if (p->team == teamB)
strncpy(freqs[freqcount].freqname,squadB,20);
}
freqs[freqcount].freqteam = p->team;
strncpy(freqs[freqcount].pilots[0].name, p->name, 20);
freqs[freqcount].playercount++;
freqcount++;
}
}
parse = parse->next;
}
}
// to get freqs in a game where there are only two teams
void botInfo::GetFreqs()
{
// read pilots into freq struct data from ingame and on playing freqs
_listnode <Player> *parse = playerlist->head;
while (parse)
{
Player *p = parse->item;
if ((p->ship != SHIP_Spectator) && ((p->team == teamA) || (p->team == teamB)))
{
// freq 100, team A
// set freq
freq = 0;
if (p->team == teamB)
freq = 1;
// number of pilots on freq counted so far, starts 0
pilot = freqs[freq].playercount;
// pilot name
strncpy(freqs[freq].pilots[pilot].name, p->name, 20);
// time stamp for playing time
freqs[freq].pilots[pilot].cplaying_time = GetTickCount();
// slot name
if (freqs[freq].playercount < NUMBER_PILOTS)
strncpy(freqs[freq].slotname[pilot], p->name, 20);
// increment freq player count
freqs[freq].playercount++;
// if freq not already have name, give it player squad name
if ((manualsquads == false) && (strlen(p->squad) > 0))
strncpy(freqs[freq].freqname, p->squad, 20);
// set player ship
freqs[freq].pilots[pilot].ship = p->ship + 1;
}
parse = parse->next;
}
}
<a name="15p"></a>
p) Example of finding MVP from struct data (2*kills - deaths formula)
int highest=-20;
int mvp=0;
for (pilot = freqs[mvpteam].playercount-1; pilot >=0; pilot--)
{
if (((freqs[mvpteam].pilots[pilot].kills
- 2) - freqs[mvpteam].pilots[pilot].deaths) > highest)
{
mvp = pilot;
highest = (freqs[mvpteam].pilots[pilot].kills
- 2) - freqs[mvpteam].pilots[pilot].deaths;
}
}
<a name="15q"></a>
q) Print time stamp of event
#include "time.h"
char u[100];
time_t t=time(NULL);
tm *tmp = localtime(&t);
strftime(u,99,"%c",tmp);
sendPublic("Current date and time: " + (String) u);
<a name="15r"></a>
r) Simple way to track player bomb/bullet damage stats
// spawn.cpp (see data section for how to setup set_tag)
// see clientprot.h for weapon information
case EVENT_WatchDamage:
{
if (PLAYING) // if tracking stats
{
if ((wi.type == PROJ_PBomb) && (p->name != k->name))
{
set_tag(k, DMG_BOMB_DEALT, get_tag(k, DMG_BOMB_DEALT) + damage);
set_tag(k, DMG_TOTAL_DEALT, get_tag(k, DMG_TOTAL_DEALT) + damage);
set_tag(p, DMG_BOMB_TAKEN, get_tag(p, DMG_BOMB_TAKEN) + damage);
set_tag(p, DMG_TOTAL_TAKEN, get_tag(p, DMG_TOTAL_TAKEN) + damage);
}
else if (wi.type == PROJ_BBullet)
{
set_tag(k, DMG_BULLET_DEALT, get_tag(k, DMG_BULLET_DEALT) + damage);
set_tag(k, DMG_TOTAL_DEALT, get_tag(k, DMG_TOTAL_DEALT) + damage);
set_tag(p, DMG_BULLET_TAKEN, get_tag(k, DMG_BULLET_TAKEN) + damage);
set_tag(p, DMG_TOTAL_TAKEN, get_tag(k, DMG_TOTAL_TAKEN) + damage);
}
}
<a name="15s"></a>
s) Simple way to print those stats
<a name="15t"></a>case OP_Moderator: { if (c->check("showstats")) { 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]; sprintf(str, "(%-20s Dmg Dealt: Total %0004d, Bomb %0004d, Bullet %0004d Dmg TAKEN: Total %0004d, Bomb %0004d, Bullet %0004d)", p->name, get_tag(p,DMG_TOTAL_DEALT), get_tag(p,DMG_BOMB_DEALT), get_tag(p,DMG_BULLET_DEALT), get_tag(p,DMG_TOTAL_TAKEN), get_tag(p,DMG_BOMB_TAKEN), get_tag(p,DMG_BULLET_TAKEN)); sendPublic(str); } parse = parse->next; } }
t) Make bot spectate specific coordinates
// make bot spectate the coord 512,600 // possible use - capturing weapon packets in a specific region tell(makeFollowing(false)); tell(makeFlying(true)); me->move(512 * 16, 600 * 16); tell(makeSendPosition(true));
</blockquote></blockquote>