|
|
(115 intermediate revisions by 9 users not shown) |
Line 1: |
Line 1: |
− | Here is the ever-popular MERVBot Tutorial by Underlord:
| + | 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]]. |
| | | |
− | <div id="merv_toc" style="font-size: 12px; margin: 2em;">
| + | 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]. |
− | Setup a MERVBot bot and project in visual c++<br />
| |
− | 0) <a href="#0">Setting up a MERVBot bot</a><br />
| |
− | <br />
| |
− | Command.cpp<br />
| |
− | 1) <a href="#1">Player commands</a> (!play, !squadA vs squadB)<br />
| |
− | <br />
| |
− | Spawn.cpp<br />
| |
− | 2) <a href="#2">Event descriptions</a> (describe events in spawn.cpp)<br />
| |
− | 2) <a href="#2">Messaging </a>("*arena hi", ":player:*scorereset)<br />
| |
− | 3) <a href="#3">MervBot Timer</a> (do this in 10 seconds)(countdown[n])<br />
| |
− | 4) <a href="#4">Writing Functions</a> (bool IsInCenter(Player *p))<br />
| |
− | <br />
| |
− | Useful operations<br />
| |
− | 6) <a href="#6">Cycling players</a><br />
| |
− | 7) <a href="#7">Check if pilot is in safe zone</a><br />
| |
− | 8) <a href="#8">Random numbers</a><br />
| |
− | 9) <a href="#9">Time without using countdown[n]</a><br />
| |
− | 10) <a href="#10">Storing data for pilots</a><br />
| |
− | 11) <a href="#11">Output data in messages</a><br />
| |
− | 12) <a href="#12">Input/Output to files</a><br />
| |
− | 13) <a href="#13">Programming commands</a><br />
| |
− | 14) <a href="#14">Useful player data</a><br />
| |
− | 15) <a href="#15">Bot built in functions</a><br />
| |
− | <br />
| |
− | Example Code<br />
| |
− | <div style="margin-left: 2em">
| |
− | a) <a href="#15a">No antiwarp in center of map</a><br />
| |
− | b) <a href="#15b">Setting freq size depending on how many pilots in game</a><br />
| |
− | c) <a href="#15c">Tracking kills and announcing when pilot gets 10 kills in a row without dying </a><br />
| |
− | d) <a href="#15d">Warp pilot to coord when they are in a certain region</a><br />
| |
− | e) <a href="#15e">Structures within structures</a><br />
| |
− | f) <a href="#15f">Tracking flag data</a><br />
| |
− | g) <a href="#15g">Way to do simple /!spam feature</a><br />
| |
− | h) <a href="#15h">Implementing a simple stack to do "next in line for several 'boxes' at once"</a><br />
| |
− | i) <a href="#15i">Reading any text from a .txt and printing it to pilot line by line</a><br />
| |
− | j) <a href="#15j">Example of printing player stats grid</a><br />
| |
− | k) <a href="#15k">Example of checking if any pilots are within a region</a><br />
| |
− | l) <a href="#15l">Example of functions to get a pilot's struct id info from a name or *player info</a><br />
| |
− | m) <a href="#15m">Example of creating a logfile name using date and squad names</a><br />
| |
− | n) <a href="#15n">Sending messages to playing freqs or public and logging depending on status</a><br />
| |
− | o) <a href="#15o">Example of reading in all player/freqs to struct data</a><br />
| |
− | p) <a href="#15p">Example of finding MVP from struct data</a><br />
| |
− | q) <a href="#15q">Print time stamp of event</a><br />
| |
− | r) <a href="#15r">Simple way to track player bomb/bullet damage stats</a><br />
| |
− | s) <a href="#15s">Simple way to print those stats</a><br />
| |
− | t) <a href="#15t">Make bot spectate specific coordinates</a>
| |
− | </div>
| |
− | </div>
| |
| | | |
− | ==Setting up a MERVBot bot (plugin)== | + | ==Setting up a MERVBot (plugin)== |
| | | |
− | MERVBot download site: http://catid.sscentral.com/
| + | [http://mervbot.com MERVBot download site] |
| | | |
− | MERVBot Forum: http://www.ssforum.net/ (Development - MervBot forum)
| |
| | | |
− | ===Download [http://catid.sscentral.com/files/MERVBot.zip MERVBot Build 37 (DLL 6.5)]=== | + | ===Obtaining MERVBot=== |
| | | |
− | <blockquote>Unzip MERVBot.zip into a new folder. (example c:\program files\continuum\mervbot)<br />
| + | * Download the [http://mervbot.com/files/MERVBot.rar latest build]. |
− | Unzip src.zip into "/src" subfolder of that new folder (example c:\program files\continuum\mervbotsrc)<br /> | + | * Unrar MERVBot.rar into a new folder. (example c:\program files\continuum\mervbot) |
− | </blockquote>
| + | * 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://catid.sscentral.com/files/Tutorial.zip DLL-plugin Tutorial]===
| + | 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). |
| | | |
− | <blockquote>
| + | ''File descriptions:'' |
− | Unzip Tutorial.zip into "/tutorial" subfolder of that new folder. (example c:\program files\continuum\mervbot\src\tutorial)<br />
| + | * spawn.h = declare/initialize globals |
− | (note spawn.h, spawn.cpp, and command.cpp are here)<br />
| + | * command.cpp = code for commands coming into bot (ie /!help, /!play, etc) |
− | <blockquote>File descriptions:<br />
| + | * spawn.cpp = code that interacts with bot spawns |
− | spawn.h = declare/initialize globals<br /> | |
− | command.cpp = code for commands coming into bot (ie /!help, /!play, etc)<br /> | |
− | spawn.cpp = code for main part of bot<br /> | |
− | </blockquote>
| |
− | Note: if you only want to execute someone's premade bot (.dll), skip to step 4 (Run your bot .dll), otherwise continue to learn how to make your own bot
| |
− | </blockquote>
| |
| | | |
| ===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)<br /> | + | <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"<br /> | + | <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 the (dll name) is "mybot" | + | <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, subspace.bin, and zlib.dll all in one folder (example c:\program files\continuum\mervbot). | + | 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 (only one line of text in file needed) | + | <li>Edit spawns.txt. '''Read every word of spawns.txt to find out what needs to go in there.''' |
| + | |
| + | <br> |
| + | ''Example:'' |
| + | <pre>2v2-Bot-League : botpw : 2v2a : 2v2league : staffpw</pre> |
| | | |
− | <blockquote>bot_name : pw_for_bot_name : arena: dll_plugin : optional_staff_password<br />
| + | ''Note:'' The bot will attempt to create the name if it doesn't exist already. |
− | <blockquote>
| |
− | ''Example:'' 2v2-Bot-League : botpw: 2v2a: 2v2league : staffpw<br /> | |
− | <br />
| |
− | - You should create your bot name and pw using your Continuum client as you would create any new name. <br />
| |
− | - There is no * in front of staffpw. <br />
| |
− | - Bot needs to be on vip.txt or have moderator+ access to enter a zone.<br />
| |
| | | |
− | </blockquote>
| |
− | </blockquote>
| |
| | | |
| <li>Edit MERVBot.ini | | <li>Edit MERVBot.ini |
− | | + | <br> |
| + | <br> |
| <pre>[Login] | | <pre>[Login] |
− | Zone=216.33.98.254:21000 // make that your zone IP:PORT available from zone.dat in Continuum dir | + | 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 /> |
− | <blockquote>
| |
− | access_level : name :<br />
| |
| | | |
| ''Example:'' | | ''Example:'' |
Line 134: |
Line 79: |
| </pre> | | </pre> |
| | | |
− | </blockquote>
| |
− |
| |
− | <li>Run MERVBot.exe
| |
− | <blockquote>
| |
− | Double click MERVBot.exe, the bot should now enter the zone<br />
| |
− | </blockquote>
| |
| | | |
− | <li>Modify your MERVBot code<br /> | + | <li>Make sure the bot is on vip.txt or has smod+ access, then run MERVBot.exe. |
− | <blockquote>
| |
− | 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 bot, then build, | + | <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 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.
| |
− | </blockquote>
| |
| </ol> | | </ol> |
| | | |
| ==Player Commands - (command.cpp)== | | ==Player Commands - (command.cpp)== |
| | | |
− | <div align="left">This section describes how to implement player commands into MERVBot <br />
| + | This section describes how to implement player commands into your plugin. Commands are sent to the botInfo::gotCommand function in command.cpp. |
− | (example: if you want /!test to make the bot respond with "hi")<br /> | + | |
− | <br />
| + | Example (makes bot reply to !test with "hi"): |
− | In command.cpp<br />
| |
| <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>
| |
− | You just put your player command where 'test' is, then what you want that command to do.<br />
| |
− | If you want the command to be for moderators you put it in the OP_Moderator section instead:
| |
− | <pre>
| |
− | case OP_Moderator:
| |
− | { // Moderator-level commands
| |
− | }
| |
− | // Note: Moderators can do Player commands, SuperModerator can do Moderator and Player commands, etc.
| |
| </pre> | | </pre> |
| | | |
− | ===How to have commands with numerical input - (ie !test 1)=== | + | ===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 - (ie !rank player)=== | + | ===How to have player name as input=== |
| + | Example (!rank player): |
| <pre> | | <pre> |
− | if (c->check("rank")) { | + | if (c->check("rank")) |
| + | { |
| + | String player_name = c->final; |
| | | |
− | String player_name = c->final; | + | if (player_name.IsEmpty()) // default name to self if invalid name |
| + | player_name = p->name; |
| + | </pre> |
| | | |
− | if (player_name.IsEmpty()) // default name to self if invalid name
| + | ===How to have multi-parameter input=== |
− | player_name = p->name;
| |
− | </pre>
| |
| | | |
− | ===How to have complicated input - (ie !squads squadA vs squadB _OR_ !squads teamA:squadA:teamB:squadB)===
| + | Use the CRT function sscanf() to scan the string for the values. |
| | | |
− | You parse the c->final message looking for identifiers, then divide the string to get the values<br />
| + | Example (!squads squadA vs squadB ''or'' !squads teamA:squadA:teamB:squadB): |
| <pre> | | <pre> |
− | else if (c->check("squads"))<br />
| + | else if (c->check("squads")) |
− | {<br />
| + | { |
− | strncpy(squadA, "", 20);<br />
| + | char squadA[20], squadB[20]; |
− | strncpy(squadB, "", 20);<br />
| + | int teamA, teamB; |
− | <br />
| |
− | char s[256];<br />
| |
− | strncpy(s,c->final,255);<br />
| |
− | <br />
| |
− | int l= strlen(s) - 1;<br />
| |
− | <br />
| |
− | for (int i=0; i<l; i++)<br />
| |
− | {<br />
| |
− | // team1 vs team2<br />
| |
− |
| |
− | if ((s[i]==' ') && (s[i+1]=='v') && (s[i+2]=='s') &&
| |
− | (s[i+3]==' '))<br />
| |
− | {<br />
| |
− | s[i]=0;<br />
| |
− | strncpy(squadA, s,20);<br />
| |
− | s[i]=' ';<br />
| |
− |
| |
− | strncpy(squadB, &(s[i+4]),20);<br />
| |
− | break;<br />
| |
− | }<br />
| |
− | <br />
| |
− | // freq1:team1:freq2:team2<br />
| |
− | if (s[i] == ':')<br />
| |
− | {<br />
| |
− | char s2[256];<br />
| |
− |
| |
− | teamA = atoi(s);<br />
| |
− |
| |
− | strncpy(s2,&(s[i+1]),255);<br />
| |
− | <br />
| |
− |
| |
− | for (int j=0; j < strlen(s2); j++)<br />
| |
− |
| |
− | if (s2[j] == ':')<br />
| |
− |
| |
− | {<br />
| |
− |
| |
− | char s3[256];<br />
| |
− | <br />
| |
− |
| |
− | s2[j]=0;<br />
| |
− |
| |
− | strncpy(squadA,s2,20);<br />
| |
− |
| |
− | s2[j]=':';<br />
| |
− |
| |
− | <br /> | |
− |
| |
− | strncpy(s3,&(s2[j+1]),255);<br />
| |
− | <br />
| |
− |
| |
− | for (int k=0;
| |
− | k<strlen(s3); k++)<br />
| |
− |
| |
− |
| |
− | if (s3[k]==':')<br />
| |
− |
| |
− |
| |
− | {
| |
− | <br />
| |
− |
| |
− |
| |
− | teamB = atoi(s3);<br />
| |
− |
| |
− |
| |
− | strncpy(squadB,&(s3[k+1]),20);<br />
| |
− |
| |
− |
| |
− | break;<br />
| |
− |
| |
− |
| |
− | }<br />
| |
− |
| |
− | break;<br />
| |
− |
| |
− | }<br />
| |
− | break;<br />
| |
− | }<br />
| |
− | }<br />
| |
− | </pre>
| |
− | </div>
| |
− | </blockquote>
| |
− | <blockquote>// Example of a help menu (!help) spawn.h<br />
| |
− | <blockquote>void botInfo::gotHelp(Player *p, Command *c)<br />
| |
− | {<br />
| |
− | if (!*c->final)<br />
| |
− | {<br />
| |
− | sendPrivate(p, "4v4 Bot General Commands:");<br />
| |
− | sendPrivate(p, "------------------------");<br />
| |
− | sendPrivate(p, "!caps
| |
− | - get captain names");<br />
| |
− | sendPrivate(p, "!roster <squad> - get roster of a squad");<br />
| |
− | sendPrivate(p, "!schedule
| |
− | - get current schedule");<br /> sendPrivate(p,
| |
− | "!score
| |
− | - get current score");<br />
| |
− | <br />
| |
− | </blockquote>
| |
− | </blockquote>
| |
| | | |
− | ==Event Call Descriptions - in spawn.cpp==
| + | strncpy(squadA, "", 20); |
− | <blockquote>MERVBot is event based, so when making a bot you need to decide what will happen at certain events<br />
| + | strncpy(squadB, "", 20); |
− | Normal plugins need to consider what happens when bot enters arena, player enters arena, player leaves arena, <br />
| |
− | player events like kill, shipchange, teamchange, spec, move then any other relevent events to your bot<br />
| |
− | just worry about events that are relevent to the tasks your bot is doing<br />
| |
− | <br />
| |
− | // Timer<br />
| |
| | | |
− | case EVENT_Tick: - called 1x/sec, decrementing each countdown[n] by 1, use for time triggered events<br />
| + | int n_found; |
− | <br />
| |
| | | |
− | // Arena Events<br /> | + | //Note: %[A-Za-z ] is equivalent to %s, but allows an internal space. |
| | | |
− | case EVENT_ArenaEnter: - called when bot enters arena<br />
| + | //scan the string for the two squads separated by " vs " |
| + | n_found = sscanf(c->final, "%[A-Za-z ] vs %[A-Za-z ]", squadA, squadB); |
| | | |
− | case EVENT_ArenaSettings: - called when bot gets arena settings<br />
| + | //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> |
| | | |
− | case EVENT_ArenaLeave: - called when bot exits arena<br />
| + | ===Help Menu=== |
| + | When a player sends !help to the bot, MERVBot calls botInfo::gotHelp() in each plugin loaded. |
| + | <pre> |
| + | 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"); |
| + | </pre> |
| | | |
− | case EVENT_ArenaListEntry: - called when bot gets an entry from the arena list (esc-a)<br />
| + | ==Event Calls== |
| | | |
− | case EVENT_ArenaListEnd: - called when bot reads last entry of arena list (esc-a)<br />
| + | 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. |
− | <br />
| |
| | | |
− | // Flag Events<br />
| + | 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. |
| | | |
− | case EVENT_FlagGrab: - called when a pilot picks up a loose flag<br />
| + | 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(). |
| | | |
− | case EVENT_FlagDrop: - called when a pilot drops a flag (timer runs out)<br />
| + | <pre>tell(makeFollowing(false));</pre> |
| | | |
− | case EVENT_FlagMove: - called when a flag moves<br />
| + | ==The Messaging System== |
| | | |
− | case EVENT_FlagVictory: - called when a team wins flag game (gets all flags)<br />
| + | Private message - void sendPrivate(Player *player, char *msg); |
| | | |
− | case EVENT_FlagReward: - called when theres a periodic flag reward given<br />
| + | ''Examples:'' |
− | <br />
| |
| | | |
− | // Timed Game<br />
| + | <pre> |
| + | sendPrivate(p,"hi"); |
| | | |
− | case EVENT_TimedGameOver: - called when a timed game is over (like speed zone game)<br />
| + | String s="test"; |
− | <br />
| + | sendPrivate(p,s); |
| | | |
− | // Soccer<br />
| + | String s="test"; |
| + | s += "ing"; |
| + | sendPrivate(p,s); |
| | | |
− | case EVENT_SoccerGoal: - called when a soccer goal is scored<br />
| + | char captain1[20]; |
| + | char captain2[20]; |
| + | strncpy(captain1,"",20); |
| + | strncpy(captain2,"",20); |
| + | sendPrivate(p,(String) captain1 + " and " + (String) captain2 + " are the captains."); |
| + | </pre> |
| | | |
− | case EVENT_BallMove: - called when a soccer ball moves<br />
| + | Team message - void sendTeamPrivate(Uint16 team, char *msg);<br /> |
− | <br />
| + | <blockquote>Examples: <br /> |
| + | a) sendTeamPrivate(8025,"hi spec freq");<br /> |
| + | b) Uint16 test=0; sendTeamPrivate(test,"hi freq 0");<br /> |
| + | </blockquote> |
| | | |
− | // Receive file<br /> | + | 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> |
| | | |
− | case EVENT_File: - called when a file is received (*getfile)<br />
| + | Note: to have bot print several lines of text fast it needs sysop in the |
− | <br />
| + | arena (sysop in arena bot first spawns to also) otherwise it'll print slow to avoid being |
| + | kicked for spam |
| | | |
− | // Player events<br /> | + | ===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 |
| | | |
− | case EVENT_PlayerEntering: - called when a pilot enters the arena<br />
| + | String s = "*arena "; |
| + | s += temp; |
| + | s += " pilots left in the game."; |
| | | |
− | case EVENT_PlayerMove: - called when a pilot's ship moves<br />
| + | sendPublic(s); |
| + | </pre> |
| + | Or, |
| + | <pre> |
| + | //NOTE: this can be considered inefficient. |
| | | |
− | case EVENT_PlayerWeapon: - called when a pilot fires a weapon<br />
| + | sendPublic("*arena " + (String)temp + " pilots left in the game"); |
− | case EVENT_WatchDamage: - called when | + | </pre> |
− | a pilot takes damage (bot must have /*watchdamage on for them)<br />
| |
| | | |
− | case EVENT_PlayerDeath: - called when a pilot dies<br />
| + | <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 |
| | | |
− | case EVENT_PlayerScore: - called when a pilot changes their score (?scorereset)<br />
| + | char str[255]; |
| + | sendPublic("*arena--------------------------------------------------------------------------------"); |
| | | |
− | case EVENT_PlayerPrize: - called when a pilot gets a prize (green)<br />
| + | sprintf(str, "*arena Squad: %-20s PTS FPTS K D DMG DEALT TAKEN F FK FLT", |
| + | freqs[freq].freqname |
| + | ); |
| | | |
− | case EVENT_PlayerShip: - called when a pilot ship changes<br />
| + | sendPublic(str); |
| | | |
− | case EVENT_PlayerSpec: - called when a pilot spectates<br />
| + | sendPublic("*arena--------------------------------------------------------------------------------"); |
| | | |
− | case EVENT_PlayerTeam: - called when a pilot changes freqs<br />
| + | // assuming existing freqs struct with data |
| + | for (pilot=freqs[freq].playercount-1; pilot>=0; pilot--) |
| + | { |
| + | // on freq squad so print stats |
| + | char outString[255]; |
| | | |
− | case EVENT_PlayerLeaving: - called when a pilot exits arena (esc-q, ?go <arena>, or lagout)<br />
| + | sprintf(outString, "*arena %-20s %12d %8d %3d %3d %10d %6d %2d %3d %3d:%02d", |
− | <br />
| + | 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); |
| + | } |
| | | |
− | // Bot events<br /> | + | // 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> |
| | | |
− | case EVENT_SelfShipReset: - called when bot's ship gets reset<br />
| + | ==Time== |
| | | |
− | case EVENT_SelfPrize: - called when bot gets a prize (green)<br />
| + | 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. |
| | | |
− | case EVENT_SelfUFO: - called when bot toggles UFO mode<br />
| + | 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 |
| | | |
− | case EVENT_PositionHook: - called when "Core is requesting the DLL to send a position packet"<br />
| + | // unrelated code |
− | <br />
| + | |
| + | 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. |
| | | |
− | // Brick<br /> | + | 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> |
| | | |
− | case EVENT_BrickDropped: - called when a pilot uses a brick<br />
| + | 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. |
− | <br />
| |
| | | |
− | // Objects<br />
| + | === Tracking time not using countdown[n] === |
| | | |
− | case EVENT_ObjectToggled: - called when a .lvz object is toggled on/off<br />
| + | 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. |
− | <br />
| |
| | | |
− | // Turrets<br />
| + | Lucky for us, Windows provides a function called GetTickCount() that is a measurement of time (milliseconds) that we can use for such cases. |
| | | |
− | case EVENT_CreateTurret: - called when a pilot attaches to another pilot (f7)<br />
| + | So: |
| + | <pre> |
| + | int begin = GetTickCount(); |
| | | |
− | case EVENT_DeleteTurret: - called when a pilot detaches from a turret<br />
| + | // do some code here. |
− | <br />
| |
| | | |
− | // Messages<br /> | + | int end = GetTickCount(); |
| + | |
| + | int delta = (end - begin) / 1000; // elapsed time converted to seconds from milliseconds |
| + | </pre> |
| | | |
− | case EVENT_Chat: - called when bot gets a chat message<br />
| + | === Obtaining the current time === |
| | | |
− | case EVENT_LocalCommand: - called when bot gets a player command from arena<br />
| + | ''Requirements:'' Include <time.h>. |
| | | |
− | case EVENT_LocalHelp: - called when bot gets a player !help command<br />
| + | 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> |
| | | |
− | case EVENT_RemoteCommand: - called when bot gets a command from outside arena<br />
| + | ==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" //////// |
| | | |
− | case EVENT_RemoteHelp: - called when bot gets a player !help command from outside arena<br />
| + | bool closeto(Player *p, int x, int y, int tolerance) |
− | <br />
| + | { |
| + | // 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> |
| | | |
− | case EVENT_Init: - called when DLL is loaded<br />
| |
| | | |
− | case EVENT_Term: - called when DLL is unloaded<br />
| + | 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. |
− | </blockquote>
| + | <pre> |
− | <br />
| + | //////// DLL "import" //////// |
− | <a name="3"></a>
| |
− | ==Messaging - How to use the messaging system==
| |
− |
| |
− | <blockquote>Private message - void sendPrivate(Player *player, char *msg);<br />
| |
− | <blockquote>Examples:<br />
| |
− | a) sendPrivate(p,"hi");<br />
| |
− | b) String s="test"; sendPrivate(p,s);<br />
| |
− | c) String s="test"; s += "ing"; sendPrivate(p,s);<br />
| |
− | </blockquote>
| |
− | <blockquote>d) char captain1[20]; char captain2[20];<br />
| |
− | strncpy(captain1,"",20); strncpy(captain2,"",20);<br />
| |
− | sendPrivate(p,(String) captain1 + " and " + (String) captain2 + " are the captains.");<br />
| |
− | </blockquote>
| |
− | <blockquote>
| |
− | </blockquote>
| |
| | | |
− | <blockquote>Team message - void sendTeamPrivate(Uint16 team, char *msg);<br />
| + | bool botInfo::closeto(Player *p, int x, int y, int tolerance) |
− | <blockquote>Examples: <br />
| + | { |
− | a) sendTeamPrivate(8025,"hi spec freq");<br />
| + | ... |
− | b) Uint16 test=0; sendTeamPrivate(test,"hi freq 0");<br />
| + | } |
− | </blockquote>
| + | </pre> |
− | </blockquote>
| + | In '''spawn.h''', add your method's prototype without botInfo::, it will look |
− | <blockquote>Public message - void sendPublic(char *msg);<br />
| + | like this: |
− | <blockquote>Example: sendPublic("*arena " + (String) p->name + " is now a captain");<br />
| + | <pre> |
− | </blockquote>
| + | //... |
− | Chat channel message - void sendChannel(char *msg);<br />
| + | botInfo(CALL_HANDLE given) |
− | <blockquote>Example: sendChannel("hi chat channel");<br />
| + | { |
− | </blockquote>
| + | // ... |
− | Remote private message - void sendRemotePrivate(char *name, char *msg);<br />
| + | } |
− | <blockquote>Example: sendRemotePrivate("Player01", "hi");<br />
| + | bool closeto(Player *p, int x, int y, int tolerance); // Your function prototype. |
− | </blockquote>
| |
− | </blockquote>
| |
− | <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<br />
| |
− | </blockquote>
| |
| | | |
− | <br />
| + | void clear_objects(); //provided by Catid, and already exists. |
− | <a name="4"></a>
| + | void object_target(Player *p); //provided by Catid, and already exists. |
− | ==Timer - How to use the timing function==
| + | // ... |
− |
| + | }; |
− | <blockquote>Setup number of timers and initialize:<br />
| + | </pre> |
− | <blockquote>// spawn.h - specify how many timers<br />
| + | If you're not familiar with prototypes, notice it is similar to that in your spawn.cpp, but without the botInfo::, and a trailing ;. |
− | </blockquote>
| |
− | <blockquote>class botInfo<br />
| |
− | {<br />
| |
− | int countdown[10]; // this gives you 10 timers</blockquote>
| |
− | <blockquote> // spawn.h - initialize timers<br />
| |
− | <br />
| |
− | public:<br />
| |
− | botInfo(CALL_HANDLE given)<br />
| |
− | {<br />
| |
− | countdown[0] = 0;<br />
| |
− | countdown[1] = 60; // 60 seconds<br />
| |
− | //<br />
| |
− | // initialize values<br />
| |
− | //<br />
| |
− | countdown[9] = 5*60; // 5 minutes<br />
| |
− | </blockquote>
| |
− | Using timer functions:<br />
| |
− | <blockquote> // in spawn.cpp<br />
| |
− | <br />
| |
− | case EVENT_Tick:<br />
| |
− | {<br />
| |
− | for (int i = 0; i < 10; ++i)<br />
| |
− | --countdown[i];<br />
| |
− | <br />
| |
− | if (countdown[1] == 2) // when timer #1 hits two seconds<br />
| |
− | {<br />
| |
− | // do stuff here when timer #1 hits 2 seconds<br />
| |
− | // example: sendPublic("two seconds left, setting timer to 1 minute");<br />
| |
− | // example: countdown[1] = 60; // change timer #1 value<br />
| |
− | }<br />
| |
− | </blockquote>
| |
− |
| |
− | <blockquote> Notes: - In the "for" loop, you want "i < 10" to be the number of timers from spawn.h<br />
| |
− | - The timer is in seconds. EVENT_Tick is called once per second.<br />
| |
− | - The timers decrease by one each second down, not stopping at 0.<br />
| |
− | <br />
| |
− | - You can change the value of the countdown[n]'s in other parts of the program<br />
| |
− | (ie. case EVENT_PlayerDeath: { countdown[1] = 45; // set timer #1 to 45 sec<br />
| |
− | everytime someone dies)<br />
| |
− | </blockquote>
| |
− | </blockquote>
| |
| | | |
− | <br />
| + | ===Function notes=== |
− | <a name="5"></a>
| |
− | ==Writing Functions== | |
− | <blockquote>
| |
− | <blockquote>Write your function in spawn.cpp at top of file in the //////// DLL "import" //////// section
| |
− | <blockquote> //////// DLL "import" ////////<br />
| |
− | <br />
| |
| | | |
− | bool closeto(Player *p, int x, int y, int tolerance) // note uses abs() function declared elsewhere<br />
| + | 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. |
− | {<br />
| |
− | // return if player
| |
− | p is in area of square with center x,y and radius = tolerance<br />
| |
| | | |
− | return (abs((p->tile.x) - x) <
| + | From time to time you will need to pass an array to a function. An example illustrating this is: |
− | tolerance) && (abs((p->tile.y) - y) < tolerance);<br />
| + | <pre> |
| + | int freqs[5]; // declare our example data. |
| | | |
− |
| + | // call function - notice freqs and not freqs[5] or freqs[]. |
− | }<br />
| + | my_function(freqs); //You're not passing the array itself, just a pointer to the array. |
− | </blockquote> If
| |
− | you want your function to have access to data from the spawn.h botInfo section<br />
| |
− | <blockquote>public:<br />
| |
− | botInfo(CALL_HANDLE given)<br />
| |
− | {<br />
| |
− | <br />
| |
− | // then you need to make your spawn.cpp function in the format:<br />
| |
− | <br />
| |
− | bool botInfo::closeto(Player *p, int x, int y, int tolerance)<br />
| |
− | {<br />
| |
− | // function here<br />
| |
− | }<br />
| |
− | <br />
| |
− | Include function in spawn.h at bottom of file with the other functions<br />
| |
− | <blockquote> public:<br />
| |
| | | |
− | botInfo(CALL_HANDLE given)<br />
| + | // 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> |
| | | |
− | {<br />
| + | ==Cycling through players== |
| | | |
− | }<br />
| + | 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. |
− | <br />
| |
| | | |
− | bool closeto(Player *p, int x, int y, int tolerance); <br />
| + | 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: |
− | // your new function. note: w/o botInfo keyword and with a ;<br />
| + | <pre> |
− | <br />
| + | _listnode <Player> *parse = playerlist->head; //set to first link of the player linked list |
| | | |
− | void clear_objects(); // functions already there<br />
| + | 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 |
| | | |
− | void object_target(Player *p); // functions already there</blockquote>
| + | // 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 |
| | | |
− | </blockquote>
| + | parse = parse->next; //set parse to the next link |
− | </blockquote>
| + | } |
− | <blockquote>Pass values by reference
| + | </pre> |
− | <blockquote>bool botInfo::closeto(Player *p, int& x, int y, int tolerance) // note the &<br />
| |
− | <br />
| |
− | bool is_close = closeto(p,x,y,tolerance); // if x is changed in closeto() its saved here <br />
| |
− | </blockquote>
| |
− | </blockquote>
| |
| | | |
− | <blockquote>Pass array as parameter<br />
| + | 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: |
− | <blockquote>int freqs[5];<br />
| + | <pre> |
− | <br />
| + | void handleCmdSetShip(enum Ship_Types ship); |
− | my_function(freqs); // call function - notice freqs and not freqs[5] or freqs[]<br />
| + | </pre> |
− | <br />
| |
− | void my_function(int freqs[]) {} // function - notice freqs[] and not freqs[5] or freqs<br />
| |
− | </blockquote>
| |
− | </blockquote>
| |
− | <a name="6"></a>
| |
− | ==Cycling players- How to search through the players in the arena==
| |
− | <blockquote> _listnode <Player> *parse = playerlist->head;<br />
| |
− | <br />
| |
− | while (parse)<br />
| |
− | {<br />
| |
− | Player *p = parse->item;<br />
| |
− | <br />
| |
− | // do functionality here<br />
| |
− | // Example 1: sendPrivate(p,"*watchdamage"); // turns on all pilot's watchdamage<br />
| |
− | // Example 2: if (p->safety != 0)
| |
− | sendPrivate(p,"*spec"); // spec all pilots in safe zone<br />
| |
− | <br />
| |
− | parse = parse->next<br />
| |
− | }<br />
| |
− | </blockquote>
| |
− | <br />
| |
| | | |
− | ==Checking if pilot is in a safe zone==
| + | In spawn.cpp add: |
− | <blockquote> if (p->safety != 0) // pilot is in a safe zone<br />
| + | <pre> |
− | if (p->safety == 0) // pilot is NOT in a safe zone<br />
| + | void botInfo::handleCmdSetShip(enum Ship_Types ship) |
− | <br />
| + | { |
− | To detect a pilot entering a safe zone do:<br />
| + | //Note that the parameter ship is of the Ship_Types enum, |
− | <br />
| + | //so its value is hopefully restricted to the proper types. |
− | case EVENT_PlayerMove:<br />
| |
− | {<br />
| |
− | if (p->safety != 0)<br />
| |
− | </blockquote><br />
| |
| | | |
− | ==Random number==
| + | _listnode <Player> *parse = playerlist->head; |
− | <blockquote>Method 1 (completely random)<br />
| + | while (parse) |
− | <blockquote> #include "time.h"<br />
| + | { |
− | <br />
| + | Player *p = parse->item; |
− | srand(time(NULL));<br />
| |
− | rand();<br />
| |
− | int temp = (int) (51 * ((float)rand()/RAND_MAX)); // returns a random integer between 0 and 51<br />
| |
− | </blockquote>
| |
− | <br />
| |
− | Method 2 (random pilot in arena)<br />
| |
− | <blockquote> #include <stdlib.h><br />
| |
− | <br />
| |
− | int temp = GetTickCount() % getIngame(); // getIngame() = how many pilots in arena<br />
| |
− | <br />
| |
− | _listnode <Player> *parse = playerlist->head;<br />
| |
− | while (parse)<br />
| |
− | {<br />
| |
− | Player *p = parse->item;<br />
| |
− | <br />
| |
− | if (p->ship != SHIP_Spectator){ // if not in spec<br />
| |
− | if (!(--temp) { // decrement temp, if its 0, make this pilot rabbit<br />
| |
− | rabbit = p;<br />
| |
− | break;<br />
| |
− | }}<br />
| |
− | parse = parse->next;<br />
| |
− | }<br />
| |
− | </blockquote>
| |
− | </blockquote>
| |
− | <br />
| |
− | <a name="9"></a>
| |
− | ==Tracking time not using countdown[n]==
| |
− | <blockquote> #include <stdlib.h><br />
| |
− | <br />
| |
− | int temp = GetTickCount(); // get time stamp<br />
| |
− | // later in program<br />
| |
− | temp = (GetTickCount() - temp)/1000; // how many seconds have passed<br />
| |
− | </blockquote>
| |
− | <br />
| |
| | | |
− | ==Storing data for pilots==
| + | if ( p->ship != ship && p->ship != SHIP_Spectator ) |
− | <blockquote>There are several ways to store data for pilots (ie tracking flagtime or kills in a period of time)<br />
| + | sendPrivate(p, "*setship " + (String)ship); |
− | <br />
| |
− | 1) get/setTag - use if you only want to track data until pilot leaves arena then its erased<br />
| |
− | built in tags track by an ID that is reset when pilot leaves/enters arena, so loses track of data once they leave arena<br />
| |
− | <br />
| |
− | 2) modified perm get/setTag - use if you want to track all pilots even if they leave (advantage - easier to sort by player)<br />
| |
− | also can track near unlimited amount of pilots<br />
| |
− | <br />
| |
− | 3) structs - use to track all pilots even if they leave, (advantage -
| |
− | easier to sort by freqs), have to specify bound of players<br />
| |
− | <br />
| |
− | note: 2 and 3 are similar in effect, mostly the difference is in how you are able to search through data<br />
| |
− | you need to decide which method of storing data is best for each bot depending on what it does<br />
| |
− | 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<br />
| |
− | <br />
| |
| | | |
− | // initialize values in spawn.h at very top<br />
| + | parse = parse->next; |
− | <br />
| + | } |
− | 1) Built in get/setTag method</blockquote><blockquote><blockquote> #define DMG_DEALT 0<br />
| + | } |
− | #define DMG_TAKEN 1<br />
| + | </pre> |
− | <br />
| |
− | // in spawn.cpp initialize the values on arena-enter and player-enter<br />
| |
− | <br />
| |
− | case EVENT_ArenaEnter: {<br />
| |
− | <br />
| |
− | _listnode <Player> *parse = playerlist->head;<br />
| |
− | <br />
| |
− | while (parse) // do for all pilots in arena when bot enters<br />
| |
− | {<br />
| |
− | Player *p = parse->item; // get pilot<br />
| |
− | <br />
| |
− | set_tag(p, DMG_DEALT, 0); // initialize to 0<br />
| |
− | set_tag(p, DMG_TAKEN, 0);<br />
| |
− | <br />
| |
− | sendPrivate(p,"*watchdamage");
| |
− | // optionally turn on player *watchdamage<br />
| |
− | <br />
| |
− | parse = parse->next; // get next pilot<br />
| |
− | }<br />
| |
− | }<br />
| |
− | <br />
| |
− | case EVENT_PlayerEntering: {<br />
| |
− | <br />
| |
− | set_tag(p, DMG_DEALT, 0); // initialize to 0<br />
| |
− | set_tag(p, DMG_TAKEN, 0);<br />
| |
− | <br />
| |
− | sendPrivate(p,"*watchdamage");<br />
| |
− | <br />
| |
− | // then somewhere edit the tag values<br />
| |
− | <br />
| |
− | case EVENT_WatchDamage: {<br />
| |
− | <br />
| |
− | // sets tag for k (shooter) to be old value plus damage dealt currently<br />
| |
− | set_tag(k, DMG_BOMB_DEALT, get_tag(k, DMG_BOMB_DEALT) + damage);<br />
| |
− | <br />
| |
− | // how to retrieve the tag values<br />
| |
− | <br />
| |
− | // as a command in spawn.h<br />
| |
− | <br />
| |
− | else if (c->check("showstats")) {<br />
| |
− | <br />
| |
− | int temp = get_tag(p, DMG_TOTAL_DEALT);<br />
| |
− | <br />
| |
− | String s = "You've done ";<br />
| |
− | s += temp;<br />
| |
− | s += " damage so far!";<br />
| |
− | <br />
| |
− | sendPrivate(p,s);<br />
| |
− | <br />
| |
− | // kill tags when player leaves arena<br />
| |
− | <br />
| |
− | case EVENT_PlayerLeaving: {<br />
| |
− | <br />
| |
− | killTags(p);<br />
| |
− | </blockquote>2) Modified permanent get/setTag method<br />
| |
− | <blockquote>// same as get/setTag with some modifications to the tag code, then can use tags exactly as above<br />
| |
− | <br />
| |
− | // spawn.h, add char name[20]; into struct PlayerTag<br />
| |
− | <blockquote>struct PlayerTag<br />
| |
− | {<br />
| |
− | Player *p;<br />
| |
− | char name[20];<br />
| |
− | int index;<br />
| |
− | int data;<br />
| |
− | };<br />
| |
− | </blockquote>
| |
− | // spawn.cpp, modifications<br />
| |
− | <blockquote>case EVENT_PlayerLeaving:<br />
| |
− | {<br />
| |
− | Player *p = (Player*)event.p[0];<br />
| |
− | <br />
| |
− | // killTags(p); // remove so tag not deleted on arena exit<br />
| |
− | <br />
| |
− | </blockquote>
| |
− | <blockquote>int botInfo::get_tag(Player *p, int index)<br />
| |
− | {<br />
| |
− | _listnode <PlayerTag> *parse = taglist.head;<br />
| |
− | PlayerTag *tag;<br />
| |
− | <br />
| |
− | while (parse)<br />
| |
− | {<br />
| |
− | tag = parse->item;<br />
| |
− | <br />
| |
− | // if (tag->p == p)<br /> | |
− | if (strcmp(tag->name,p->name)==0) // now tracking by player name, not ID<br />
| |
− | if (tag->index == index)<br />
| |
− | return tag->data;<br />
| |
− | <br />
| |
− | parse = parse->next;<br />
| |
− | }<br />
| |
− | <br />
| |
− | return 0;<br />
| |
− | }<br /> | |
− | <br />
| |
− | void botInfo::set_tag(Player *p, int index, int data)<br />
| |
− | {<br />
| |
− | _listnode <PlayerTag> *parse = taglist.head;<br />
| |
− | PlayerTag *tag;<br />
| |
− | <br />
| |
− | while (parse)<br />
| |
− | {<br />
| |
− | tag = parse->item;<br />
| |
− | <br />
| |
− | //if (tag->p == p)<br />
| |
− | if (strcmp(tag->name,p->name)==0) // now tracking by player name, not ID<br />
| |
− | if (tag->index == index)<br />
| |
− | {<br />
| |
− | tag->data = data;<br />
| |
− | return;<br />
| |
− | }<br />
| |
− | parse = parse->next;<br />
| |
− | }<br />
| |
− | <br />
| |
− | tag = new PlayerTag;<br />
| |
− | // tag->p = p; // not tracking by ID anymore<br />
| |
− | strncpy(tag->name, p->name, 20); // tracking by player name<br />
| |
− | tag->index = index;<br />
| |
− | tag->data = data;<br />
| |
− | taglist.append(tag);<br />
| |
− | }</blockquote>
| |
− | </blockquote>
| |
− | 3) Using Struct's<br />
| |
− | <blockquote>struct name <br />
| |
− | {
| |
− |
| |
− | <br />
| |
| | | |
− | // variables <br />
| + | To use, just call the function with the appropriate Ship_Type from the enum in clientprot.h: |
| + | <pre> |
| + | handleCmdSetShip(SHIP_Warbird); |
| + | </pre> |
| | | |
− | };<br />
| + | ==Random numbers== |
− | <br />
| |
− | // Example: (in spawn.h)<br />
| |
− | <blockquote>class botInfo<br />
| |
− | {<br />
| |
− | <br />
| |
− | struct freqdata {<br />
| |
− | int kills;<br />
| |
− | int deaths; <br />
| |
− | };<br />
| |
− | <br />
| |
− | freqdata freqs[100]; // 100 of those structs<br />
| |
− | <br />
| |
− | // then access it in spawn.cpp using<br />
| |
− | freqs[56].kills = 1;<br />
| |
− | </blockquote>
| |
− | </blockquote>
| |
− | <br />
| |
− | </blockquote>
| |
| | | |
− | ==Output of data/messages== | + | ===Generating a random number=== |
− | <br />
| |
− | using normal Strings<br />
| |
− | <br />
| |
− | // does *arena X pilots left in the game.<br />
| |
− | <br />
| |
− | String s = "*arena ";<br />
| |
− | s += temp; // some variable (int)<br />
| |
− | s += " pilots left in the game.";<br />
| |
− | sendPublic(s)<br />
| |
− |
| |
− | <blockquote>OR<br />
| |
− |
| |
− | </blockquote>
| |
− | <blockquote>sendPublic("*arena " + (String) temp + " pilots left in the game.");<br />
| |
− |
| |
− |
| |
− | </blockquote>
| |
| | | |
− | <br />
| + | To use this method, these two includes must be used: |
− | example using sprintf to align/space data<br />
| + | <pre> |
− | <br />
| + | #include "time.h" //provides time() function. |
− | // output data
| + | #include "stdlib.h" //provides srand() and rand() functions. |
− | will be in this approximate format (not lined up perfectly because of html)<br />
| + | </pre> |
− | <br />
| |
− | //
| |
− | ----------------------------------------------------------------------------------------<br />
| |
− | // Squad: squadname
| |
− | PTS FPTS K D DMG DEALT
| |
− | TAKEN F FK FLT<br />
| |
− | // ----------------------------------------------------------------------------------------<br />
| |
− | //
| |
− | PlayerA
| |
− | 10000 500
| |
− | 116 101
| |
− | 9999 99999 10 150 980:55<br />
| |
− | // PlayerB
| |
− | 500
| |
− | 200 7 5
| |
− | 9999 99999 5
| |
− | 3 0:04<br />
| |
− | <br />
| |
− | char str[255];<br />
| |
− | sendPublic("*arena----------------------------------------------------------------------------------");<br />
| |
− | sprintf(str, "*arena
| |
− | Squad: %-20s PTS FPTS K
| |
− | D DMG DEALT TAKEN F FK FLT",freqs[freq].freqname);<br />
| |
− | sendPublic(str);<br />
| |
− | sendPublic("*arena----------------------------------------------------------------------------------");<br />
| |
− | <br />
| |
− | // assuming existing freqs struct with data<br />
| |
− | for (pilot=freqs[freq].playercount-1; pilot>=0; pilot--)<br />
| |
− | {<br />
| |
− | // on freq squad so print stats<br />
| |
− | char outString[255];<br />
| |
− | <br />
| |
− | sprintf(outString,
| |
− | "*arena %-20s %12d %8d %3d %3d %10d %6d %2d %3d %3d:%02d",freqs[freq].pilots[pilot].name,<br />
| |
− | freqs[freq].pilots[pilot].points,
| |
− | freqs[freq].pilots[pilot].flagpoints, freqs[freq].pilots[pilot].kills,<br />
| |
− | freqs[freq].pilots[pilot].deaths,
| |
− | freqs[freq].pilots[pilot].dmgdealt, freqs[freq].pilots[pilot].dmgtaken,freqs[freq].pilots[pilot].flags,<br />
| |
− |
| |
− | freqs[freq].pilots[pilot].flagkills, freqs[freq].pilots[pilot].flagtime
| |
− | /60, freqs[freq].pilots[pilot].flagtime %60);<br />
| |
− | <br />
| |
− | sendPublic(outString);<br />
| |
− | }<br />
| |
− | <br />
| |
− | // Notes: sprintf
| |
− | format = sprintf(output char string, spacing, variables)<br />
| |
− | // Notes: s = chars, d = integer, - = left align, right align default<br />
| |
− | // Notes: doing
| |
− | %02d = put 0 in front if not 2 digits, %3d:%02d makes 0:04 format<br />
| |
− | <br />
| |
− | <br />
| |
− | <a name="12"></a>
| |
− | ==Input/Output to files==
| |
− |
| |
− | <blockquote>Input to file<br />
| |
− | <blockquote>// example reading from duel.ini looking for line that starts with MaxBoxes= then taking the next char as value to store as <br />
| |
− | // MAX_BOXES (ie duel.ini = MaxBoxes=5)<br />
| |
− | #include <fstream><br /> | |
− | using namespace std;<br />
| |
− | <br />
| |
− | ifstream file("duel.ini");<br />
| |
− | char line[256];<br />
| |
− | <br />
| |
− | // read in MaxBoxes=X<br />
| |
− | while (file.getline(line, 256))<br />
| |
− | {<br />
| |
− | if (CMPSTART("MaxBoxes=", line))<br />
| |
− | {<br />
| |
− | MAX_BOXES = atoi(&(line[9]));<br />
| |
− | break;<br />
| |
− | }<br />
| |
− | }<br />
| |
− | </blockquote>
| |
− | Output to file<br />
| |
− |
| |
− |
| |
− | <blockquote>// normal char output<br />
| |
− |
| |
− |
| |
− | <blockquote>#include <fstream><br />
| |
| | | |
| + | Use: |
| + | <pre> |
| + | srand(time(NULL)); // seed random number generator. |
| | | |
− | using namespace std;<br />
| + | rand(); // randomize. |
| | | |
− | <br />
| + | 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> |
| | | |
− | ofstream file("duelleaguestat.inc", ios::app); // app = put all data at end of file<br />
| + | ===Picking a random pilot=== |
| | | |
− | <br />
| + | ''Note:'' A required user-defined function, getInGame(), must be created for this example to work. |
| | | |
− | file << squad1<< endl; // squad1 = char[20]<br />
| + | <pre> |
| + | int temp = GetTickCount() % getInGame(); // getInGame() = how many pilots in arena |
| | | |
− | file << " vs "<< endl;<br />
| + | Player *rabbit = NULL; |
| | | |
− | file << squad2<< endl; // squad2 = char[20]<br />
| + | _listnode <Player> *parse = playerlist->head; |
− |
| + | while (parse) |
− |
| + | { |
− | </blockquote>
| + | Player *p = parse->item; |
− |
| |
− | // how to output String's
| |
− | to file (key is converting String to (char*) to file write)<br />
| |
− |
| |
− |
| |
− | <blockquote>
| |
− | String str = freqs[freq].slotname[slot];<br />
| |
| | | |
− | str += ", Repels: " + (String)(int) t->repel;<br />
| + | 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== |
− |
| |
− | <br />
| |
| | | |
− | outf << endl;<br />
| + | 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. |
| | | |
− | outf << (char*) str;<br />
| + | # 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) |
− | </blockquote>
| + | # Custom Structs: Tracks data until plugin deletes it. (Advantage: easier to sort by freqs) |
| | | |
− | <br />
| + | ''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. |
− | // date and time stamp<br />
| |
− |
| |
− | <blockquote>#include "time.h"<br />
| |
− | <br />
| |
− | char u[100];<br />
| |
− | time_t t=time(NULL);<br />
| |
− | tm *tmp = localtime(&t);<br />
| |
− | strftime(u,99,"%c",tmp);<br />
| |
− | sendPublic("Date and time: " + (String) u);<br />
| |
| | | |
− |
| + | ===Built-in get/setTag method=== |
− | </blockquote>
| |
− | </blockquote>
| |
− | </blockquote>
| |
− | <blockquote>Example reading input from file using "GetPrivateProfileString" (from rampage plugin)<br />
| |
− | <blockquote>format of rampage.ini<br />
| |
− | <blockquote>7=is on a killing spree! (6:0)<br />10=is opening a can of whoop-ass! (9:0)<br />
| |
− | </blockquote>
| |
− | read input<br />
| |
− | <blockquote>rampageini.h<br />
| |
− | <blockquote>#pragma once<br />
| |
| | | |
− | #ifndef RAMPAGEINI_H<br />
| + | Player tags simply tag a player with a number. Define the wanted values in '''spawn.h''' at the top: |
− | #define RAMPAGEINI_H<br />
| + | <pre> |
− | <br />
| + | #define DMG_DEALT 0 |
− | #define NUM_RANKS 10<br />
| + | #define DMG_TAKEN 1 |
− | #define BUFFER_LEN 256<br />
| + | </pre> |
− | <br />
| |
− | struct RampageSettings<br />
| |
− | {<br />
| |
− | char quotes[NUM_RANKS][BUFFER_LEN];<br />
| |
− | };<br />
| |
− | <br />
| |
− | void LoadSettings(RampageSettings &setts);<br />
| |
− | <br />
| |
− | #endif // RAMPAGEINI_H<br />
| |
− | </blockquote>
| |
− | rampageini.cpp<br />
| |
− | <blockquote>#include "rampageini.h"<br />
| |
− | static char buffer[BUFFER_LEN];<br />
| |
− | static char path[BUFFER_LEN];<br />
| |
− | #include "../algorithms.h"<br /> | |
− | #define WIN32_LEAN_AND_MEAN<br /> | |
− | #include <windows.h><br />
| |
− |
| |
− | <br />
| |
− | char *rank_type[10] = {<br />
| |
− | "7",<br />
| |
− | "10",<br />
| |
− | };<br />
| |
− | <br />
| |
− | void LoadSettings(RampageSettings &setts)<br />
| |
− | {<br />
| |
− | GetCurrentDirectory(BUFFER_LEN - 64, path);<br />
| |
− | strcat(path, "\rampage.ini");<br />
| |
− |
| |
− | <br />
| |
− | for (int i = 0; i < NUM_RANKS; ++i)<br />
| |
− | {<br />
| |
− | GetPrivateProfileString("Comments",
| |
− | rank_type[i], "-ERROR-", setts.quotes[i], BUFFER_LEN, path);<br />
| |
− | }<br />
| |
− | }<br />
| |
− | </blockquote>
| |
− | </blockquote> </blockquote> </blockquote>
| |
| | | |
− | ==Simple programming commands==
| + | In spawn.cpp, initialize the values on ArenaEnter and PlayerEnter: |
− |
| + | <pre> |
− |
| + | case EVENT_ArenaEnter: |
− | <blockquote>simple commands <br />
| + | { |
− |
| + | // ... |
− | <blockquote>
| |
− | declare/initialize variables // example: int
| |
− | temp; int temp2 = 1; temp2 = 3; <br />
| |
− |
| |
− |
| |
− | // bool check = true; check = false;<br />
| |
− |
| |
− | <br />
| |
− | if (condition) {}
| |
− | // example: if (a > b) { a++; }<br />
| |
| | | |
− | <br />
| + | // do for all pilots in arena when bot enters |
| + | _listnode <Player> *parse = playerlist->head; |
| + | while (parse) |
| + | { |
| + | Player *p = parse->item; // get pilot |
| | | |
− | if (condition) {} else {}
| + | set_tag(p, DMG_DEALT, 0); // initialize to 0 |
− | // example: if (b <= 0) { b--; } else { a++;
| + | set_tag(p, DMG_TAKEN, 0); |
− | }<br />
| + | sendPrivate(p, "*watchdamage"); // optionally turn on player *watchdamage |
| | | |
− | <br />
| + | 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> |
| | | |
− | while (condition) {}
| + | Then somewhere edit the tag values: |
− | // example: while (b > 0) { b--; }<br />
| + | <pre> |
| + | case EVENT_WatchDamage: |
| + | { |
| + | // sets tag for k (shooter) to be old value plus damage currently dealt |
| | | |
− | <br />
| + | int old_damage = get_tag(k, DMG_BOMB_DEALT); |
| + | set_tag(k, DMG_BOMB_DEALT, old_damage + damage); |
| + | } |
| + | </pre> |
| | | |
− | for (initialize; condition; increment){}
| + | The following demonstrates how to retrieve the tag values as a command in command.cpp: |
− | // example: for (a=1; a < 10; a++) { a = a + b; }<br />
| + | <pre> |
− |
| + | if (c->check("showstats")) |
− | </blockquote>
| + | { |
| + | int temp = get_tag(p, DMG_TOTAL_DEALT); |
| | | |
− | array<br />
| + | String s = "You've done "; |
− |
| + | s += temp; |
− | <blockquote>single dimension<br />
| + | s += " damage so far!"; |
− |
| |
− | <blockquote>int teams[100]; // create 100 hundred teams 0-99<br />
| |
− |
| |
− | <br />
| |
− | teams[50] = 1;<br />
| |
− |
| |
− | </blockquote>
| |
− | multi-dimensional<br />
| |
− |
| |
− | <blockquote>int teams[100][50]; // multidimensional arry<br />
| |
− |
| |
− | <br />
| |
− | teams[99][49] = 2;<br />
| |
− |
| |
− | </blockquote>
| |
− | variable size <br />
| |
− |
| |
− | <blockquote>String *list = new String[amount+1]; // string array with size amount (variable) + 1;<br />
| |
− |
| |
− | <br />
| |
− | list[amount-1] = "hi";<br />
| |
− |
| |
− | </blockquote> </blockquote>
| |
− | struct <br />
| |
− |
| |
− | <blockquote> struct name
| |
− | <br />
| |
| | | |
− | {
| + | sendPrivate(p,s); |
− |
| + | } |
− | <br />
| + | </pre> |
| | | |
− | // variables
| + | ===Modified permanent get/setTag method=== |
− | <br />
| |
| | | |
| + | 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. |
| | | |
− | };<br />
| |
− |
| |
− | <blockquote>// example:<br />
| |
− | struct freqdata {
| |
− | int kills; int deaths; };<br />
| |
− |
| |
− | <br />
| |
− | freqdata freqs[100]; // 100 of those structs<br />
| |
− |
| |
− | <br />
| |
− | freqs[56].kills =
| |
− | 1; // access struct<br />
| |
− |
| |
− | </blockquote> </blockquote>
| |
| | | |
− | switch <br />
| + | // spawn.h, add char name[20]; into struct PlayerTag |
− |
| + | <pre> |
− |
| + | struct PlayerTag |
− | <blockquote>switch (variable)
| + | { |
− | <br />
| + | Player *p; |
| + | char name[20]; |
| + | int index; |
| + | int data; |
| + | }; |
| + | </pre> |
| | | |
− | {
| + | In spawn.cpp: |
− | | + | <pre> |
− | <br />
| + | case EVENT_PlayerLeaving: |
| + | { |
| + | Player *p = (Player*)event.p[0]; |
| | | |
− | case n: | + | // killTags(p); // remove so tag not deleted on arena exit |
− | <br />
| |
| | | |
− | { }
| + | // ... |
− |
| + | </pre> |
− | <br />
| |
| | | |
− | break;
| + | Locate in spawn.cpp and modify accordingly: |
− | <br />
| + | <pre> |
| + | int botInfo::get_tag(Player *p, int index) |
| + | { |
| + | _listnode <PlayerTag> *parse = taglist.head; |
| + | PlayerTag *tag; |
| | | |
− |
| + | while (parse) |
− |
| + | { |
− | <br />
| + | tag = parse->item; |
| | | |
− | case m:
| + | // if (tag->p == p) |
− | <br />
| + | if (strcmp(tag->name,p->name)==0) // now tracking by player name, not pointer |
| + | if (tag->index == index) |
| + | return tag->data; |
| | | |
− | { }
| + | parse = parse->next; |
− |
| + | } |
− | <br />
| + | return 0; |
| + | } |
| | | |
− | break;
| + | void botInfo::set_tag(Player *p, int index, int data) |
− | <br />
| + | { |
| + | _listnode <PlayerTag> *parse = taglist.head; |
| + | PlayerTag *tag; |
| | | |
| + | while (parse) |
| + | { |
| + | tag = parse->item; |
| | | |
− | <br />
| + | //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> |
| | | |
− | default:<br />
| + | ===Using structs=== |
| | | |
| + | In '''spawn.h''': |
| + | <pre> |
| + | class botInfo |
| + | { |
| + | struct freqdata |
| + | { |
| + | int kills, deaths; |
| + | }; |
| + | // ... |
| + | }; |
| + | </pre> |
| | | |
− | break;<br />
| + | 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. |
| | | |
− | }<br />
| + | ==Input/Output to files== |
− |
| |
− | <blockquote> <blockquote>// Example:<br />
| |
− | switch (p->ship) {<br />
| |
− |
| |
− | <br />
| |
− | case SHIP_Warbird: {<br />
| |
− | sendPrivate(p,"You're in a warbird");<br />
| |
− | } break;<br />
| |
− |
| |
− | <br />
| |
− | default:<br />
| |
− | break;</blockquote> </blockquote>
| |
| | | |
− | </blockquote>
| + | For reading and/or writing to files with C++ you must have the required include statement as follows: |
− | </blockquote>
| + | <pre> |
| + | #include <fstream> |
| + | using namespace std; |
| + | </pre> |
| | | |
− | <br />
| + | ===File stream input=== |
− | ==Useful Player data== | + | The following example will show you how to read a file, duel.ini, line by line. |
− | <blockquote>
| |
− |
| |
− | // useful data stored
| |
− | by MervBot about each player (player.h)<br /> | |
− |
| |
− | <br />
| |
| | | |
− | p->name = player name stored as char[20]<br />
| + | <pre>#include "stdlib.h" // for atoi()</pre> |
− | p->squad = player squad stored as char[20]<br />
| |
− | p->ship = ship (0-7) enumerated as SHIP_Warbird, SHIP_Spectator, etc..<br />
| |
− | p->safety = if ship is in safety zone (boolean)<br />
| |
− | p->bounty = player bounty<br />
| |
− | p->energy = player energy (have bot with *energy on to get accurate readings)<br />
| |
− | p->flagCount = how many flags player is holding<br />
| |
| | | |
− | p->team = player frequency<br />
| + | <pre> |
− | p->(burst, repel, thor, brick, decoy, rocket, portal) = how many items of that type player has<br />
| + | ifstream file("duel.ini"); |
− | p->(stealth, cloak, xradar, awarp, ufo, flash, safety, shields, supers) = if player has that item on (boolean)<br />
| |
| | | |
− | p->score.killPoints = player kill points<br />
| + | if (!file.good()) // if there was an error opening the file |
− | p->score.flagPoints = player flag points<br />
| + | sendPublic("*arena Error opening file for reading"); // or add your own error handler |
− | p->score.wins = player kills from f2<br />
| + | else |
− | p->score.losses = player deaths from f2<br />
| + | { |
− | </blockquote>
| + | char line[256]; |
− | <br />
| |
− | <br />
| |
− | ==Bot built in functions==
| |
− | <blockquote>// useful MervBot commands to control what the bot is doing<br />
| |
− | <br />
| |
− | // player.cpp<br />
| |
− | Player::move(Sint32 x, Sint32 y) // example me->move(512,512) - bot moves to coord 512 512<br />
| |
− | Player::clone(Player *p) // example me->clone(p) <br />
| |
− | <br />
| |
− | // dllcore.cpp (descriptions of functions in dllcore.h)<br />
| |
− | <br />
| |
− | BotEvent makeEcho (char *m);<br />
| |
− | BotEvent makeSay (int t, int s, int i, char *m);<br />
| |
− | <br />
| |
− | BotEvent makeShip (int s);<br />
| |
− | BotEvent makeTeam (int t);<br />
| |
− | BotEvent makeGrabFlag (int f);<br />
| |
− | BotEvent makeSendPosition (bool reliable);<br />
| |
− | BotEvent makeDropFlags ();<br />
| |
− | <br />
| |
− | BotEvent makeDeath (Player *p);<br />
| |
− | BotEvent makeAttach (Player *p);<br />
| |
− | BotEvent makeDetach ();<br />
| |
− | BotEvent makeFollowing (bool f);<br />
| |
− | BotEvent makeFlying (bool f);<br />
| |
− | BotEvent makeBanner (BYTE *b);<br />
| |
− | BotEvent makeDropBrick ();<br />
| |
− | BotEvent makeFireWeapon (void *weapon_info);<br />
| |
− | <br />
| |
− | BotEvent makeToggleObjects (Uint16 player, Uint16 *objects, int num_objects);<br />
| |
− | <br />
| |
− | BotEvent makeSpawnBot
| |
− | (char *name, char *password, char *staff, char *arena);<br />
| |
− | BotEvent makeChangeArena (char *name);<br />
| |
− | BotEvent makeChangeSettings (_linkedlist <String> *settings);<br />
| |
− | <br />
| |
− | // example how to use them:<br />
| |
− | <br />
| |
− | tell(makeFollowing(false));<br />
| |
− | <br />
| |
− | // look in Commands.txt , command.cpp (core), or /!help to bot to see all bot external commands (example /!go <arena>)<br />
| |
− | </blockquote>
| |
| | | |
− | ==Example Code== | + | // 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; |
| + | } |
| + | } |
| | | |
− | <a name="15a"></a>
| + | file.close(); |
− | a) No antiwarp in center of map (take green away and warn)<br />
| + | } |
− |
| + | </pre> |
− |
| |
− | <blockquote>// using function closeto() and abs()<br /></blockquote> <blockquote>
| |
− | bool closeto(Player *p, int x, int y, int tolerance) {<br />
| |
− | return (abs((p->tile.x) - x) < tolerance) && (abs((p->tile.y) - y) < tolerance); }<br /><br />
| |
− | inline int abs(int n) {<br />
| |
− | if (n < 0) return -n;<br />
| |
− | else return n; }<br /><br />
| |
− | radius = 35; // global variable<br /></blockquote> <blockquote>case EVENT_PlayerMove: {<br />
| |
− | Player *p = (Player*)event.p[0];<br /><br />
| |
− | // no anti in center<br />
| |
− | if ((p->ship != SHIP_Spectator) && (p->awarp)) {<br />
| |
− | if (closeto(p, 512, 512, radius)){<br />
| |
− | sendPrivate(p, "*prize #-20");<br />
| |
− |
| |
− | sendPrivate(p, "*warn Antiwarp is not allowed in center.");<br />
| |
− | }<br />
| |
− | }<br /></blockquote>
| |
− |
| |
− |
| |
− | <a name="15b"></a>
| |
| | | |
− | b) Setting freq size depending on how many pilots in game<br />
| + | ===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 |
| | | |
− | <blockquote>
| + | if (!file.good()) // if there was an error opening the file |
− | case EVENT_Tick: {<br />
| + | sendPublic("*arena Error opening file."); |
− | <blockquote>if (countdown[0] == 0) { // assuming countdown[0] initialized to > 0 in spawn.h, freqchange=0;<br />
| + | else |
− | </blockquote>
| + | { |
− |
| + | file << squad1<< endl; // squad1 = char[20] |
− | _listnode <Player> *parse = playerlist->head;<br />
| + | file << " vs "<< endl; |
− | int count = 0;<br /><br />
| + | file << squad2<< endl; // squad2 = char[20] |
− | while (parse)<br />
| + | file.close(); |
− | {<br />
| + | } |
− | Player *p = parse->item;<br /><br />
| + | </pre> |
− | if (p->ship != SHIP_Spectator)<br />
| |
− | ++count;<br /><br />
| |
− | parse = parse->next;<br />
| |
− | }<br /><br />
| |
− | if ((count > 24) && (freqchange != 4))<br />
| |
− | {<br />
| |
− |
| |
− | sendPublic("?set team:maxperteam:4");<br />
| |
− | String s;<br />
| |
− | s = "Max freq size 4 (";<br />
| |
− | s += count;<br />
| |
− | s += " pilots in game)";<br />
| |
− | sendPublic(s);<br />
| |
− | freqchange = 4;<br />
| |
− | }<br /><br />
| |
− |
| |
− | if ((count < 25) && (count > 14) && (freqchange !=
| |
− | 3))<br />
| |
− | {<br />
| |
− |
| |
− | sendPublic("?set team:maxperteam:3");<br />
| |
− | String s;<br />
| |
− | s = "Max freq size 3 (";<br />
| |
− | s += count;<br />
| |
− | s += " pilots in game)";<br />
| |
− | sendPublic(s);<br />
| |
− | freqchange = 3;<br />
| |
− | }<br /><blockquote>countdown[0] = 120;<br />
| |
− | }<br /></blockquote></blockquote>
| |
− |
| |
− | <a name="15c"></a> c) Tracking kills and announcing when pilot gets 10 kills in a row without
| |
− | dying
| |
− |
| |
− |
| |
− | <blockquote>case EVENT_PlayerDeath: {<br />
| |
− | <blockquote>// assuming tags are setup (see storing data section)<br /> | |
− | set_tag(p,KILLS, 0); // pilot died, reset to 0 kills in a row<br />
| |
− | set_tag(k, KILLS, get_tag(k, KILLS) + 1); // pilot killed someone, increment kills in a row by 1<br /><br />
| |
− | if (get_tag(k,KILLS) == 10) sendPublic("*arena (String) k->name + " has gotten 10 kills.");</blockquote></blockquote>
| |
− |
| |
− |
| |
− | <a name="15d"></a>
| |
| | | |
− | d) Warp pilot to coord when they are in a certain region<br />
| + | Similarly, you are able to write an output of a String to a file: |
− |
| + | <pre> |
− |
| + | // key is converting String to (char*) to file write |
− | <blockquote>// using functions closeto() and abs()<br /><br />
| + | String str = freqs[freq].slotname[slot]; |
− | bool closeto(Player *p, int x, int y, int tolerance) {<br />
| + | str += ", Repels: " + (String)(int) t->repel; |
| + | file << endl; |
| + | file << (char*) str; |
| + | </pre> |
| | | |
− | return (abs((p->tile.x) - x) < tolerance) && (abs((p->tile.y) - y) < tolerance); }<br /><br />
| + | ===Input with GetPrivateProfileString=== |
| | | |
− | inline int abs(int n) {<br />
| + | 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. |
| | | |
− | if (n < 0) return -n;<br />
| + | 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> |
| | | |
− | else return n; }<br /><br />
| + | In '''rampageini.cpp''': |
− | case EVENT_PlayerMove: {<br /><br />
| + | <pre> |
− | if (closeto(p, 509, 509, 2)) { // if pilot within 2 of map coord 509,509<br />
| + | #include "rampageini.h" |
− | sendPrivate(p, "*warpto 509 504"); // warp to coord 509,504<br /></blockquote>
| + | #define WIN32_LEAN_AND_MEAN |
− |
| + | #include <windows.h> |
− |
| |
− | <br />
| |
− |
| |
− | <a name="15e"></a>
| |
| | | |
− | e) Structures within structures (spawn.h botinfo)<br />
| + | #define NUM_RANKS 10 |
− |
| + | #define BUFFER_LEN 256 |
− |
| |
− | <blockquote> // Declare in spawn.h<br /><blockquote> struct playerstats<br />
| |
− | {<br />
| |
− | char name[20];<br /><br />
| |
− | int kills;<br />
| |
− | int deaths;<br />
| |
− | Uint16 points;<br />
| |
− | Uint16 flagpoints;<br />
| |
− | int flagtime;<br />
| |
− | int cflagtime;<br />
| |
− | int flags;<br />
| |
− | int flagkills;<br /><br />
| |
− | int dmgdealt;<br />
| |
− | int dmgtaken;<br /><br />
| |
− | };<br /><br />
| |
− | struct freqdata<br />
| |
− | {<br />
| |
− | playerstats pilots[100];<br /><br />
| |
− | int freqpoints;<br />
| |
− | char freqname[20];<br />
| |
− | int freqflagpoints;<br />
| |
− | Uint16 freqteam;<br />
| |
− | int freqflagtime;<br /><br />
| |
− | int flags;<br />
| |
− | int kills;<br />
| |
− | int deaths;<br />
| |
− | int flagkills;<br /><br />
| |
− | int dmgdealt;<br />
| |
− | int dmgtaken;<br /><br />
| |
− | int playercount;<br />
| |
− | };<br /><br />
| |
− | freqdata freqs[100];<br /></blockquote>
| |
− | // Initialize in spawn.cpp<br /><blockquote>void botInfo::Clear()<br />
| |
− | {<br />
| |
− | // initialize/clear struct data<br />
| |
− | for (int n=99; n>=0; n--)<br />
| |
− | {<br />
| |
− | freqs[n].freqteam=-1;<br />
| |
− | freqs[n].freqpoints=0;<br />
| |
− | freqs[n].freqflagpoints=0;<br />
| |
− | freqs[n].playercount=0;<br />
| |
− | freqs[n].flags=0;<br />
| |
− | freqs[n].kills=0;<br />
| |
− | freqs[n].deaths=0;<br />
| |
− | freqs[n].freqflagtime=0;<br />
| |
− | freqs[n].flagkills=0;<br />
| |
− | freqs[n].dmgdealt=0;<br />
| |
− | freqs[n].dmgtaken=0;<br />
| |
− | <br />
| |
− | for (int m = 99; m>=0; m--)<br />
| |
− | {<br />
| |
− | freqs[n].pilots[m].deaths=0;<br />
| |
− | freqs[n].pilots[m].kills=0;<br />
| |
− | freqs[n].pilots[m].points=0;<br />
| |
− | freqs[n].pilots[m].flagpoints=0;<br />
| |
− | freqs[n].pilots[m].flagtime=0;<br />
| |
− | freqs[n].pilots[m].cflagtime=0;<br />
| |
− | freqs[n].pilots[m].flags=0;<br />
| |
− | freqs[n].pilots[m].flagkills=0;<br />
| |
− | freqs[n].pilots[m].dmgdealt=0;<br />
| |
− | freqs[n].pilots[m].dmgtaken=0;<br />
| |
− | }<br />
| |
− | }<br />
| |
− | }<br /></blockquote></blockquote> <blockquote>// Access data in spawn.cpp<br />
| |
− | <blockquote>int freq = p->team;<br /><br />
| |
− | freqs[1].pilots[2].kills++;<br /><br />
| |
− | OR<br /><br />
| |
− | freqs[freq].deaths++;<br /></blockquote></blockquote>
| |
− |
| |
− |
| |
− | <a name="15f"></a>
| |
| | | |
− | f) Tracking flag data<br />
| + | struct RampageSettings |
− |
| + | { |
− | <blockquote>Example GetPilot() function (using structs from example e)<br /><blockquote>bool botInfo::GetPilot(Player *p)<br />
| + | char quotes[NUM_RANKS][BUFFER_LEN]; |
− | {<br /> | + | }; |
− | // get a pilots freq/pilot id from struct<br />
| + | |
− | for (freq=freqcount-1; freq>=0; freq--)<br />
| + | void LoadSettings(RampageSettings &setts); |
− | if (p->team == freqs[freq].freqteam)<br />
| |
− | for (pilot = freqs[freq].playercount-1; pilot>=0; pilot--)<br />
| |
− | | |
− | if (strcmp(p->name,freqs[freq].pilots[pilot].name)==0)<br />
| |
− | return true;<br /><br />
| |
− | return false;<br />
| |
− | }<br /> | |
− | </blockquote></blockquote>
| |
− |
| |
− | <blockquote>Example way to track flag data using above struct/functions<br /><blockquote>
| |
− | case EVENT_FlagGrab:<br />
| |
− | {<br />
| |
− | if (GetPilot(p)) // function<br />
| |
− | {<br />
| |
− | freqs[freq].pilots[pilot].flags++;<br />
| |
− | freqs[freq].flags++;<br /><br />
| |
− |
| |
− | if (freqs[freq].pilots[pilot].flags < 2) // didnt have a flag before,
| |
− | first flag<br />
| |
− |
| |
− | freqs[freq].pilots[pilot].cflagtime = GetTickCount();
| |
− | // time stamp when picked up flag<br />
| |
− | }<br /></blockquote></blockquote>
| |
− | <blockquote>Example way to track flag data using built in get/set tag (from catid flagbot)<br />
| |
− |
| |
− |
| |
− | <blockquote>
| |
− | case EVENT_FlagGrab:<br />
| |
| | | |
| + | static char path[BUFFER_LEN]; |
| | | |
− | {<br />
| + | char *rank_type[NUM_RANKS] = { "7", "10" }; |
| | | |
| + | void LoadSettings(RampageSettings &setts) |
| + | { |
| + | GetCurrentDirectory(BUFFER_LEN - 64, path); |
| + | strcat(path, "\rampage.ini"); |
| | | |
− | set_tag(p, TAG_STAT_FS, get_tag(p, TAG_STAT_FS) + 1);<br /> | + | for (int i = 0; i < NUM_RANKS; ++i) |
| + | { |
| + | GetPrivateProfileString("Comments", rank_type[i], "-ERROR-", |
| + | setts.quotes[i], BUFFER_LEN, path); |
| + | } |
| + | } |
| + | </pre> |
| | | |
| + | ==Player data== |
| | | |
− | set_tag(p, TAG_FLAGTIMER, GetTickCount());<br />
| + | 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 |
| | | |
− | }<br />
| + | Just access the respective member of the Player class to check the player's property. |
− |
| |
− |
| |
− | </blockquote> </blockquote> <blockquote>Get current flag times using struct format<br />
| |
− |
| |
− |
| |
− | <blockquote>void botInfo::SetFlagTimes()<br />
| |
− | {<br />
| |
− | // set current flagtime for pilots/freqs<br />
| |
− | _listnode <Player> *parse = playerlist->head;<br />
| |
− | <br />
| |
− | while (parse)<br />
| |
− | {<br />
| |
− | Player *p = parse->item;<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | if (GetPilot(p))<br />
| |
− | if (freqs[freq].pilots[pilot].flags > 0)<br />
| |
− | {<br />
| |
− | if (PilotOnSquad(p))<br />
| |
− |
| |
− | freqs[freq].freqflagtime += (GetTickCount() - freqs[freq].pilots[pilot].cflagtime)/1000;<br />
| |
− |
| |
− |
| |
− | <br />
| |
− |
| |
− | freqs[freq].pilots[pilot].flagtime += (GetTickCount() - freqs[freq].pilots[pilot].cflagtime)/1000;<br />
| |
− |
| |
− | freqs[freq].pilots[pilot].cflagtime = GetTickCount();<br />
| |
− | }<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | parse = parse->next;<br />
| |
− | }<br />
| |
− | }<br />
| |
− |
| |
− | </blockquote>
| |
− | // side note: case EVENT_FlagDrop: {} gets called anytime theres a teamkill<br />
| |
− |
| |
− |
| |
− | </blockquote>
| |
− |
| |
− |
| |
− | <br />
| |
− |
| |
− |
| |
− | <a name="15g"></a>
| |
− | g) Example way to do simple /!spam feature (allowed 1x/60s)<br />
| |
− |
| |
− |
| |
− | <blockquote>declare and initialize variables in spawn.h<br />
| |
− |
| |
− |
| |
− | <blockquote>class botInfo<br />
| |
− | {<br />
| |
− | bool spamready;<br />
| |
− | int SPAM_TIME;<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | public:<br />
| |
− | botInfo(CALL_HANDLE given)<br />
| |
− | {<br />
| |
− | spamready = true;<br />
| |
− | SPAM_TIME = 60;<br />
| |
− |
| |
− |
| |
− | </blockquote>
| |
− | spawn.cpp - mark as spamready=true when 60 seconds up<br />
| |
− |
| |
− |
| |
− | <blockquote>case EVENT_Tick:<br />
| |
− | {<br />
| |
− | if (countdown[0] == 1) {<br />
| |
− | spamready = true; // ready to spam again<br />
| |
− | }<br />
| |
− |
| |
− |
| |
− | </blockquote>
| |
− |
| |
− |
| |
− | <br />
| |
− | command.cpp - handle !spam command<br />
| |
− |
| |
− |
| |
− | <blockquote>case OP_Player:<br />
| |
− | { // Player-level commands<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | else if (c->check("spam"))<br />
| |
− | {<br />
| |
− |
| |
− | // zone announcement "Need pilots to duel in ?go arena -pilotname"<br />
| |
− | if (spamready == true)<br />
| |
− | {<br />
| |
− | String s;<br />
| |
− |
| |
− | s += "*zone Need pilots to duel in ?go ";<br />
| |
− | s += arena;<br />
| |
− | s += " - ";<br />
| |
− | s += p->name;<br />
| |
− | sendPublic(s);<br />
| |
− | <br />
| |
− | spamready=false;<br />
| |
− |
| |
− | countdown[0] = SPAM_TIME * 60; // next spam time limit<br />
| |
− | }<br />
| |
− | else if (countdown[0] < 0)<br />
| |
− | {<br />
| |
− |
| |
− | sendPrivate(p,"Spam ability disabled.");<br />
| |
− | }<br />
| |
− | else <br />
| |
− | {<br />
| |
− | String s;<br />
| |
− | s += SPAM_TIME;<br />
| |
− |
| |
− | s += " Minute timer between announcements. ";<br />
| |
− | s += countdown[0] / 60;<br />
| |
− | s += ":";<br />
| |
− | if (countdown[0] % 60 < 10)<br />
| |
− | s += "0";<br />
| |
− | s += countdown[0] % 60;<br />
| |
− |
| |
− | s += " minutes left before next spam allowed.";<br />
| |
− | sendPrivate(p, s);<br />
| |
− | }<br />
| |
− | }<br />
| |
− |
| |
− |
| |
− | </blockquote> </blockquote>
| |
− |
| |
− |
| |
− | <a name="15h"></a>
| |
− | h) Example of implementing a simple stack to do "next in line for several 'boxes' at once"<br />
| |
− |
| |
− |
| |
− | <blockquote>//spawn.h declare variables<br />
| |
− |
| |
− |
| |
− | <blockquote>class botInfo<br />
| |
− | {<br />
| |
− | Player *next[99][99];<br />
| |
− | int MAX_NEXT;<br />
| |
− | int nextcount[99];<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | public:<br />
| |
− | botInfo(CALL_HANDLE given)<br />
| |
− | {<br />
| |
− | MAX_NEXT = 8; <br />
| |
− |
| |
− |
| |
− | </blockquote>
| |
− | // spawn.cpp MoveUp function<br />
| |
− |
| |
− |
| |
− | <blockquote>void botInfo::MoveUp(int pos, int box)<br />
| |
− | {<br />
| |
− | // moves up the next line for that box and decrement box's nextcount<br />
| |
− | if (nextcount[box] > 0)<br />
| |
− | nextcount[box]--;<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | for (pos = pos; pos < MAX_NEXT - 1; pos++)<br />
| |
− | {<br />
| |
− | next[box][pos] = next[box][pos + 1];<br />
| |
− | }<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | next[box][MAX_NEXT] = 0;<br />
| |
− | }<br />
| |
− |
| |
− | </blockquote>
| |
− | </blockquote>
| |
− |
| |
− |
| |
− | <a name="15i"></a>
| |
− | i) Example of reading any text from a .txt and printing it to pilot line by line<br />
| |
− |
| |
− |
| |
− | <blockquote>#include <fstream><br />
| |
− | using namespace std;<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | case OP_Player:<br />
| |
− | {<br />
| |
− | if (c->check("schedule"))<br />
| |
− | {<br />
| |
− | // read in schedule from schedule.txt<br />
| |
− | ifstream file("schedule.txt");<br />
| |
− | char line[256];<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | while (file.getline(line, 256))<br />
| |
− | {<br />
| |
− | sendPrivate(p, line);<br />
| |
− | }<br />
| |
− | file.close();<br />
| |
− | } <br />
| |
− |
| |
− |
| |
− | </blockquote>
| |
− |
| |
− |
| |
− | <a name="15j"></a>
| |
− | j) Example of printing player stats grid<br />
| |
− |
| |
− |
| |
− | <blockquote>// spawn.cpp (see "structures within structures" example for variable declarations, varibale freqcount = # of freqs)<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | void botInfo::DisplayPlayers()<br />
| |
− | {<br />
| |
− | // Display Match player/freq stats in this format (not aligned b/c of html but aligned in bot)<br />
| |
− | // ---------------------------------------------------<br />
| |
− | // Squad: squad_name_1
| |
− | K D TK DMG DEALT TAKEN<br />
| |
− | // ---------------------------------------------------<br />
| |
− | // Player_1
| |
− | 0 0 0 0
| |
− | 0<br /> // Player_2
| |
− | 0 0 0 0
| |
− | 0<br /> // TOTAL:
| |
− | 0 0 0 0
| |
− | 0<br />
| |
− | // ---------------------------------------------------<br />
| |
− | // Squad: squad_name_2
| |
− | K D TK DMG DEALT TAKEN<br />
| |
| | | |
− | // ---------------------------------------------------<br />
| + | For example, in spawn.cpp, to check whether a player is in a safety zone: |
− | // Player_3
| + | <pre> |
− | 0 0 0 0
| + | EVENT_PlayerMove: |
− | 0<br /> // Player_4
| + | { |
− | 0 0 0 0
| + | Player *p = (Player*)event.p[0]; |
− | 0<br /> // Player_5
| |
− | 0 0 0 0
| |
− | 0<br /> // TOTAL:
| |
− | 0 0 0 0
| |
− | 0<br />
| |
− | // ---------------------------------------------------<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | for (freq=freqcount-1; freq>=0; freq--)<br />
| |
− | {<br />
| |
− | char str[255];<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | sendFreqs("---------------------------------------------------");<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | sprintf(str, "Squad: %-20s K D TK DMG DEALT TAKEN", freqs[freq].freqname);<br />
| |
− | sendFreqs(str);<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | sendFreqs("---------------------------------------------------");<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | for (pilot=freqs[freq].playercount-1; pilot >= 0; pilot--)<br />
| |
− | {<br />
| |
− | sprintf(str, "%-20s
| |
− | %8d %2d %2d %9d %5d", freqs[freq].pilots[pilot].name, freqs[freq].pilots[pilot].kills,<br />
| |
− |
| |
− | freqs[freq].pilots[pilot].deaths, freqs[freq].pilots[pilot].teamkills,
| |
− | freqs[freq].pilots[pilot].dmgdealt,<br />
| |
− |
| |
− | freqs[freq].pilots[pilot].dmgtaken);<br />
| |
− | sendFreqs(str);<br />
| |
− | }<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | sprintf(str, "TOTAL:
| |
− | %2d %2d %2d %9d %5d", freqs[freq].kills, freqs[freq].deaths, <br />
| |
− | freqs[freq].teamkills, freqs[freq].dmgdealt, freqs[freq].dmgtaken);<br />
| |
− | sendFreqs(str);<br />
| |
− | }<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | sendFreqs("---------------------------------------------------");<br />
| |
− | }<br />
| |
− |
| |
− |
| |
− | </blockquote>
| |
− |
| |
− |
| |
− | <a name="15k"></a> k) Example of checking if any pilots are
| |
− | within a region
| |
− |
| |
− |
| |
− | <blockquote>// see GetPilotName(name) function in other example, returns *player as TempPlayer from name<br />
| |
− | // closeto() function from several previous examples<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | bool botInfo::FreqAInBox()<br />
| |
− | {<br />
| |
− | // return true if teamA has a pilot in the box, otherwise false<br />
| |
− | for (int tempplayercount = freqs[0].playercount-1; tempplayercount >= 0; tempplayercount--)<br />
| |
− | if (GetPilotName(freqs[0].pilots[tempplayercount].name))<br />
| |
− | if (closeto(TempPlayer,
| |
− | coordX, coordY, 73) && (TempPlayer->ship != SHIP_Spectator))<br />
| |
− | return true;<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | return false;<br />
| |
− | }<br />
| |
− |
| |
− |
| |
− | </blockquote>
| |
− |
| |
− |
| |
− | <a name="15l"></a>
| |
− | l) Example of functions to get a pilot's struct id info from a name or *player info<br />
| |
− |
| |
− |
| |
− | <blockquote>// see struct examples for variable info<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | // return struct freq/pilot id from *player info<br />
| |
− | bool botInfo::GetPilot(Player *p)<br />
| |
| | | |
− | {<br /> | + | if ( p->safety ) // player is in safe zone. |
| + | { |
| + | // do something. |
| + | } |
| | | |
− | // return freq, pilot of a player p<br />
| + | if ( !p->safety ) // player NOT in safe zone. |
− | | + | { |
− | for (freq=freqcount-1; freq>=0; freq--)<br />
| + | // do something. |
− | | + | } |
− | if (p->team == freqs[freq].freqteam)<br />
| + | } |
− | | + | </pre> |
− | for (pilot = freqs[freq].playercount-1; pilot>=0; pilot--)<br />
| |
− |
| |
− | if (strcmp(p->name,freqs[freq].pilots[pilot].name)==0)<br />
| |
| | | |
− | return true;<br />
| + | === Obtaining a player pointer using name comparison === |
− |
| |
− |
| |
− |
| |
− | <br />
| |
| | | |
− | return false;<br />
| + | 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. |
| | | |
− | }<br />
| + | ''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. |
− |
| |
− |
| |
− | <br />
| |
− | // return *player as TempPlayer info from p->name info<br />bool botInfo::GetPilotName(char *name)<br />
| |
− | {<br />
| |
− | // get pilot from a name, return as TempPlayer<br />
| |
− | _listnode <Player> *parse = playerlist->head;<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | while (parse)<br />
| |
− | {<br />
| |
− | Player *p = parse->item;<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | // convert both to lowercase to compare<br />
| |
− | char pname[20];
| |
− | <br />
| |
− | strncpy(pname,p->name,20);<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | char nname[20];
| |
− | <br />
| |
− | strncpy(nname,name,20);<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | tolower(pname);<br />
| |
− | tolower(nname);<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | if (strcmp(pname,nname)==0)<br />
| |
− | {<br />
| |
− | TempPlayer = p;<br />
| |
− | return true;<br />
| |
− | }<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | parse = parse->next;<br />
| |
− | }<br />
| |
− | return false;<br />
| |
− | }<br />
| |
− | <br />
| |
− | // note: better to implement these functions as passing values by reference instead of using global variables<br />
| |
− | // just easier to not have to be declaring different int freq, int pilot all the time<br />
| |
− |
| |
− |
| |
− |
| |
− | </blockquote>
| |
− |
| |
− |
| |
− | <a name="15m"></a>
| |
− | m) Example of creating a logfile name using date and squad names<br />
| |
− |
| |
− |
| |
− | <blockquote> // create log file name (squadA and squadB external char[20] variables)<br />
| |
− | char u[100];<br />
| |
− | time_t t=time(NULL);<br />
| |
− | tm *tmp = localtime(&t);<br />
| |
− | strftime(u,99,"%y",tmp);<br />
| |
− | logname = "c:\Program Files\Continuum\logs\";<br />
| |
− | logname += u;<br />
| |
− | logname += "y";<br />
| |
− | strftime(u,99,"%m",tmp);<br />
| |
− | logname += u;<br />
| |
− | logname += "m";<br />
| |
− | strftime(u,99,"%d",tmp);<br />
| |
− | logname += u;<br />
| |
− | logname += "d";<br />
| |
− | logname += squadA;<br />
| |
− | logname += " vs ";<br />
| |
− | logname += squadB;<br />
| |
− | strftime(u,99,"%I",tmp);<br />
| |
− | logname += u;<br />
| |
− | logname += "h";<br />
| |
− | strftime(u,99,"%M",tmp);<br />
| |
− | logname += u;<br />
| |
− | logname += "m";<br />
| |
− | logname += ".txt";<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | // example name created: 03y01m27dBLACKDRaGON vs Integral05h08m.txt<br />
| |
− | // format year, month, day, squadA vs squadB, hour, minute<br />
| |
− |
| |
− |
| |
− | </blockquote>
| |
− |
| |
− |
| |
− |
| |
− | <a name="15n"></a>
| |
− | n) Example of sending messages to playing freqs or public and logging depending on status<br />
| |
− |
| |
− |
| |
− |
| |
− | <blockquote>// teamA, teamB, logname global variables<br />
| |
− | void botInfo::sendFreqs(char *msg)<br />
| |
− | {<br />
| |
− | char *mmsg = "*arena";<br />
| |
− | String s = msg;<br />
| |
− | <br />
| |
− | if (teammsgs == false)<br />
| |
− | {<br />
| |
− | s.prepend("*arena ",7);<br />
| |
− | sendPublic(s);<br />
| |
− | }<br />
| |
− | else<br />
| |
− | {<br />
| |
− | sendTeamPrivate(8025,msg);<br />
| |
− | sendTeamPrivate(teamA,msg);<br />
| |
− | sendTeamPrivate(teamB,msg);<br />
| |
− | }<br />
| |
− | if (gameon == true)<br />
| |
− | {<br />
| |
− | ofstream outf(logname, ios::app);<br />
| |
− | outf << msg << endl;<br />
| |
− | outf.close();<br />
| |
− | }<br />
| |
− | }<br />
| |
− |
| |
− |
| |
− | </blockquote>
| |
− |
| |
− |
| |
− | <a name="15o"></a>
| |
− | o) Example of reading in all player/freqs to struct data<br />
| |
− |
| |
− |
| |
− | <blockquote>// see structures within structures example for freqs[] declaration<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | // to get freqs in a game where there are several freqs<br />
| |
− | void botInfo::GetFreqs()<br />
| |
− | {<br />
| |
− | // read pilots into freq struct data from ingame and on playing freqs<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | _listnode <Player> *parse = playerlist->head;<br />
| |
− | <br />
| |
− | while (parse)<br />
| |
− | {<br />
| |
− | Player *p = parse->item;<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | if (p->ship != SHIP_Spectator)<br />
| |
− | if (closeto(p, coordX, coordY, 73))<br />
| |
− | {<br />
| |
− | // look for freq in struct<br />
| |
− | bool foundfreq=false;<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | freq=freqcount-1;<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | while ((freq>=0) && (foundfreq==false))<br />
| |
− | {<br />
| |
− |
| |
− | if (p->team == freqs[freq].freqteam)<br />
| |
− | {<br />
| |
− |
| |
− | foundfreq=true;<br />
| |
− |
| |
− | strncpy(freqs[freq].pilots[freqs[freq].playercount].name,
| |
− | p->name, 20);<br />
| |
− |
| |
− | freqs[freq].playercount++;
| |
− | <br />
| |
− | }<br />
| |
− | freq--;<br />
| |
− | }<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | // didnt find freq in struct so add new freq<br />
| |
− | if (foundfreq == false)<br />
| |
− | {<br />
| |
− | if (manualsquads == false)<br />
| |
− | {<br />
| |
− |
| |
− | strncpy(freqs[freqcount].freqname,
| |
− | p->squad, 20);<br />
| |
− |
| |
− |
| |
− | <br />
| |
− |
| |
− | if (freqcount == 0)<br />
| |
− | {<br />
| |
− |
| |
− | teamA = p->team;<br />
| |
− |
| |
− | strncpy(squadA,
| |
− | p->squad, 20);<br />
| |
− | }<br />
| |
− | else<br />
| |
− | {<br />
| |
− |
| |
− | teamB = p->team;<br />
| |
− |
| |
− | strncpy(squadB,
| |
− | p->squad, 20);<br />
| |
− | }<br />
| |
− | }<br />
| |
− | else<br />
| |
− | {<br />
| |
− |
| |
− | if (p->team == teamA)<br />
| |
− |
| |
− | strncpy(freqs[freqcount].freqname,squadA,20);<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− |
| |
− | else if (p->team == teamB)<br />
| |
− |
| |
− | strncpy(freqs[freqcount].freqname,squadB,20);<br />
| |
− | }<br />
| |
− | <br />
| |
− |
| |
− | freqs[freqcount].freqteam = p->team;<br />
| |
− |
| |
− |
| |
− | <br />
| |
− |
| |
− | strncpy(freqs[freqcount].pilots[0].name, p->name, 20);<br />
| |
− |
| |
− |
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | freqs[freqcount].playercount++;<br />
| |
− | freqcount++;<br />
| |
− | }<br />
| |
− | }<br />
| |
− | parse = parse->next;<br />
| |
− | }<br />
| |
− | }<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | // to get freqs in a game where there are only two teams<br />
| |
− | void botInfo::GetFreqs()<br />
| |
− | {<br />
| |
− | // read pilots into freq struct data from ingame and on playing freqs<br />
| |
− | _listnode <Player> *parse = playerlist->head;<br />
| |
− | <br />
| |
− | while (parse)<br />
| |
− | {<br />
| |
− | Player *p = parse->item;<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | if ((p->ship != SHIP_Spectator)
| |
− | && ((p->team == teamA) || (p->team == teamB)))<br />
| |
− | {<br />
| |
− | // freq 100, team A<br />
| |
− | // set freq<br />
| |
− | freq = 0;<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | if (p->team == teamB)<br />
| |
− | freq = 1;<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | // number of pilots on freq counted so far, starts 0<br />
| |
− | pilot = freqs[freq].playercount;<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | // pilot name<br />
| |
− | strncpy(freqs[freq].pilots[pilot].name, p->name, 20);<br />
| |
− | // time stamp for playing time<br />
| |
− | freqs[freq].pilots[pilot].cplaying_time = GetTickCount();<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | // slot name<br />
| |
− | if (freqs[freq].playercount < NUMBER_PILOTS)<br />
| |
− |
| |
− | strncpy(freqs[freq].slotname[pilot], p->name, 20);<br />
| |
− | <br />
| |
− | // increment freq player count<br />
| |
− | freqs[freq].playercount++;<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | // if freq not already have name, give it player squad name<br />
| |
− | if ((manualsquads == false) && (strlen(p->squad) > 0))<br />
| |
− | strncpy(freqs[freq].freqname, p->squad, 20);<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | // set player ship<br />
| |
− | freqs[freq].pilots[pilot].ship = p->ship + 1;<br />
| |
− | }<br />
| |
− | parse = parse->next;<br />
| |
− | }<br />
| |
− | }<br />
| |
− |
| |
− |
| |
− | </blockquote>
| |
− |
| |
− |
| |
− | <a name="15p"></a>
| |
− | p) Example of finding MVP from struct data (2*kills - deaths formula)<br />
| |
− |
| |
− |
| |
− |
| |
− | <blockquote> int highest=-20;<br />
| |
− | int mvp=0;<br />
| |
− |
| |
− |
| |
− | <br />
| |
− | for (pilot = freqs[mvpteam].playercount-1; pilot >=0; pilot--)<br />
| |
− | {<br />
| |
− | if (((freqs[mvpteam].pilots[pilot].kills
| |
− | * 2) - freqs[mvpteam].pilots[pilot].deaths) > highest)<br />
| |
− | {<br />
| |
− | mvp = pilot;<br />
| |
− | highest = (freqs[mvpteam].pilots[pilot].kills
| |
− | * 2) - freqs[mvpteam].pilots[pilot].deaths;<br />
| |
− | }<br />
| |
− | }<br />
| |
− |
| |
− |
| |
− | </blockquote>
| |
− |
| |
− |
| |
− | <a name="15q"></a>
| |
− | q) Print time stamp of event<br />
| |
− |
| |
− |
| |
− | <blockquote>#include "time.h"<br />
| |
− |
| |
− |
| |
− |
| |
− | <br />
| |
− | char u[100];<br />
| |
− | time_t t=time(NULL);<br />
| |
− | tm *tmp = localtime(&t);<br />
| |
− | strftime(u,99,"%c",tmp);<br />
| |
− | sendPublic("Current date and time: " + (String) u);<br />
| |
− |
| |
− |
| |
− | </blockquote>
| |
− |
| |
− |
| |
− | <a name="15r"></a>
| |
− | r) Simple way to track player bomb/bullet damage stats<br />
| |
− |
| |
− |
| |
− | <blockquote>// spawn.cpp (see data section for how to setup set_tag)<br />
| |
− | // see clientprot.h for weapon information<br />
| |
− | <br />
| |
− | case EVENT_WatchDamage:<br />
| |
− | {<br />
| |
− | if (PLAYING) // if tracking stats<br />
| |
− | {<br />
| |
− |
| |
− | if ((wi.type == PROJ_PBomb) && (p->name != k->name))<br />
| |
− | {<br />
| |
− |
| |
− | set_tag(k, DMG_BOMB_DEALT, get_tag(k, DMG_BOMB_DEALT)
| |
− | + damage);<br />
| |
− |
| |
− | set_tag(k, DMG_TOTAL_DEALT, get_tag(k, DMG_TOTAL_DEALT)
| |
− | + damage);<br />
| |
− |
| |
− | set_tag(p, DMG_BOMB_TAKEN, get_tag(p, DMG_BOMB_TAKEN)
| |
− | + damage);<br />
| |
− |
| |
− | set_tag(p, DMG_TOTAL_TAKEN, get_tag(p, DMG_TOTAL_TAKEN)
| |
− | + damage);<br />
| |
− | }<br />
| |
− | else if (wi.type == PROJ_BBullet)<br />
| |
− | {<br />
| |
− |
| |
− | set_tag(k, DMG_BULLET_DEALT, get_tag(k, DMG_BULLET_DEALT)
| |
− | + damage);<br />
| |
− |
| |
− | set_tag(k, DMG_TOTAL_DEALT, get_tag(k, DMG_TOTAL_DEALT)
| |
− | + damage);<br />
| |
− |
| |
− | set_tag(p, DMG_BULLET_TAKEN, get_tag(k, DMG_BULLET_TAKEN)
| |
− | + damage);<br />
| |
− |
| |
− | set_tag(p, DMG_TOTAL_TAKEN, get_tag(k, DMG_TOTAL_TAKEN)
| |
− | + damage);<br />
| |
− | }<br />
| |
− | }<br />
| |
− |
| |
− |
| |
− |
| |
− | </blockquote>
| |
− |
| |
− |
| |
− | <a name="15s"></a>
| |
− | s) Simple way to print those stats<br />
| |
− |
| |
| | | |
− | <blockquote>
| |
| <pre> | | <pre> |
− | case OP_Moderator:
| + | // return Player* info (or NULL if not found) from p->name info |
| + | Player * botInfo::GetPilot(char *name) |
| { | | { |
− | if (c->check("showstats")) | + | // get pilot from a name, return as TempPlayer |
− | {
| + | _listnode <Player> *parse = playerlist->head; |
− | sendPublic("Showing stats:");
| |
− |
| |
− |
| |
− | _listnode <Player> *parse = playerlist->head; | |
| | | |
| + | //convert search name to lowercase |
| + | char nname[20], pname[20]; |
| + | strncpy(nname, name, 20); |
| + | tolower(nname); |
| | | |
− | while (parse) | + | while (parse) |
− | { | + | { |
− | Player *p = parse->item;
| + | Player *p = parse->item; |
| | | |
− | if (get_tag(p, DMG_TOTAL_DEALT) > 0)
| + | // convert to lowercase to compare |
− | {
| + | strncpy(pname,p->name,20); |
− | char str[256]; | + | tolower(pname); |
− | sprintf(str, "(%-20s Dmg Dealt: Total %0004d, Bomb %0004d, Bullet %0004d Dmg TAKEN: Total %0004d, Bomb %0004d, Bullet %0004d)",
| + | if (strcmp(pname,nname)==0) |
− | p->name, get_tag(p,DMG_TOTAL_DEALT), get_tag(p,DMG_BOMB_DEALT), get_tag(p,DMG_BULLET_DEALT),
| + | return p; |
− | get_tag(p,DMG_TOTAL_TAKEN), get_tag(p,DMG_BOMB_TAKEN), get_tag(p,DMG_BULLET_TAKEN));
| + | |
− | sendPublic(str);
| + | parse = parse->next; |
− | }
| |
− | parse = parse->next;
| |
− | }
| |
| } | | } |
| + | |
| + | return NULL; //player not found |
| + | } |
| </pre> | | </pre> |
− | </blockquote><a name="15t"></a>
| |
− | t) Make bot spectate specific coordinates<br />
| |
| | | |
− | <blockquote>
| + | ==Bot built in functions== |
− | <pre style="font-size: 11px">
| |
− | // make bot spectate the coord 512,600
| |
− | // possible use - capturing weapon packets in a specific region
| |
| | | |
− | tell(makeFollowing(false));
| + | Here are some useful MervBot commands to control what the bot is doing. |
− | tell(makeFlying(true));
| + | |
− | me->move(512 * 16, 600 * 16);
| + | Player.cpp: |
− | tell(makeSendPosition(true));
| + | * Player::move(Sint32 x, Sint32 y) moves a player to the coordinates specified by x and y |
− | </pre>
| + | * Player::clone(Player *p) clones a player into a player class |
− | </blockquote>
| + | |
− | </blockquote></blockquote>
| + | 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]] |
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.
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).
This section describes how to implement player commands into your plugin. Commands are sent to the botInfo::gotCommand function in command.cpp.
Use the CRT function sscanf() to scan the string for the values.
When a player sends !help to the bot, MERVBot calls botInfo::gotHelp() in each plugin loaded.
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().
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
An example of using normal strings to output data/messages.
An example using sprintf to align/space data, where output data will be in this approximate format.
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.
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.
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.
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:
If you're not familiar with prototypes, notice it is similar to that in your spawn.cpp, but without the botInfo::, and a trailing ;.
From time to time you will need to pass an array to a function. An example illustrating this is:
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:
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:
To use, just call the function with the appropriate Ship_Type from the enum in clientprot.h:
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.
Player tags simply tag a player with a number. Define the wanted values in spawn.h at the top:
The following demonstrates how to retrieve the tag values as a command in command.cpp:
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.
For reading and/or writing to files with C++ you must have the required include statement as follows:
The following example will show you how to read a file, duel.ini, line by line.
The following code example will demonstrate how to append to a file, duelleaguestat.inc.
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.
As stated earlier in the tutorial, MervBot stores useful player data internally as Player objects, see player.h for implementation details.
Just access the respective member of the Player class to check the player's property.
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.
Here are some useful MervBot commands to control what the bot is doing.
Look in Commands.txt , command.cpp (core), or /!help to bot to see all bot external commands (example /!go <arena>).