Difference between revisions of "MERVBot Tutorial"
(→Player Commands - (command.cpp): cleaned c++) |
Doc flabby (talk | contribs) m (fixed tutorial link) |
||
(114 intermediate revisions by 9 users not shown) | |||
Line 1: | Line 1: | ||
− | + | This tutorial is based on the ever-popular MERVBot Tutorial by Underlord. It has since been updated to reflect new changes with MervBot. To see examples of how to use this instruction, see [[MERVBot Example Code]]. | |
− | + | This tutorial also assumes that you have a basic knowledge of C++. If you don't, check out cplusplus.com's great [http://www.cplusplus.com/doc documentation]. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | ==Setting up a MERVBot | + | ==Setting up a MERVBot (plugin)== |
− | + | [http://mervbot.com MERVBot download site] | |
− | |||
− | === | + | ===Obtaining MERVBot=== |
− | + | * Download the [http://mervbot.com/files/MERVBot.rar latest build]. | |
− | Unzip src.zip into " | + | * Unrar MERVBot.rar 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) | |
+ | |||
+ | ===Preparing to write a plugin=== | ||
+ | |||
+ | ''Note:'' if you only want to execute someone's premade plugin (.dll), skip to [[MERVBot Tutorial#Run your bot dll|step 4]], otherwise continue to learn how to make your own bot | ||
− | + | Download [http://www.mervbot.com/files/Tutorial.rar DLL-plugin Tutorial] and unzip Tutorial.zip (containing spawn.h, spawn.cpp, and command.cpp) into a "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 that interacts with bot spawns | |
− | spawn.h = declare/initialize globals | ||
− | command.cpp = code for commands coming into bot (ie /!help, /!play, etc) | ||
− | spawn.cpp = code | ||
− | |||
− | |||
− | |||
===Microsoft Visual c++=== | ===Microsoft Visual c++=== | ||
Line 84: | Line 33: | ||
<li>On the next screen that comes up, choose from the Project tab, then Win32 Dynamic-Link Library | <li>On the next screen that comes up, choose from the Project tab, then Win32 Dynamic-Link Library | ||
<li>Select the "/src" folder as the base folder (example c:\program files\continuum\mervbot\src) | <li>Select the "/src" folder as the base folder (example c:\program files\continuum\mervbot\src) | ||
− | <li>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) | + | <li>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) |
<li>Choose to create an "Empty DLL project". | <li>Choose to create an "Empty DLL project". | ||
<li>Click "Finish". | <li>Click "Finish". | ||
<li>Click the Drop Down Menu labbled "Project". | <li>Click the Drop Down Menu labbled "Project". | ||
− | <li>Click "Add To Project Files" | + | <li>Click "Add To Project Files" |
<li>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) | <li>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) | ||
<li>Click the Drop Down Menu labelled "Build". | <li>Click the Drop Down Menu labelled "Build". | ||
− | <li>Click "Build (dll name)" - where | + | <li>Click "Build (dll name)" - where (dll name) is "mybot" |
<li>Go into your "mybot" folder and look for a folder named "Debug" | <li>Go into your "mybot" folder and look for a folder named "Debug" | ||
(example c:\program files\continuum\mervbot\src\mybot\debug) | (example c:\program files\continuum\mervbot\src\mybot\debug) | ||
Line 97: | Line 46: | ||
<li>Copy mybot.dll to your base folder that has mervbot.exe in it (example c:\program files\continuum\mervbot) | <li>Copy mybot.dll to your base folder that has mervbot.exe in it (example c:\program files\continuum\mervbot) | ||
</ol> | </ol> | ||
+ | |||
===Run your bot dll=== | ===Run your bot dll=== | ||
− | To run your bot you need your DLL (mybot.dll), Commands.txt, MERVBot.exe, MERVBot.ini, Operators.txt, Spawns.txt | + | To run your bot you need your DLL (mybot.dll), Commands.txt, MERVBot.exe, MERVBot.ini, Operators.txt, Spawns.txt, and zlib.dll all in one folder (example c:\program files\continuum\mervbot). |
<ol> | <ol> | ||
− | <li>Edit spawns.txt | + | <li>Edit spawns.txt. '''Read every word of spawns.txt to find out what needs to go in there.''' |
− | + | <br> | |
− | + | ''Example:'' | |
− | ''Example:'' 2v2-Bot-League : botpw: 2v2a: 2v2league : staffpw< | + | <pre>2v2-Bot-League : botpw : 2v2a : 2v2league : staffpw</pre> |
− | + | ||
− | + | ''Note:'' The bot will attempt to create the name if it doesn't exist already. | |
− | |||
− | |||
− | |||
− | |||
<li>Edit MERVBot.ini | <li>Edit MERVBot.ini | ||
− | + | <br> | |
+ | <br> | ||
<pre>[Login] | <pre>[Login] | ||
− | Zone=216.33.98.254:21000 // | + | Zone=216.33.98.254:21000 // your zone IP:PORT available from zone.dat in Continuum dir |
</pre> | </pre> | ||
− | <li>Edit operators.txt | + | <li>Edit operators.txt. '''Read every word of operators.txt to find out what needs to go in there.'''<br /> |
− | |||
− | |||
''Example:'' | ''Example:'' | ||
Line 134: | Line 79: | ||
</pre> | </pre> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | <li> | + | <li>Make sure the bot is on vip.txt or has smod+ access, then run MERVBot.exe. |
− | |||
− | |||
− | |||
− | Edit the spawn.h, spawn.cpp, and command.cpp to create your | + | <li>You can now edit your plugin code by opening "mybot.dsw" (example c:\program files\continuum\mervbot\src\mybot\mybot.dsw) in Microsoft Visual C++. 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. |
− | copy your updated | ||
− | |||
− | |||
</ol> | </ol> | ||
==Player Commands - (command.cpp)== | ==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"): | |
− | |||
<pre> | <pre> | ||
void botInfo::gotCommand(Player *p, Command *c) { | void botInfo::gotCommand(Player *p, Command *c) { | ||
− | switch (p->access) { | + | switch (p->access) |
− | case OP_Player: | + | { |
+ | case OP_Moderator: | ||
+ | { | ||
+ | // handle moderator-operator commands here. | ||
+ | } | ||
+ | case OP_Player: //appropriate staff rank here. | ||
{ | { | ||
− | if (c->check("test")) | + | if (c->check("test")) //replace "test" with whatever command you want |
{ | { | ||
− | sendPrivate(p,"hi"); | + | //put your command code here |
+ | sendPrivate(p,"hi"); //example | ||
} | } | ||
} | } | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
</pre> | </pre> | ||
− | ===How to have commands with numerical | + | ===How to have commands with numerical parameters=== |
+ | Example (!test #): | ||
<pre> | <pre> | ||
if (c->check("test")) { // reads in test #, default to 1 if invalid number input | if (c->check("test")) { // reads in test #, default to 1 if invalid number input | ||
Line 188: | Line 119: | ||
</pre> | </pre> | ||
− | ===How to have player name as input | + | ===How to have player name as input=== |
+ | Example (!rank player): | ||
<pre> | <pre> | ||
− | if (c- | + | if (c->check("rank")) |
+ | { | ||
+ | String player_name = c->final; | ||
− | + | if (player_name.IsEmpty()) // default name to self if invalid name | |
+ | player_name = p->name; | ||
+ | </pre> | ||
− | + | ===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): | |
<pre> | <pre> | ||
else if (c->check("squads")) | else if (c->check("squads")) | ||
{ | { | ||
+ | char squadA[20], squadB[20]; | ||
+ | int teamA, teamB; | ||
+ | |||
strncpy(squadA, "", 20); | strncpy(squadA, "", 20); | ||
strncpy(squadB, "", 20); | strncpy(squadB, "", 20); | ||
− | + | int n_found; | |
− | |||
− | |||
− | int | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | //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); | ||
} | } | ||
</pre> | </pre> | ||
− | |||
− | + | ===Help Menu=== | |
+ | When a player sends !help to the bot, MERVBot calls botInfo::gotHelp() in each plugin loaded. | ||
<pre> | <pre> | ||
void botInfo::gotHelp(Player *p, Command *c) | void botInfo::gotHelp(Player *p, Command *c) | ||
Line 285: | Line 172: | ||
</pre> | </pre> | ||
− | ==Event | + | ==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. When a plugin wants the bot to do something, it sends tell(event) to the bot. | |
− | + | See dllcore.h for a list of current events and their descriptions. Dllcore.h also contains functions (like makeFollowing) to make events to send back to the bot via tell(). | |
− | + | <pre>tell(makeFollowing(false));</pre> | |
− | + | ==The Messaging System== | |
− | + | Private message - void sendPrivate(Player *player, char *msg); | |
− | + | ''Examples:'' | |
− | |||
− | + | <pre> | |
+ | 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."); | ||
+ | </pre> | ||
− | + | Team message - void sendTeamPrivate(Uint16 team, char *msg);<br /> | |
+ | <blockquote>Examples: <br /> | ||
+ | a) sendTeamPrivate(8025,"hi spec freq");<br /> | ||
+ | b) Uint16 test=0; sendTeamPrivate(test,"hi freq 0");<br /> | ||
+ | </blockquote> | ||
− | + | Public message - void sendPublic(char *msg);<br /> | |
− | + | <blockquote>Example: sendPublic("*arena " + (String) p->name + " is now a captain");<br /> | |
+ | </blockquote> | ||
+ | Chat channel message - void sendChannel(char *msg);<br /> | ||
+ | <blockquote>Example: sendChannel("hi chat channel");<br /> | ||
+ | </blockquote> | ||
+ | Remote private message - void sendRemotePrivate(char *name, char *msg);<br /> | ||
+ | <blockquote>Example: sendRemotePrivate("Player01", "hi");<br /> | ||
+ | </blockquote> | ||
− | + | 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 | ||
− | + | ===Output of data in messages=== | |
− | + | <p>An example of using normal strings to output data/messages.</p> | |
+ | <pre> | ||
+ | // does *arena X pilots left in game | ||
+ | // NOTE: variable temp needs to be defined with some value | ||
− | + | String s = "*arena "; | |
+ | s += temp; | ||
+ | s += " pilots left in the game."; | ||
− | + | sendPublic(s); | |
+ | </pre> | ||
+ | Or, | ||
+ | <pre> | ||
+ | //NOTE: this can be considered inefficient. | ||
− | + | sendPublic("*arena " + (String)temp + " pilots left in the game"); | |
− | + | </pre> | |
− | // | + | <p>An example using sprintf to align/space data, where output data will be in this approximate format.</p> |
+ | <pre> | ||
+ | // 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 | ||
+ | </pre> | ||
− | + | ==Time== | |
− | + | 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: | |
+ | <pre> | ||
+ | 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 | ||
+ | </pre> | ||
+ | Using timer functions in spawn.cpp: | ||
+ | <pre> | ||
+ | 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 | ||
+ | } | ||
+ | </pre> | ||
− | + | 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. | |
− | + | === 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, Windows provides a function called GetTickCount() that is a measurement of time (milliseconds) that we can use for such cases. | |
− | + | So: | |
− | + | <pre> | |
+ | int begin = GetTickCount(); | ||
− | // | + | // do some code here. |
− | + | int end = GetTickCount(); | |
− | + | ||
+ | int delta = (end - begin) / 1000; // elapsed time converted to seconds from milliseconds | ||
+ | </pre> | ||
− | + | === Obtaining the current time === | |
− | + | ''Requirements:'' Include <time.h>. | |
− | |||
− | + | Use: | |
+ | <pre> | ||
+ | char u[100]; | ||
+ | time_t t=time(NULL); | ||
+ | tm *tmp = localtime(&t); | ||
+ | strftime(u,99,"%c",tmp); | ||
+ | sendPublic("Current date and time: " + u); | ||
+ | </pre> | ||
− | + | ==Writing Functions== | |
+ | <!-- begin of functions. --> | ||
+ | For this example, we will take the function called closeto, which determines | ||
+ | if a player exists in an specific radius around a point. 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, as below: | ||
+ | <pre> | ||
+ | //////// 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); | ||
+ | } | ||
+ | </pre> | ||
− | |||
− | + | If you want your function to have access to the data from spawn.h botInfo class, you make the function apart of it. To do this, we add the '''botInfo::''' infront of the function name, in spawn.cpp. | |
+ | <pre> | ||
+ | //////// DLL "import" //////// | ||
− | + | bool botInfo::closeto(Player *p, int x, int y, int tolerance) | |
+ | { | ||
+ | ... | ||
+ | } | ||
+ | </pre> | ||
+ | In '''spawn.h''', add your method's prototype without botInfo::, it will look | ||
+ | like this: | ||
+ | <pre> | ||
+ | //... | ||
+ | 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. | ||
+ | // ... | ||
+ | }; | ||
+ | </pre> | ||
+ | If you're not familiar with prototypes, notice it is similar to that in your spawn.cpp, but without the botInfo::, and a trailing ;. | ||
− | + | ===Function notes=== | |
− | + | Remember that you can pass variables [http://www.cplusplus.com/doc/tutorial/tut2-3.html by reference]. If variables are passed by reference, any changes a function makes to the variables will remain after the function returns. | |
− | |||
− | + | From time to time you will need to pass an array to a function. An example illustrating this is: | |
+ | <pre> | ||
+ | int freqs[5]; // declare our example data. | ||
− | + | // call function - notice freqs and not freqs[5] or freqs[]. | |
− | + | my_function(freqs); //You're not passing the array itself, just a pointer to the array. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | // function - notice freqs[] and not freqs[5] or freqs | |
− | + | void my_function(int freqs[]) {} //You're specifying that the freqs parameter is an array | |
− | + | </pre> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | ==Cycling through players== | |
− | |||
− | == | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | MERVBot stores player-related data in a linked list. A linked list is a datatype that stores its data in a series of structures linked to each other, hence the name. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | To search through the players in the arena, just start at the first link, then continue through all the following links until you reach the end: | |
− | + | <pre> | |
− | + | _listnode <Player> *parse = playerlist->head; //set to first link of the player linked list | |
− | |||
− | + | while (parse) //parse will be NULL when we reach the last link | |
− | + | { | |
+ | Player *p = parse->item; //item is the actual data stored in the link | ||
− | + | // 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; //set parse to the next link | |
+ | } | ||
+ | </pre> | ||
− | + | For example, 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: | |
+ | <pre> | ||
+ | void handleCmdSetShip(enum Ship_Types ship); | ||
+ | </pre> | ||
− | + | In spawn.cpp add: | |
− | + | <pre> | |
+ | void botInfo::handleCmdSetShip(enum Ship_Types ship) | ||
+ | { | ||
+ | //Note that the parameter ship is of the Ship_Types enum, | ||
+ | //so its value is hopefully restricted to the proper types. | ||
− | + | _listnode <Player> *parse = playerlist->head; | |
− | + | while (parse) | |
− | + | { | |
+ | Player *p = parse->item; | ||
− | & | + | if ( p->ship != ship && p->ship != SHIP_Spectator ) |
+ | sendPrivate(p, "*setship " + (String)ship); | ||
− | + | parse = parse->next; | |
+ | } | ||
+ | } | ||
+ | </pre> | ||
− | + | To use, just call the function with the appropriate Ship_Type from the enum in clientprot.h: | |
− | + | <pre> | |
− | + | handleCmdSetShip(SHIP_Warbird); | |
− | + | </pre> | |
− | |||
− | |||
− | |||
− | |||
− | + | ==Random numbers== | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | == | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | == | + | ===Generating a random number=== |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | To use this method, these two includes must be used: | |
− | + | <pre> | |
− | + | #include "time.h" //provides time() function. | |
− | + | #include "stdlib.h" //provides srand() and rand() functions. | |
− | + | </pre> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | Use: | |
− | < | + | <pre> |
− | + | 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 | ||
+ | </pre> | ||
− | + | ===Picking a random pilot=== | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | ''Note:'' A required user-defined function, getInGame(), must be created for this example to work. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | <pre> | |
− | + | 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) ) // and if we've hit the randomly-selected pilot | ||
+ | { | ||
+ | rabbit = p; | ||
+ | break; | ||
+ | } | ||
+ | parse = parse->next; | ||
+ | } | ||
+ | </pre> | ||
− | + | ==Storing data for pilots== | |
− | + | There are several ways to store data for pilots (ie tracking flagtime or kills in a period of time). Note that these methods are all purely internal to the bot, and don't effect anything beyond the plugin in any way. | |
− | + | # Built-in get/setTag: Tracks data until player leaves the arena, then automatically deletes data. | |
+ | # Modified perm get/setTag: Tracks data until bot leaves arena, then automatically deletes data. (Advantage: easier to sort by player) | ||
+ | # Custom Structs: Tracks data until plugin deletes it. (Advantage: easier to sort by freqs) | ||
− | + | ''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. | |
− | + | ===Built-in get/setTag method=== | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | Player tags simply tag a player with a number. Define the wanted values in '''spawn.h''' at the top: | |
+ | <pre> | ||
+ | #define DMG_DEALT 0 | ||
+ | #define DMG_TAKEN 1 | ||
+ | </pre> | ||
− | + | In spawn.cpp, initialize the values on ArenaEnter and PlayerEnter: | |
− | + | <pre> | |
− | + | case EVENT_ArenaEnter: | |
+ | { | ||
+ | // ... | ||
− | + | // do for all pilots in arena when bot enters | |
+ | _listnode <Player> *parse = playerlist->head; | ||
+ | while (parse) | ||
+ | { | ||
+ | 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 | |
− | // | + | } |
− | + | } | |
− | + | </pre> | |
− | + | <pre> | |
− | + | case EVENT_PlayerEntering: | |
− | + | { | |
− | + | // ... | |
− | + | set_tag(p, DMG_DEALT, 0); // initialize to 0 | |
− | + | set_tag(p, DMG_TAKEN, 0); | |
+ | sendPrivate(p,"*watchdamage"); | ||
+ | } | ||
+ | </pre> | ||
− | + | Then somewhere edit the tag values: | |
− | + | <pre> | |
− | + | case EVENT_WatchDamage: | |
− | + | { | |
− | + | // sets tag for k (shooter) to be old value plus damage currently dealt | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | int old_damage = get_tag(k, DMG_BOMB_DEALT); | |
− | + | set_tag(k, DMG_BOMB_DEALT, old_damage + damage); | |
− | + | } | |
− | + | </pre> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | } | ||
− | |||
− | |||
− | + | The following demonstrates how to retrieve the tag values as a command in command.cpp: | |
− | + | <pre> | |
− | + | 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); | |
− | + | } | |
− | + | </pre> | |
− | + | ===Modified permanent get/setTag method=== | |
− | + | This method is the same as get/setTag with some modifications to the tag code to retain them after the player leaves. Beware of using this method if bot is in an arena for long periods of time, linkedlist could get huge. | |
− | |||
− | |||
− | + | // spawn.h, add char name[20]; into struct PlayerTag | |
− | + | <pre> | |
− | + | struct PlayerTag | |
− | + | { | |
+ | Player *p; | ||
+ | char name[20]; | ||
+ | int index; | ||
+ | int data; | ||
+ | }; | ||
+ | </pre> | ||
− | + | In spawn.cpp: | |
− | + | <pre> | |
− | + | case EVENT_PlayerLeaving: | |
− | + | { | |
− | + | Player *p = (Player*)event.p[0]; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | // killTags(p); // remove so tag not deleted on arena exit | |
− | |||
− | |||
− | + | // ... | |
− | + | </pre> | |
+ | Locate in spawn.cpp and modify accordingly: | ||
+ | <pre> | ||
+ | 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 pointer | |
− | + | 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 pointer | |
+ | if (tag->index == index) | ||
+ | { | ||
+ | tag->data = data; | ||
+ | return; | ||
+ | } | ||
+ | parse = parse->next; | ||
+ | } | ||
− | + | tag = new PlayerTag; | |
− | + | // tag->p = p; // not tracking by pointer anymore | |
− | + | strncpy(tag->name, p->name, 20); // tracking by player name | |
+ | tag->index = index; | ||
+ | tag->data = data; | ||
+ | taglist.append(tag); | ||
+ | } | ||
+ | </pre> | ||
− | + | ===Using structs=== | |
− | |||
− | + | In '''spawn.h''': | |
− | + | <pre> | |
− | + | class botInfo | |
+ | { | ||
+ | struct freqdata | ||
+ | { | ||
+ | int kills, deaths; | ||
+ | }; | ||
+ | // ... | ||
+ | }; | ||
+ | </pre> | ||
− | + | To make use of this structure, implement accordingly: | |
− | + | <pre> | |
+ | freqdata freqs[100]; // 100 of those structs | ||
+ | </pre> | ||
+ | Access the data in spawn.cpp using | ||
+ | <pre> | ||
+ | freqs[56].kills = 1; | ||
+ | </pre> | ||
+ | See CPlusPlus.com's [http://www.cplusplus.com/doc/tutorial/tut3-5.html Structures] tutorial for a more comprehensive guide. Note that, as shown on the bottom of the page, you can have structures within structures. Thus, for example, you could have a structure for each freq with a structure for each player nested within them. | ||
− | + | ==Input/Output to files== | |
+ | For reading and/or writing to files with C++ you must have the required include statement as follows: | ||
+ | <pre> | ||
+ | #include <fstream> | ||
+ | using namespace std; | ||
+ | </pre> | ||
− | + | ===File stream input=== | |
+ | The following example will show you how to read a file, duel.ini, line by line. | ||
+ | <pre>#include "stdlib.h" // for atoi()</pre> | ||
− | + | <pre> | |
+ | ifstream file("duel.ini"); | ||
+ | if (!file.good()) // if there was an error opening the file | ||
+ | sendPublic("*arena Error opening file for reading"); // or add your own error handler | ||
+ | else | ||
+ | { | ||
+ | char line[256]; | ||
− | + | // read in MaxBoxes=X | |
− | + | while (file.getline(line, 256)) | |
− | + | { | |
− | + | ||
− | + | if (CMPSTART("MaxBoxes=", line)) //Does the line begin with MaxBoxes= ? | |
− | + | { | |
− | + | MAX_BOXES = atoi(&(line[9])); //If so, read the value into an integer, using atoi. | |
− | + | break; | |
− | + | } | |
− | + | } | |
− | |||
− | |||
− | |||
− | + | file.close(); | |
− | + | } | |
+ | </pre> | ||
− | + | ===File stream output=== | |
− | == | + | The following code example will demonstrate how to append to a file, duelleaguestat.inc. |
− | + | <pre> | |
− | + | ofstream file("duelleaguestat.inc", ios::app); // app = put all data at end of file | |
− | |||
− | |||
− | |||
− | |||
− | + | if (!file.good()) // if there was an error opening the file | |
− | + | sendPublic("*arena Error opening file."); | |
− | + | else | |
− | + | { | |
− | + | file << squad1<< endl; // squad1 = char[20] | |
− | + | file << " vs "<< endl; | |
− | + | file << squad2<< endl; // squad2 = char[20] | |
+ | file.close(); | ||
+ | } | ||
+ | </pre> | ||
− | + | Similarly, you are able to write an output of a String to a file: | |
− | + | <pre> | |
− | + | // key is converting String to (char*) to file write | |
+ | String str = freqs[freq].slotname[slot]; | ||
+ | str += ", Repels: " + (String)(int) t->repel; | ||
+ | file << endl; | ||
+ | file << (char*) str; | ||
+ | </pre> | ||
− | + | ===Input with GetPrivateProfileString=== | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | == | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | = | + | GetPrivateProfileString(), a function provided by Windows for reading INI files, will automatically find an INI key (like "MaxBoxes=") in a file for you. See the [http://msdn.microsoft.com/library MSDN Library] for help on this function. This next example will show how to read input using GetPrivateProfileString() based on the rampage plugin. |
− | + | The file format for rampage.ini is like this: | |
− | + | <pre> | |
− | + | 7=is on a killing spree! (6:0) | |
− | + | 10=is opening a can of booya! (9:0) | |
− | + | </pre> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | In '''rampageini.cpp''': | |
− | + | <pre> | |
− | + | #include "rampageini.h" | |
+ | #define WIN32_LEAN_AND_MEAN | ||
+ | #include <windows.h> | ||
− | + | #define NUM_RANKS 10 | |
− | + | #define BUFFER_LEN 256 | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | struct RampageSettings | |
− | + | { | |
− | + | char quotes[NUM_RANKS][BUFFER_LEN]; | |
− | + | }; | |
− | + | ||
+ | void LoadSettings(RampageSettings &setts); | ||
− | + | static char path[BUFFER_LEN]; | |
− | + | char *rank_type[NUM_RANKS] = { "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); | |
− | + | } | |
− | + | } | |
− | + | </pre> | |
− | |||
− | |||
− | + | ==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 = whether 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 | ||
− | + | Just access the respective member of the Player class to check the player's property. | |
+ | For example, in spawn.cpp, to check whether a player is in a safety zone: | ||
+ | <pre> | ||
+ | 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. | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
− | + | === Obtaining a player pointer using name comparison === | |
+ | Since Player pointers are internal to MERVBot, it is necessary to find a way of obtaining a Player pointer from the identifying information given by the game. One of the simpler ways is just to compare the names after converting to lowercase. | ||
− | + | ''Note'': Using pilot names as vital comparisions should be used with caution. See [http://cypherjf.sscentral.com/articles/bots-as-clients/ Bot-Issues] by CypherJF. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | <pre> | |
− | + | // return Player* info (or NULL if not found) from p->name info | |
− | + | Player * botInfo::GetPilot(char *name) | |
− | + | { | |
− | + | // get pilot from a name, return as TempPlayer | |
− | + | _listnode <Player> *parse = playerlist->head; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | { | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | //convert search name to lowercase | |
+ | char nname[20], pname[20]; | ||
+ | strncpy(nname, name, 20); | ||
+ | tolower(nname); | ||
− | + | while (parse) | |
+ | { | ||
+ | Player *p = parse->item; | ||
− | + | // convert to lowercase to compare | |
+ | strncpy(pname,p->name,20); | ||
+ | tolower(pname); | ||
+ | if (strcmp(pname,nname)==0) | ||
+ | return p; | ||
+ | |||
+ | parse = parse->next; | ||
+ | } | ||
− | + | return NULL; //player not found | |
+ | } | ||
+ | </pre> | ||
− | + | ==Bot built in functions== | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
+ | Here are some useful MervBot commands to control what the bot is doing. | ||
− | + | Player.cpp: | |
− | + | * Player::move(Sint32 x, Sint32 y) moves a player to the coordinates specified by x and y | |
− | + | * Player::clone(Player *p) clones a player into a player class | |
− | + | Look in Commands.txt , command.cpp (core), or /!help to bot to see all bot external commands (example /!go <arena>). | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | ''LVZ Object toggling commands in plugins are to go here.'' | |
− | |||
− | |||
− | |||
− | + | [[Category:Guides]] | |
− | |||
− | |||
− | |||
− | |||
− | |||
− |
Latest revision as of 04:38, 3 August 2009
This tutorial is based on the ever-popular MERVBot Tutorial by Underlord. It has since been updated to reflect new changes with MervBot. To see examples of how to use this instruction, see MERVBot Example Code.
This tutorial also assumes that you have a basic knowledge of C++. If you don't, check out cplusplus.com's great documentation.
Contents
Setting up a MERVBot (plugin)
Obtaining MERVBot
- Download the latest build.
- Unrar MERVBot.rar 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)
Preparing to write a plugin
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
Download DLL-plugin Tutorial and unzip Tutorial.zip (containing spawn.h, spawn.cpp, and command.cpp) into a "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 that interacts with bot spawns
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, and zlib.dll all in one folder (example c:\program files\continuum\mervbot).
- Edit spawns.txt. Read every word of spawns.txt to find out what needs to go in there.
Example:2v2-Bot-League : botpw : 2v2a : 2v2league : staffpw
Note: The bot will attempt to create the name if it doesn't exist already.
- Edit MERVBot.ini
[Login] Zone=216.33.98.254:21000 // your zone IP:PORT available from zone.dat in Continuum dir
- Edit operators.txt. Read every word of operators.txt to find out what needs to go in there.
Example:4:my_name: 4:another_sysop: 3:other_person:
- Make sure the bot is on vip.txt or has smod+ access, then run MERVBot.exe.
- You can now edit your plugin code by opening "mybot.dsw" (example c:\program files\continuum\mervbot\src\mybot\mybot.dsw) in Microsoft Visual C++. 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. When a plugin wants the bot to do something, it sends tell(event) to the bot.
See dllcore.h for a list of current events and their descriptions. Dllcore.h also contains functions (like makeFollowing) to make events to send back to the bot via tell().
tell(makeFollowing(false));
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
Output of data in messages
An example of using normal strings to output data/messages.
// does *arena X pilots left in game // NOTE: variable temp needs to be defined with some value String s = "*arena "; s += temp; s += " pilots left in the game."; sendPublic(s);
Or,
//NOTE: this can be considered inefficient. sendPublic("*arena " + (String)temp + " pilots left in the game");
An example using sprintf to align/space data, where output data will be in this approximate format.
// 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
Time
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.
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, Windows 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) / 1000; // elapsed time converted to seconds from milliseconds
Obtaining the current time
Requirements: Include <time.h>.
Use:
char u[100]; time_t t=time(NULL); tm *tmp = localtime(&t); strftime(u,99,"%c",tmp); sendPublic("Current date and time: " + u);
Writing Functions
For this example, we will take the function called closeto, which determines if a player exists in an specific radius around a point. 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, 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. To do this, we add the botInfo:: infront of the function name, in spawn.cpp.
//////// DLL "import" //////// 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. // ... };
If you're not familiar with prototypes, notice it is similar to that in your spawn.cpp, but without the botInfo::, and a trailing ;.
Function notes
Remember that you can pass variables by reference. If variables are passed by reference, any changes a function makes to the variables will remain after the function returns.
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. // call function - notice freqs and not freqs[5] or freqs[]. my_function(freqs); //You're not passing the array itself, just a pointer to the array. // function - notice freqs[] and not freqs[5] or freqs void my_function(int freqs[]) {} //You're specifying that the freqs parameter is an array
Cycling through players
MERVBot stores player-related data in a linked list. A linked list is a datatype that stores its data in a series of structures linked to each other, hence the name.
To search through the players in the arena, just start at the first link, then continue through all the following links until you reach the end:
_listnode <Player> *parse = playerlist->head; //set to first link of the player linked list while (parse) //parse will be NULL when we reach the last link { Player *p = parse->item; //item is the actual data stored in the link // 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; //set parse to the next link }
For example, 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(enum Ship_Types ship);
In spawn.cpp add:
void botInfo::handleCmdSetShip(enum Ship_Types ship) { //Note that the parameter ship is of the Ship_Types enum, //so its value is hopefully restricted to the proper types. _listnode <Player> *parse = playerlist->head; while (parse) { Player *p = parse->item; if ( p->ship != ship && p->ship != SHIP_Spectator ) sendPrivate(p, "*setship " + (String)ship); parse = parse->next; } }
To use, just call the function with the appropriate Ship_Type from the enum in clientprot.h:
handleCmdSetShip(SHIP_Warbird);
Random numbers
Generating a random number
To use this method, these two includes must be used:
#include "time.h" //provides time() function. #include "stdlib.h" //provides srand() and rand() functions.
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
Picking a random pilot
Note: A required user-defined function, getInGame(), must be created 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) ) // and if we've hit the randomly-selected pilot { rabbit = p; break; } parse = parse->next; }
Storing data for pilots
There are several ways to store data for pilots (ie tracking flagtime or kills in a period of time). Note that these methods are all purely internal to the bot, and don't effect anything beyond the plugin in any way.
- Built-in get/setTag: Tracks data until player leaves the arena, then automatically deletes data.
- Modified perm get/setTag: Tracks data until bot leaves arena, then automatically deletes data. (Advantage: easier to sort by player)
- Custom Structs: Tracks data until plugin deletes it. (Advantage: easier to sort by freqs)
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.
Built-in get/setTag method
Player tags simply tag a player with a number. Define the wanted values in spawn.h at the top:
#define DMG_DEALT 0 #define DMG_TAKEN 1
In spawn.cpp, initialize the values on ArenaEnter and PlayerEnter:
case EVENT_ArenaEnter: { // ... // do for all pilots in arena when bot enters _listnode <Player> *parse = playerlist->head; while (parse) { 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 currently dealt int old_damage = get_tag(k, DMG_BOMB_DEALT); set_tag(k, DMG_BOMB_DEALT, old_damage + damage); }
The following demonstrates how to retrieve the tag values as a command in command.cpp:
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); }
Modified permanent get/setTag method
This method is the same as get/setTag with some modifications to the tag code to retain them after the player leaves. Beware of using this method if bot is in an arena for long periods of time, linkedlist could get huge.
// spawn.h, add char name[20]; into struct PlayerTag
struct PlayerTag { Player *p; char name[20]; int index; int data; };
In spawn.cpp:
case EVENT_PlayerLeaving: { Player *p = (Player*)event.p[0]; // killTags(p); // remove so tag not deleted on arena exit // ...
Locate in spawn.cpp and modify accordingly:
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 pointer 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 pointer if (tag->index == index) { tag->data = data; return; } parse = parse->next; } tag = new PlayerTag; // tag->p = p; // not tracking by pointer anymore strncpy(tag->name, p->name, 20); // tracking by player name tag->index = index; tag->data = data; taglist.append(tag); }
Using structs
In spawn.h:
class botInfo { struct freqdata { int kills, deaths; }; // ... };
To make use of this structure, implement accordingly:
freqdata freqs[100]; // 100 of those structs
Access the data in spawn.cpp using
freqs[56].kills = 1;
See CPlusPlus.com's Structures tutorial for a more comprehensive guide. Note that, as shown on the bottom of the page, you can have structures within structures. Thus, for example, you could have a structure for each freq with a structure for each player nested within them.
Input/Output to files
For reading and/or writing to files with C++ you must have the required include statement as follows:
#include <fstream> using namespace std;
File stream input
The following example will show you how to read a file, duel.ini, line by line.
#include "stdlib.h" // for atoi()
ifstream file("duel.ini"); if (!file.good()) // if there was an error opening the file sendPublic("*arena Error opening file for reading"); // or add your own error handler else { char line[256]; // read in MaxBoxes=X while (file.getline(line, 256)) { if (CMPSTART("MaxBoxes=", line)) //Does the line begin with MaxBoxes= ? { MAX_BOXES = atoi(&(line[9])); //If so, read the value into an integer, using atoi. break; } } file.close(); }
File stream output
The following code example will demonstrate how to append to a file, duelleaguestat.inc.
ofstream file("duelleaguestat.inc", ios::app); // app = put all data at end of file if (!file.good()) // if there was an error opening the file sendPublic("*arena Error opening file."); else { file << squad1<< endl; // squad1 = char[20] file << " vs "<< endl; file << squad2<< endl; // squad2 = char[20] file.close(); }
Similarly, you are able to write an output of a String to a file:
// key is converting String to (char*) to file write String str = freqs[freq].slotname[slot]; str += ", Repels: " + (String)(int) t->repel; file << endl; file << (char*) str;
Input with GetPrivateProfileString
GetPrivateProfileString(), a function provided by Windows for reading INI files, will automatically find an INI key (like "MaxBoxes=") in a file for you. See the MSDN Library for help on this function. This next example will show how to read input using GetPrivateProfileString() based on the rampage plugin.
The file format for rampage.ini is like this:
7=is on a killing spree! (6:0) 10=is opening a can of booya! (9:0)
In rampageini.cpp:
#include "rampageini.h" #define WIN32_LEAN_AND_MEAN #include <windows.h> #define NUM_RANKS 10 #define BUFFER_LEN 256 struct RampageSettings { char quotes[NUM_RANKS][BUFFER_LEN]; }; void LoadSettings(RampageSettings &setts); static char path[BUFFER_LEN]; char *rank_type[NUM_RANKS] = { "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); } }
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 = whether 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
Just access the respective member of the Player class to check the player's property.
For example, in spawn.cpp, to check whether a player is in a safety zone:
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. } }
Obtaining a player pointer using name comparison
Since Player pointers are internal to MERVBot, it is necessary to find a way of obtaining a Player pointer from the identifying information given by the game. One of the simpler ways is just to compare the names after converting to lowercase.
Note: Using pilot names as vital comparisions should be used with caution. See Bot-Issues by CypherJF.
// return Player* info (or NULL if not found) from p->name info Player * botInfo::GetPilot(char *name) { // get pilot from a name, return as TempPlayer _listnode <Player> *parse = playerlist->head; //convert search name to lowercase char nname[20], pname[20]; strncpy(nname, name, 20); tolower(nname); while (parse) { Player *p = parse->item; // convert to lowercase to compare strncpy(pname,p->name,20); tolower(pname); if (strcmp(pname,nname)==0) return p; parse = parse->next; } return NULL; //player not found }
Bot built in functions
Here are some useful MervBot commands to control what the bot is doing.
Player.cpp:
- Player::move(Sint32 x, Sint32 y) moves a player to the coordinates specified by x and y
- Player::clone(Player *p) clones a player into a player class
Look in Commands.txt , command.cpp (core), or /!help to bot to see all bot external commands (example /!go <arena>).
LVZ Object toggling commands in plugins are to go here.