yapb-noob-edition/source/control.cpp

2078 lines
56 KiB
C++
Raw Normal View History

//
// Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd").
// Copyright (c) YaPB Development Team.
//
// This software is licensed under the BSD-style license.
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
// https://yapb.ru/license
//
#include <yapb.h>
ConVar yb_display_menu_text ("yb_display_menu_text", "1");
2019-07-27 17:36:24 +03:00
ConVar yb_password ("yb_password", "", Var::Password);
ConVar yb_password_key ("yb_password_key", "_ybpw");
2019-07-27 17:36:24 +03:00
int BotControl::cmdAddBot () {
enum args { alias = 1, difficulty, personality, team, model, name, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
2019-07-27 17:36:24 +03:00
// this is duplicate error as in main bot creation code, but not to be silent
if (!graph.length () || graph.hasChanged ()) {
2019-07-28 15:47:46 +03:00
ctrl.msg ("There is no graph found or graph is changed. Cannot create bot.");
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
// if team is specified, modify args to set team
2019-07-27 17:36:24 +03:00
if (m_args[alias].find ("_ct", 0) != String::kInvalidIndex) {
m_args.set (team, "2");
}
2019-07-27 17:36:24 +03:00
else if (m_args[alias].find ("_t", 0) != String::kInvalidIndex) {
m_args.set (team, "1");
}
// if highskilled bot is requsted set personality to rusher and maxout difficulty
2019-07-27 17:36:24 +03:00
if (m_args[alias].find ("hs", 0) != String::kInvalidIndex) {
m_args.set (difficulty, "4");
m_args.set (personality, "1");
}
bots.addbot (m_args[name], m_args[difficulty], m_args[personality], m_args[team], m_args[model], true);
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdKickBot () {
enum args { alias = 1, team, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
// if team is specified, kick from specified tram
2019-07-27 17:36:24 +03:00
if (m_args[alias].find ("_ct", 0) != String::kInvalidIndex || getInt (team) == 2 || getStr (team) == "ct") {
bots.kickFromTeam (Team::CT);
}
2019-07-27 17:36:24 +03:00
else if (m_args[alias].find ("_t", 0) != String::kInvalidIndex || getInt (team) == 1 || getStr (team) == "t") {
bots.kickFromTeam (Team::Terrorist);
}
else {
bots.kickRandom ();
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdKickBots () {
enum args { alias = 1, instant, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
// check if we're need to remove bots instantly
auto kickInstant = getStr (instant) == "instant";
// kick the bots
bots.kickEveryone (kickInstant);
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdKillBots () {
enum args { alias = 1, team, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
// if team is specified, kick from specified tram
2019-07-27 17:36:24 +03:00
if (m_args[alias].find ("_ct", 0) != String::kInvalidIndex || getInt (team) == 2 || getStr (team) == "ct") {
bots.killAllBots (Team::CT);
}
2019-07-27 17:36:24 +03:00
else if (m_args[alias].find ("_t", 0) != String::kInvalidIndex || getInt (team) == 1 || getStr (team) == "t") {
bots.killAllBots (Team::Terrorist);
}
else {
bots.killAllBots ();
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdFill () {
enum args { alias = 1, team, count, difficulty, personality, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
if (!hasArg (team)) {
2019-07-27 17:36:24 +03:00
return BotCommandResult::BadFormat;
}
bots.serverFill (getInt (team), hasArg (personality) ? getInt (personality) : -1, hasArg (difficulty) ? getInt (difficulty) : -1, hasArg (count) ? getInt (count) : -1);
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdVote () {
enum args { alias = 1, mapid, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
if (!hasArg (mapid)) {
2019-07-27 17:36:24 +03:00
return BotCommandResult::BadFormat;
}
int mapID = getInt (mapid);
// loop through all players
2019-07-27 17:36:24 +03:00
for (const auto &bot : bots) {
bot->m_voteMap = mapID;
}
msg ("All dead bots will vote for map #%d", mapID);
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdWeaponMode () {
enum args { alias = 1, type, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
if (!hasArg (type)) {
2019-07-27 17:36:24 +03:00
return BotCommandResult::BadFormat;
}
2019-07-27 17:36:24 +03:00
Dictionary <String, int> modes;
2019-07-27 17:36:24 +03:00
modes.push ("kinfe", 1);
modes.push ("pistol", 2);
modes.push ("shotgun", 3);
modes.push ("smg", 4);
modes.push ("rifle", 5);
modes.push ("sniper", 6);
modes.push ("stanard", 7);
auto mode = getStr (type);
// check if selected mode exists
if (!modes.exists (mode)) {
2019-07-27 17:36:24 +03:00
return BotCommandResult::BadFormat;
}
bots.setWeaponMode (modes[mode]);
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdVersion () {
msg ("%s v%s (build %u)", PRODUCT_NAME, PRODUCT_VERSION, util.buildNumber ());
msg (" compiled: %s %s by %s", __DATE__, __TIME__, PRODUCT_GIT_COMMIT_AUTHOR);
msg (" commit: %scommit/%s", PRODUCT_COMMENTS, PRODUCT_GIT_HASH);
msg (" url: %s", PRODUCT_URL);
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeMenu () {
enum args { alias = 1, max };
2019-07-27 17:36:24 +03:00
// graph editor is available only with editor
if (!graph.hasEditor ()) {
msg ("Unable to open graph editor without setting the editor player.");
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeMainPage1);
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdMenu () {
enum args { alias = 1, cmd, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
// reset the current menu
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
if (getStr (cmd) == "cmd" && util.isAlive (m_ent)) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::Commands);
}
else {
2019-07-27 17:36:24 +03:00
showMenu (Menu::Main);
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdList () {
enum args { alias = 1, max };
bots.listBots ();
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNode () {
enum args { root, alias, cmd, cmd2, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
2019-07-27 17:36:24 +03:00
// graph editor supported only with editor
if (game.isDedicated () && !graph.hasEditor () && getStr (cmd) != "acquire_editor") {
msg ("Unable to use graph edit commands without setting graph editor player. Please use \"graph acquire_editor\" to acquire rights for graph editing.");
return BotCommandResult::Handled;
}
// should be moved to class?
2019-07-27 17:36:24 +03:00
static Dictionary <String, BotCmd> commands;
static StringArray descriptions;
// fill only once
if (descriptions.empty ()) {
// separate function
2019-07-27 17:36:24 +03:00
auto pushGraphCmd = [&] (String cmd, String format, String help, Handler handler) -> void {
BotCmd botCmd (cr::move (cmd), cr::move (format), cr::move (help), cr::move (handler));
commands.push (cmd, cr::move (botCmd));
descriptions.push (cmd);
};
2019-07-27 17:36:24 +03:00
// add graph commands
pushGraphCmd ("on", "on [display|auto|noclip|models]", "Enables displaying of graph, nodes, noclip cheat", &BotControl::cmdNodeOn);
pushGraphCmd ("off", "off [display|auto|noclip|models]", "Disables displaying of graph, auto adding nodes, noclip cheat", &BotControl::cmdNodeOff);
pushGraphCmd ("menu", "menu [noarguments]", "Opens and displays bots graph edtior.", &BotControl::cmdNodeMenu);
pushGraphCmd ("add", "add [noarguments]", "Opens and displays graph node add menu.", &BotControl::cmdNodeAdd);
pushGraphCmd ("addbasic", "menu [noarguments]", "Adds basic nodes such as player spawn points, goals and ladders.", &BotControl::cmdNodeAddBasic);
pushGraphCmd ("save", "save [noarguments]", "Save graph file to disk.", &BotControl::cmdNodeSave);
pushGraphCmd ("load", "load [noarguments]", "Load graph file from disk.", &BotControl::cmdNodeLoad);
pushGraphCmd ("erase", "erase [iamsure]", "Erases the graph file from disk.", &BotControl::cmdNodeErase);
pushGraphCmd ("delete", "delete [nearest|index]", "Deletes single graph node from map.", &BotControl::cmdNodeDelete);
pushGraphCmd ("check", "check [noarguments]", "Check if graph working correctly.", &BotControl::cmdNodeCheck);
pushGraphCmd ("cache", "cache [nearest|index]", "Caching node for future use.", &BotControl::cmdNodeCache);
pushGraphCmd ("clean", "clean [all|nearest|index]", "Clean useless path connections from all or single node.", &BotControl::cmdNodeClean);
pushGraphCmd ("setradius", "setradius [radius] [nearest|index]", "Sets the radius for node.", &BotControl::cmdNodeSetRadius);
pushGraphCmd ("flags", "flags [noarguments]", "Open and displays menu for modifying flags for nearest point.", &BotControl::cmdNodeSetFlags);
pushGraphCmd ("teleport", "teleport [index]", "Teleports player to specified node index.", &BotControl::cmdNodeTeleport);
pushGraphCmd ("upload", "upload [id]", "Uploads created graph to graph database.", &BotControl::cmdNodeUpload);
// add path commands
2019-07-27 17:36:24 +03:00
pushGraphCmd ("path_create", "path_create [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdNodePathCreate);
pushGraphCmd ("path_create_in", "path_create_in [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdNodePathCreate);
pushGraphCmd ("path_create_out", "path_create_out [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdNodePathCreate);
pushGraphCmd ("path_create_both", "path_create_both [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdNodePathCreate);
pushGraphCmd ("path_delete", "path_create_both [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdNodePathDelete);
pushGraphCmd ("path_set_autopath", "path_set_autoath [max_distance]", "Opens and displays path creation menu.", &BotControl::cmdNodePathSetAutoDistance);
// remote graph editing stuff
if (game.isDedicated ()) {
2019-07-27 17:36:24 +03:00
pushGraphCmd ("acquire_editor", "acquire_editor [max_distance]", "Acquires rights to edit graph on dedicated server.", &BotControl::cmdNodeAcquireEditor);
pushGraphCmd ("release_editor", "acquire_editor [max_distance]", "Releases graph editing rights.", &BotControl::cmdNodeAcquireEditor);
}
}
if (commands.exists (getStr (cmd))) {
2019-07-27 17:36:24 +03:00
auto item = commands[getStr (cmd)];
2019-07-27 17:36:24 +03:00
// graph have only bad format return status
int status = (this->*item.handler) ();
2019-07-27 17:36:24 +03:00
if (status == BotCommandResult::BadFormat) {
msg ("Incorrect usage of \"%s %s %s\" command. Correct usage is:\n\n\t%s\n\nPlease use correct format.", m_args[root].chars (), m_args[alias].chars (), item.name.chars (), item.format.chars ());
}
}
else {
if (getStr (cmd) == "help" && hasArg (cmd2) && commands.exists (getStr (cmd2))) {
auto &item = commands[getStr (cmd2)];
msg ("Command: \"%s %s %s\"\nFormat: %s\nHelp: %s", m_args[root].chars (), m_args[alias].chars (), item.name.chars (), item.format.chars (), item.help.chars ());
}
else {
for (auto &desc : descriptions) {
auto &item = commands[desc];
msg (" %s - %s", item.name.chars (), item.help.chars ());
}
2019-07-27 17:36:24 +03:00
msg ("Currently Graph Status %s", graph.hasEditFlag (GraphEdit::On) ? "Enabled" : "Disabled");
}
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeOn () {
enum args { alias = 1, cmd, option, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
// enable various features of editor
if (getStr (option) == "empty" || getStr (option) == "display" || getStr (option) == "models") {
2019-07-27 17:36:24 +03:00
graph.setEditFlag (GraphEdit::On);
enableDrawModels (true);
2019-07-27 17:36:24 +03:00
msg ("Graph editor has been enabled.");
}
else if (getStr (option) == "noclip") {
m_ent->v.movetype = MOVETYPE_NOCLIP;
2019-07-27 17:36:24 +03:00
graph.setEditFlag (GraphEdit::On | GraphEdit::Noclip);
enableDrawModels (true);
2019-07-27 17:36:24 +03:00
msg ("Graph editor has been enabled with noclip mode.");
}
else if (getStr (option) == "auto") {
2019-07-27 17:36:24 +03:00
graph.setEditFlag (GraphEdit::On | GraphEdit::Auto);
enableDrawModels (true);
2019-07-27 17:36:24 +03:00
msg ("Graph editor has been enabled with auto add node mode.");
}
2019-07-27 17:36:24 +03:00
if (graph.hasEditFlag (GraphEdit::On)) {
extern ConVar mp_roundtime, mp_freezetime, mp_timelimit;
mp_roundtime.set (9);
mp_freezetime.set (0);
mp_timelimit.set (0);
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeOff () {
enum args { graph_cmd = 1, cmd, option, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
// enable various features of editor
if (getStr (option) == "empty" || getStr (option) == "display") {
2019-07-27 17:36:24 +03:00
graph.clearEditFlag (GraphEdit::On | GraphEdit::Auto | GraphEdit::Noclip);
enableDrawModels (false);
2019-07-27 17:36:24 +03:00
msg ("Graph editor has been disabled.");
}
else if (getStr (option) == "models") {
enableDrawModels (false);
2019-07-27 17:36:24 +03:00
msg ("Graph editor has disabled spawn points highlighting.");
}
else if (getStr (option) == "noclip") {
m_ent->v.movetype = MOVETYPE_WALK;
2019-07-27 17:36:24 +03:00
graph.clearEditFlag (GraphEdit::Noclip);
2019-07-27 17:36:24 +03:00
msg ("Graph editor has disabled noclip mode.");
}
else if (getStr (option) == "auto") {
2019-07-27 17:36:24 +03:00
graph.clearEditFlag (GraphEdit::Auto);
msg ("Graph editor has disabled auto add node mode.");
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeAdd () {
enum args { graph_cmd = 1, cmd, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
2019-07-27 17:36:24 +03:00
// turn graph on
graph.setEditFlag (GraphEdit::On);
// show the menu
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeType);
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeAddBasic () {
enum args { graph_cmd = 1, cmd, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
2019-07-27 17:36:24 +03:00
// turn graph on
graph.setEditFlag (GraphEdit::On);
2019-07-27 17:36:24 +03:00
graph.addBasic ();
msg ("Basic graph nodes was added.");
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeSave () {
enum args { graph_cmd = 1, cmd, option, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
// if no check is set save anyway
2019-07-27 17:36:24 +03:00
if (getStr (option) == "nocheck") {
graph.saveGraphData ();
2019-07-27 17:36:24 +03:00
msg ("All nodes has been saved and written to disk (IGNORING QUALITY CONTROL).");
}
else if (getStr (option) == "old") {
graph.saveOldFormat ();
msg ("All nodes has been saved and written to disk (POD-Bot Format (.pwf)).");
}
else {
2019-07-27 17:36:24 +03:00
if (graph.checkNodes (false)) {
graph.saveGraphData ();
msg ("All nodes has been saved and written to disk.");
}
else {
2019-07-27 17:36:24 +03:00
msg ("Could not save save nodes to disk. Graph check has failed.");
}
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeLoad () {
enum args { graph_cmd = 1, cmd, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
2019-07-27 17:36:24 +03:00
// just save graph on request
if (graph.loadGraphData ()) {
msg ("Graph successfully loaded.");
}
else {
2019-07-27 17:36:24 +03:00
msg ("Could not load Graph. See console...");
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeErase () {
enum args { graph_cmd = 1, cmd, iamsure, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
2019-07-27 17:36:24 +03:00
// prevent accidents when graph are deleted unintentionally
if (getStr (iamsure) == "iamsure") {
2019-07-27 17:36:24 +03:00
graph.eraseFromDisk ();
}
else {
2019-07-27 17:36:24 +03:00
msg ("Please, append \"iamsure\" as parameter to get graph erased from the disk.");
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeDelete () {
enum args { graph_cmd = 1, cmd, nearest, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
2019-07-27 17:36:24 +03:00
// turn graph on
graph.setEditFlag (GraphEdit::On);
// if "neareset" or nothing passed delete neareset, else delete by index
if (getStr (nearest) == "empty" || getStr (nearest) == "nearest") {
2019-07-27 17:36:24 +03:00
graph.erase (kInvalidNodeIndex);
}
else {
int index = getInt (nearest);
// check for existence
2019-07-27 17:36:24 +03:00
if (graph.exists (index)) {
graph.erase (index);
msg ("Node #%d has beed deleted.", index);
}
else {
2019-07-27 17:36:24 +03:00
msg ("Could not delete node #%d.", index);
}
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeCheck () {
enum args { graph_cmd = 1, cmd, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
// check if nodes are ok
2019-07-27 17:36:24 +03:00
if (graph.checkNodes (true)) {
msg ("Graph seems to be OK.");
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeCache () {
enum args { graph_cmd = 1, cmd, nearest, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
2019-07-27 17:36:24 +03:00
// turn graph on
graph.setEditFlag (GraphEdit::On);
// if "neareset" or nothing passed delete neareset, else delete by index
if (getStr (nearest) == "empty" || getStr (nearest) == "nearest") {
2019-07-27 17:36:24 +03:00
graph.cachePoint (kInvalidNodeIndex);
2019-07-27 17:36:24 +03:00
msg ("Nearest node has been put into the memory.");
}
else {
int index = getInt (nearest);
// check for existence
2019-07-27 17:36:24 +03:00
if (graph.exists (index)) {
graph.cachePoint (index);
msg ("Node #%d has been put into the memory.", index);
}
else {
2019-07-27 17:36:24 +03:00
msg ("Could not put node #%d into the memory.", index);
}
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeClean () {
enum args { graph_cmd = 1, cmd, option, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
2019-07-27 17:36:24 +03:00
// turn graph on
graph.setEditFlag (GraphEdit::On);
// if "all" passed clean up all the paths
if (getStr (option) == "all") {
int removed = 0;
2019-07-27 17:36:24 +03:00
for (int i = 0; i < graph.length (); ++i) {
removed += graph.clearConnections (i);
}
2019-07-27 17:36:24 +03:00
msg ("Done. Processed %d nodes. %d useless paths was cleared.", graph.length (), removed);
}
else if (getStr (option) == "empty" || getStr (option) == "nearest") {
2019-07-27 17:36:24 +03:00
int removed = graph.clearConnections (graph.getEditorNeareset ());
2019-07-27 17:36:24 +03:00
msg ("Done. Processed node #%d. %d useless paths was cleared.", graph.getEditorNeareset (), removed);
}
else {
int index = getInt (option);
// check for existence
2019-07-27 17:36:24 +03:00
if (graph.exists (index)) {
int removed = graph.clearConnections (index);
2019-07-27 17:36:24 +03:00
msg ("Done. Processed node #%d. %d useless paths was cleared.", index, removed);
}
else {
2019-07-27 17:36:24 +03:00
msg ("Could not process node #%d clearance.", index);
}
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeSetRadius () {
enum args { graph_cmd = 1, cmd, radius, index, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
// radius is a must
if (!hasArg (radius)) {
2019-07-27 17:36:24 +03:00
return BotCommandResult::BadFormat;
}
2019-07-27 17:36:24 +03:00
int radiusIndex = kInvalidNodeIndex;
if (getStr (index) == "empty" || getStr (index) == "nearest") {
2019-07-27 17:36:24 +03:00
radiusIndex = graph.getEditorNeareset ();
}
else {
radiusIndex = getInt (index);
}
2019-07-27 17:36:24 +03:00
float value = getStr (radius).float_ ();
2019-07-27 17:36:24 +03:00
graph.setRadius (radiusIndex, value);
msg ("Node #%d has been set to radius %.2f.", radiusIndex, value);
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeSetFlags () {
enum args { graph_cmd = 1, cmd, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
2019-07-27 17:36:24 +03:00
// turn graph on
graph.setEditFlag (GraphEdit::On);
//show the flag menu
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeFlag);
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeTeleport () {
enum args { graph_cmd = 1, cmd, teleport_index, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
if (!hasArg (teleport_index)) {
2019-07-27 17:36:24 +03:00
return BotCommandResult::BadFormat;
}
int index = getInt (teleport_index);
// check for existence
2019-07-27 17:36:24 +03:00
if (graph.exists (index)) {
engfuncs.pfnSetOrigin (graph.getEditor (), graph[index].origin);
2019-07-27 17:36:24 +03:00
msg ("You have been teleported to node #%d.", index);
2019-07-27 17:36:24 +03:00
// turn graph on
graph.setEditFlag (GraphEdit::On | GraphEdit::Noclip);
}
else {
2019-07-27 17:36:24 +03:00
msg ("Could not teleport to node #%d.", index);
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodePathCreate () {
enum args { graph_cmd = 1, cmd, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
2019-07-27 17:36:24 +03:00
// turn graph on
graph.setEditFlag (GraphEdit::On);
// choose the direction for path creation
2019-07-27 17:36:24 +03:00
if (m_args[cmd].find ("_both", 0) != String::kInvalidIndex) {
graph.pathCreate (PathConnection::Bidirectional);
}
2019-07-27 17:36:24 +03:00
else if (m_args[cmd].find ("_in", 0) != String::kInvalidIndex) {
graph.pathCreate (PathConnection::Incoming);
}
2019-07-27 17:36:24 +03:00
else if (m_args[cmd].find ("_out", 0) != String::kInvalidIndex) {
graph.pathCreate (PathConnection::Outgoing);
}
else {
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodePath);
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodePathDelete () {
enum args { graph_cmd = 1, cmd, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
2019-07-27 17:36:24 +03:00
// turn graph on
graph.setEditFlag (GraphEdit::On);
// delete the patch
2019-07-27 17:36:24 +03:00
graph.erasePath ();
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodePathSetAutoDistance () {
enum args { graph_cmd = 1, cmd, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
2019-07-27 17:36:24 +03:00
// turn graph on
graph.setEditFlag (GraphEdit::On);
showMenu (Menu::NodeAutoPath);
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeAcquireEditor () {
enum args { graph_cmd = 1, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
if (game.isNullEntity (m_ent)) {
msg ("This command should not be executed from HLDS console.");
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
if (graph.hasEditor ()) {
msg ("Sorry, players \"%s\" already acquired rights to edit graph on this server.", STRING (graph.getEditor ()->v.netname));
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
graph.setEditor (m_ent);
msg ("You're acquired rights to edit graph on this server. You're now able to use graph commands.");
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeReleaseEditor () {
enum args { graph_cmd = 1, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
2019-07-27 17:36:24 +03:00
if (!graph.hasEditor ()) {
msg ("No one is currently has rights to edit. Nothing to release.");
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
graph.setEditor (nullptr);
msg ("Graph editor rights freed. You're now not able to use graph commands.");
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::cmdNodeUpload () {
enum args { graph_cmd = 1, cmd, id, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
if (!hasArg (id)) {
return BotCommandResult::BadFormat;
}
// do not allow to upload bad graph
if (!graph.checkNodes (false)) {
msg ("Sorry, unable to upload graph file that contains errors. Please type \"wp check\" to verify graph consistency.");
return BotCommandResult::BadFormat;
}
msg ("\n");
msg ("WARNING!");
msg ("Graph uploaded to graph database in synchronous mode. That means if graph is big enough");
msg ("you may notice the game freezes a bit during upload and issue request creation. Please, be patient.");
msg ("\n");
// six seconds is enough?
http.setTimeout (6);
extern ConVar yb_graph_url;
// try to upload the file
if (http.uploadFile (strings.format ("%s/", yb_graph_url.str ()), strings.format ("%sgraph/%s.graph", graph.getDataDirectory (false), game.getMapName ()))) {
msg ("Graph file was uploaded and Issue Request has been created for review.");
msg ("As soon as database administrator review your upload request, your graph will");
msg ("be available to download for all YaPB users. You can check your issue request at:");
msg ("URL: https://github.com/jeefo/yapb-graph/issues");
msg ("\n");
msg ("Thank you.");
msg ("\n");
}
else {
String status;
auto code = http.getLastStatusCode ();
if (code == HttpClientResult::Forbidden) {
status = "AlreadyExists";
}
else if (code == HttpClientResult::NotFound) {
status = "AccessDenied";
}
else {
status.assignf ("%d", code);
}
msg ("Something went wrong with uploading. Come back later. (%s)", status.chars ());
msg ("\n");
if (code == HttpClientResult::Forbidden) {
msg ("You should create issue-request manually for this graph");
msg ("as it's already exists in database, can't overwrite. Sorry...");
}
else {
msg ("There is an internal error, or somethingis totally wrong with");
msg ("your files, and they are not passed sanity checks. Sorry...");
}
msg ("\n");
}
return BotCommandResult::Handled;
}
int BotControl::menuMain (int item) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
m_isMenuFillCommand = false;
2019-07-27 17:36:24 +03:00
showMenu (Menu::Control);
break;
case 2:
2019-07-27 17:36:24 +03:00
showMenu (Menu::Features);
break;
case 3:
m_isMenuFillCommand = true;
2019-07-27 17:36:24 +03:00
showMenu (Menu::TeamSelect);
break;
case 4:
bots.killAllBots ();
break;
case 10:
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
break;
default:
2019-07-27 17:36:24 +03:00
showMenu (Menu::Main);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuFeatures (int item) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
2019-07-27 17:36:24 +03:00
showMenu (Menu::WeaponMode);
break;
case 2:
2019-07-27 17:36:24 +03:00
showMenu (graph.hasEditor () ? Menu::NodeMainPage1 : Menu::Features);
break;
case 3:
2019-07-27 17:36:24 +03:00
showMenu (Menu::Personality);
break;
case 4:
extern ConVar yb_debug;
2019-07-27 17:36:24 +03:00
yb_debug.set (yb_debug.int_ () ^ 1);
2019-07-27 17:36:24 +03:00
showMenu (Menu::Features);
break;
case 5:
if (util.isAlive (m_ent)) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::Commands);
}
else {
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
msg ("You're dead, and have no access to this menu");
}
break;
case 10:
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuControl (int item) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
bots.createRandom (true);
2019-07-27 17:36:24 +03:00
showMenu (Menu::Control);
break;
case 2:
2019-07-27 17:36:24 +03:00
showMenu (Menu::Difficulty);
break;
case 3:
bots.kickRandom ();
2019-07-27 17:36:24 +03:00
showMenu (Menu::Control);
break;
case 4:
bots.kickEveryone ();
break;
case 5:
kickBotByMenu (1);
break;
case 10:
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuWeaponMode (int item) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
bots.setWeaponMode (item);
2019-07-27 17:36:24 +03:00
showMenu (Menu::WeaponMode);
break;
case 10:
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuPersonality (int item) {
if (m_isMenuFillCommand) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
case 2:
case 3:
case 4:
bots.serverFill (m_menuServerFillTeam, item - 2, m_interMenuData[0]);
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
break;
case 10:
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
case 2:
case 3:
case 4:
m_interMenuData[3] = item - 2;
2019-07-27 17:36:24 +03:00
showMenu (Menu::TeamSelect);
break;
case 10:
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuDifficulty (int item) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
m_interMenuData[0] = 0;
break;
case 2:
m_interMenuData[0] = 1;
break;
case 3:
m_interMenuData[0] = 2;
break;
case 4:
m_interMenuData[0] = 3;
break;
case 5:
m_interMenuData[0] = 4;
break;
case 10:
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
break;
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::Personality);
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuTeamSelect (int item) {
if (m_isMenuFillCommand) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
if (item < 3) {
extern ConVar mp_limitteams, mp_autoteambalance;
// turn off cvars if specified team
mp_limitteams.set (0);
mp_autoteambalance.set (0);
}
switch (item) {
case 1:
case 2:
case 5:
m_menuServerFillTeam = item;
2019-07-27 17:36:24 +03:00
showMenu (Menu::Difficulty);
break;
case 10:
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
case 2:
case 5:
m_interMenuData[1] = item;
if (item == 5) {
m_interMenuData[2] = item;
bots.addbot ("", m_interMenuData[0], m_interMenuData[3], m_interMenuData[1], m_interMenuData[2], true);
}
else {
2019-07-27 17:36:24 +03:00
showMenu (item == 1 ? Menu::TerroristSelect : Menu::CTSelect);
}
break;
case 10:
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuClassSelect (int item) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
case 2:
case 3:
case 4:
case 5:
m_interMenuData[2] = item;
bots.addbot ("", m_interMenuData[0], m_interMenuData[3], m_interMenuData[1], m_interMenuData[2], true);
break;
case 10:
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuCommands (int item) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
Bot *bot = nullptr;
switch (item) {
case 1:
case 2:
if (util.findNearestPlayer (reinterpret_cast <void **> (&bot), m_ent, 600.0f, true, true, true) && bot->m_hasC4 && !bot->hasHostage ()) {
if (item == 1) {
bot->startDoubleJump (m_ent);
}
else {
bot->resetDoubleJump ();
}
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::Commands);
break;
case 3:
case 4:
if (util.findNearestPlayer (reinterpret_cast <void **> (&bot), m_ent, 600.0f, true, true, true, true, item == 4 ? false : true)) {
bot->dropWeaponForUser (m_ent, item == 4 ? false : true);
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::Commands);
break;
case 10:
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::menuGraphPage1 (int item) {
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
2019-07-27 17:36:24 +03:00
if (graph.hasEditFlag (GraphEdit::On)) {
graph.clearEditFlag (GraphEdit::On);
enableDrawModels (false);
2019-07-27 17:36:24 +03:00
msg ("Graph editor has been disabled.");
}
else {
2019-07-27 17:36:24 +03:00
graph.setEditFlag (GraphEdit::On);
enableDrawModels (true);
2019-07-27 17:36:24 +03:00
msg ("Graph editor has been enabled.");
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeMainPage1);
break;
case 2:
2019-07-27 17:36:24 +03:00
graph.setEditFlag (GraphEdit::On);
graph.cachePoint (kInvalidNodeIndex);
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeMainPage1);
break;
case 3:
2019-07-27 17:36:24 +03:00
graph.setEditFlag (GraphEdit::On);
showMenu (Menu::NodePath);
break;
case 4:
2019-07-27 17:36:24 +03:00
graph.setEditFlag (GraphEdit::On);
graph.erasePath ();
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeMainPage1);
break;
case 5:
2019-07-27 17:36:24 +03:00
graph.setEditFlag (GraphEdit::On);
showMenu (Menu::NodeType);
break;
case 6:
2019-07-27 17:36:24 +03:00
graph.setEditFlag (GraphEdit::On);
graph.erase (kInvalidNodeIndex);
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeMainPage1);
break;
case 7:
2019-07-27 17:36:24 +03:00
graph.setEditFlag (GraphEdit::On);
showMenu (Menu::NodeAutoPath);
break;
case 8:
2019-07-27 17:36:24 +03:00
graph.setEditFlag (GraphEdit::On);
showMenu (Menu::NodeRadius);
break;
case 9:
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeMainPage2);
break;
case 10:
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::menuGraphPage2 (int item) {
showMenu (Menu::None); // reset menu display
switch (item) {
case 1: {
int terrPoints = 0;
int ctPoints = 0;
int goalPoints = 0;
int rescuePoints = 0;
int campPoints = 0;
int sniperPoints = 0;
int noHostagePoints = 0;
2019-07-27 17:36:24 +03:00
for (int i = 0; i < graph.length (); ++i) {
Path &path = graph[i];
2019-07-27 17:36:24 +03:00
if (path.flags & NodeFlag::TerroristOnly) {
++terrPoints;
}
2019-07-27 17:36:24 +03:00
if (path.flags & NodeFlag::CTOnly) {
++ctPoints;
}
2019-07-27 17:36:24 +03:00
if (path.flags & NodeFlag::Goal) {
++goalPoints;
}
2019-07-27 17:36:24 +03:00
if (path.flags & NodeFlag::Rescue) {
++rescuePoints;
}
2019-07-27 17:36:24 +03:00
if (path.flags & NodeFlag::Camp) {
++campPoints;
}
2019-07-27 17:36:24 +03:00
if (path.flags & NodeFlag::Sniper) {
++sniperPoints;
}
2019-07-27 17:36:24 +03:00
if (path.flags & NodeFlag::NoHostage) {
++noHostagePoints;
}
}
2019-07-27 17:36:24 +03:00
msg ("Nodes: %d - T Points: %d\n"
"CT Points: %d - Goal Points: %d\n"
"Rescue Points: %d - Camp Points: %d\n"
"Block Hostage Points: %d - Sniper Points: %d\n",
2019-07-27 17:36:24 +03:00
graph.length (), terrPoints, ctPoints, goalPoints, rescuePoints, campPoints, noHostagePoints, sniperPoints);
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeMainPage2);
} break;
case 2:
2019-07-27 17:36:24 +03:00
graph.setEditFlag (GraphEdit::On);
2019-07-27 17:36:24 +03:00
if (graph.hasEditFlag (GraphEdit::Auto)) {
graph.clearEditFlag (GraphEdit::Auto);
}
else {
2019-07-27 17:36:24 +03:00
graph.setEditFlag (GraphEdit::Auto);
}
2019-07-27 17:36:24 +03:00
msg ("Auto-Add-Nodes %s", graph.hasEditFlag (GraphEdit::Auto) ? "Enabled" : "Disabled");
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeMainPage2);
break;
case 3:
2019-07-27 17:36:24 +03:00
graph.setEditFlag (GraphEdit::On);
showMenu (Menu::NodeFlag);
break;
case 4:
2019-07-27 17:36:24 +03:00
if (graph.checkNodes (true)) {
graph.saveGraphData ();
}
else {
2019-07-27 17:36:24 +03:00
msg ("Graph not saved\nThere are errors. See console...");
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeMainPage2);
break;
case 5:
2019-07-27 17:36:24 +03:00
graph.saveGraphData ();
showMenu (Menu::NodeMainPage2);
break;
case 6:
2019-07-27 17:36:24 +03:00
graph.loadGraphData ();
showMenu (Menu::NodeMainPage2);
break;
case 7:
2019-07-27 17:36:24 +03:00
if (graph.checkNodes (true)) {
msg ("Nodes works fine");
}
else {
msg ("There are errors, see console");
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeMainPage2);
break;
case 8:
2019-07-27 17:36:24 +03:00
graph.setEditFlag (GraphEdit::On | GraphEdit::Noclip);
showMenu (Menu::NodeMainPage2);
break;
case 9:
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeMainPage1);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::menuGraphRadius (int item) {
showMenu (Menu::None); // reset menu display
graph.setEditFlag (GraphEdit::On); // turn graph on in case
constexpr float radius[] = { 0.0f, 8.0f, 16.0f, 32.0f, 48.0f, 64.0f, 80.0f, 96.0f, 128.0f };
if (item >= 1 && item <= 9) {
2019-07-27 17:36:24 +03:00
graph.setRadius (kInvalidNodeIndex, radius[item - 1]);
showMenu (Menu::NodeRadius);
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::menuGraphType (int item) {
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
2019-07-27 17:36:24 +03:00
graph.add (item - 1);
showMenu (Menu::NodeType);
break;
case 8:
2019-07-27 17:36:24 +03:00
graph.add (100);
showMenu (Menu::NodeType);
break;
case 9:
2019-07-27 17:36:24 +03:00
graph.startLearnJump ();
showMenu (Menu::NodeType);
break;
case 10:
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::menuGraphFlag (int item) {
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
2019-07-27 17:36:24 +03:00
graph.toggleFlags (NodeFlag::NoHostage);
showMenu (Menu::NodeFlag);
break;
case 2:
2019-07-27 17:36:24 +03:00
graph.toggleFlags (NodeFlag::TerroristOnly);
showMenu (Menu::NodeFlag);
break;
case 3:
2019-07-27 17:36:24 +03:00
graph.toggleFlags (NodeFlag::CTOnly);
showMenu (Menu::NodeFlag);
break;
case 4:
2019-07-27 17:36:24 +03:00
graph.toggleFlags (NodeFlag::Lift);
showMenu (Menu::NodeFlag);
break;
case 5:
2019-07-27 17:36:24 +03:00
graph.toggleFlags (NodeFlag::Sniper);
showMenu (Menu::NodeFlag);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuAutoPathDistance (int item) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
constexpr float distances[] = { 0.0f, 100.0f, 130.0f, 160.0f, 190.0f, 220.0f, 250.0f };
float result = 0.0f;
if (item >= 1 && item <= 7) {
result = distances[item - 1];
2019-07-27 17:36:24 +03:00
graph.setAutoPathDistance (result);
}
if (cr::fzero (result)) {
msg ("Autopathing is now disabled.");
}
else {
msg ("Autopath distance is set to %.2f.", result);
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeAutoPath);
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuKickPage1 (int item) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
bots.kickBot (item - 1);
kickBotByMenu (1);
break;
case 9:
kickBotByMenu (2);
break;
case 10:
2019-07-27 17:36:24 +03:00
showMenu (Menu::Control);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuKickPage2 (int item) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
bots.kickBot (item + 8 - 1);
kickBotByMenu (2);
break;
case 9:
kickBotByMenu (3);
break;
case 10:
kickBotByMenu (1);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuKickPage3 (int item) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
bots.kickBot (item + 16 - 1);
kickBotByMenu (3);
break;
case 9:
kickBotByMenu (4);
break;
case 10:
kickBotByMenu (2);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuKickPage4 (int item) {
2019-07-27 17:36:24 +03:00
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
bots.kickBot (item + 24 - 1);
kickBotByMenu (4);
break;
case 10:
kickBotByMenu (3);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::menuGraphPath (int item) {
showMenu (Menu::None); // reset menu display
switch (item) {
case 1:
2019-07-27 17:36:24 +03:00
graph.pathCreate (PathConnection::Outgoing);
showMenu (Menu::NodePath);
break;
case 2:
2019-07-27 17:36:24 +03:00
graph.pathCreate (PathConnection::Incoming);
showMenu (Menu::NodePath);
break;
case 3:
2019-07-27 17:36:24 +03:00
graph.pathCreate (PathConnection::Bidirectional);
showMenu (Menu::NodePath);
break;
case 10:
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
bool BotControl::executeCommands () {
if (m_args.empty ()) {
return false;
}
// handle only "yb" and "yapb" commands
if (m_args[0] != "yb" && m_args[0] != "yapb") {
return false;
}
2019-07-27 17:36:24 +03:00
Client &client = util.getClient (game.indexOfPlayer (m_ent));
// do not allow to execute stuff for non admins
2019-07-27 17:36:24 +03:00
if (m_ent != game.getLocalEntity () && !(client.flags & ClientFlags::Admin)) {
msg ("Access to %s commands is restricted.", PRODUCT_SHORT_NAME);
return true;
}
auto aliasMatch = [] (String &test, const String &cmd, String &aliasName) -> bool {
2019-07-27 17:36:24 +03:00
for (auto &alias : test.split ("/")) {
if (alias == cmd) {
aliasName = alias;
return true;
}
}
return false;
};
String cmd;
// give some help
2019-07-27 17:36:24 +03:00
if (m_args.length () > 0 && m_args[1] == "help") {
for (auto &item : m_cmds) {
if (aliasMatch (item.name, m_args[2], cmd)) {
msg ("Command: \"%s %s\"\nFormat: %s\nHelp: %s", m_args[0].chars (), cmd.chars (), item.format.chars (), item.help.chars ());
String aliases;
2019-07-27 17:36:24 +03:00
for (auto &alias : item.name.split ("/")) {
aliases.appendf ("%s, ", alias.chars ());
}
aliases.rtrim (", ");
msg ("Aliases: %s", aliases.chars ());
return true;
}
}
if (m_args[2].empty ()) {
return true;
}
else {
msg ("No help found for \"%s\"", m_args[2].chars ());
}
return true;
}
cmd.clear ();
// if no args passed just print all the commands
if (m_args.length () == 1) {
msg ("usage %s <command> [arguments]", m_args[0].chars ());
msg ("valid commands are: ");
for (auto &item : m_cmds) {
2019-07-27 17:36:24 +03:00
msg (" %s - %s", item.name.split ("/")[0].chars (), item.help.chars ());
}
return true;
}
// first search for a actual cmd
for (auto &item : m_cmds) {
auto root = m_args[0].chars ();
if (aliasMatch (item.name, m_args[1], cmd)) {
auto alias = cmd.chars ();
switch ((this->*item.handler) ()) {
2019-07-27 17:36:24 +03:00
case BotCommandResult::Handled:
default:
break;
2019-07-27 17:36:24 +03:00
case BotCommandResult::ListenServer:
msg ("Command \"%s %s\" is only available from the listenserver console.", root, alias);
break;
2019-07-27 17:36:24 +03:00
case BotCommandResult::BadFormat:
msg ("Incorrect usage of \"%s %s\" command. Correct usage is:\n\n\t%s\n\nPlease type \"%s help %s\" to get more information.", root, alias, item.format.chars (), root, alias);
break;
}
m_isFromConsole = false;
return true;
}
}
msg ("Unrecognized command: %s", m_args[1].chars ());
return true;
}
2019-07-27 17:36:24 +03:00
bool BotControl::executeMenus () {
if (!util.isPlayer (m_ent) || game.isBotCmd ()) {
return false;
}
2019-07-27 17:36:24 +03:00
auto &issuer = util.getClient (game.indexOfPlayer (m_ent));
// check if it's menu select, and some key pressed
2019-07-27 17:36:24 +03:00
if (getStr (0) != "menuselect" || getStr (1).empty () || issuer.menu == Menu::None) {
return false;
}
// let's get handle
for (auto &menu : m_menus) {
if (menu.ident == issuer.menu) {
2019-07-27 17:36:24 +03:00
return (this->*menu.handler) (getStr (1).int_ ());
}
}
return false;
}
void BotControl::showMenu (int id) {
static bool s_menusParsed = false;
// make menus looks like we need only once
if (!s_menusParsed) {
for (auto &parsed : m_menus) {
const String &translated = conf.translate (parsed.text.chars ());
// translate all the things
parsed.text = translated;
2019-07-27 17:36:24 +03:00
// make menu looks best
if (!(game.is (GameFlags::Legacy))) {
for (int j = 0; j < 10; ++j) {
parsed.text.replace (strings.format ("%d.", j), strings.format ("\\r%d.\\w", j));
}
}
}
s_menusParsed = true;
}
if (!util.isPlayer (m_ent)) {
return;
}
2019-07-27 17:36:24 +03:00
Client &client = util.getClient (game.indexOfPlayer (m_ent));
2019-07-27 17:36:24 +03:00
if (id == Menu::None) {
MessageWriter (MSG_ONE_UNRELIABLE, game.getMessageId (NetMsg::ShowMenu), nullvec, m_ent)
.writeShort (0)
.writeChar (0)
.writeByte (0)
.writeString ("");
client.menu = id;
return;
}
for (auto &display : m_menus) {
if (display.ident == id) {
2019-07-28 23:21:24 +03:00
auto text = (game.is (GameFlags::Xash3D | GameFlags::Mobility) && !yb_display_menu_text.bool_ ()) ? " " : display.text.chars ();
MessageWriter msg;
while (strlen (text) >= 64) {
2019-07-27 17:36:24 +03:00
msg.start (MSG_ONE_UNRELIABLE, game.getMessageId (NetMsg::ShowMenu), nullvec, m_ent)
.writeShort (display.slots)
.writeChar (-1)
.writeByte (1);
2019-07-27 17:36:24 +03:00
for (int i = 0; i < 64; ++i) {
msg.writeChar (text[i]);
}
msg.end ();
text += 64;
}
2019-07-27 17:36:24 +03:00
MessageWriter (MSG_ONE_UNRELIABLE, game.getMessageId (NetMsg::ShowMenu), nullvec, m_ent)
.writeShort (display.slots)
.writeChar (-1)
.writeByte (0)
.writeString (text);
client.menu = id;
engfuncs.pfnClientCommand (m_ent, "speak \"player/geiger1\"\n"); // Stops others from hearing menu sounds..
}
}
}
void BotControl::kickBotByMenu (int page) {
if (page > 4 || page < 1) {
return;
}
String menus;
2019-07-27 17:36:24 +03:00
menus.assignf ("\\yBots Remove Menu (%d/4):\\w\n\n", page);
int menuKeys = (page == 4) ? cr::bit (9) : (cr::bit (8) | cr::bit (9));
int menuKey = (page - 1) * 8;
2019-07-27 17:36:24 +03:00
for (int i = menuKey; i < page * 8; ++i) {
auto bot = bots[i];
if (bot != nullptr) {
menuKeys |= cr::bit (cr::abs (i - menuKey));
2019-07-27 17:36:24 +03:00
menus.appendf ("%1.1d. %s%s\n", i - menuKey + 1, STRING (bot->pev->netname), bot->m_team == Team::CT ? " \\y(CT)\\w" : " \\r(T)\\w");
}
else {
2019-07-27 17:36:24 +03:00
menus.appendf ("\\d %1.1d. Not a Bot\\w\n", i - menuKey + 1);
}
}
2019-07-27 17:36:24 +03:00
menus.appendf ("\n%s 0. Back", (page == 4) ? "" : " 9. More...\n");
// force to clear current menu
2019-07-27 17:36:24 +03:00
showMenu (Menu::None);
2019-07-27 17:36:24 +03:00
auto id = Menu::KickPage1 - 1 + page;
for (auto &menu : m_menus) {
if (menu.ident == id) {
2019-07-27 17:36:24 +03:00
menu.slots = menuKeys & static_cast <uint32> (-1);
menu.text = menus;
break;
}
}
showMenu (id);
}
void BotControl::assignAdminRights (edict_t *ent, char *infobuffer) {
if (!game.isDedicated () || util.isFakeClient (ent)) {
return;
}
const String &key = yb_password_key.str ();
const String &password = yb_password.str ();
if (!key.empty () && !password.empty ()) {
2019-07-27 17:36:24 +03:00
auto &client = util.getClient (game.indexOfPlayer (ent));
if (password == engfuncs.pfnInfoKeyValue (infobuffer, key.chars ())) {
2019-07-27 17:36:24 +03:00
client.flags |= ClientFlags::Admin;
}
else {
2019-07-27 17:36:24 +03:00
client.flags &= ~ClientFlags::Admin;
}
}
}
2019-07-27 17:36:24 +03:00
void BotControl::maintainAdminRights () {
if (!game.isDedicated ()) {
return;
}
2019-07-27 17:36:24 +03:00
for (int i = 0; i < game.maxClients (); ++i) {
edict_t *player = game.playerOfIndex (i);
// code below is executed only on dedicated server
if (util.isPlayer (player) && !util.isFakeClient (player)) {
Client &client = util.getClient (i);
2019-07-27 17:36:24 +03:00
if (client.flags & ClientFlags::Admin) {
if (util.isEmptyStr (yb_password_key.str ()) && util.isEmptyStr (yb_password.str ())) {
2019-07-27 17:36:24 +03:00
client.flags &= ~ClientFlags::Admin;
}
else if (!!strcmp (yb_password.str (), engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (client.ent), const_cast <char *> (yb_password_key.str ())))) {
2019-07-27 17:36:24 +03:00
client.flags &= ~ClientFlags::Admin;
game.print ("Player %s had lost remote access to %s.", STRING (player->v.netname), PRODUCT_SHORT_NAME);
}
}
2019-07-27 17:36:24 +03:00
else if (!(client.flags & ClientFlags::Admin) && !util.isEmptyStr (yb_password_key.str ()) && !util.isEmptyStr (yb_password.str ())) {
if (strcmp (yb_password.str (), engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (client.ent), const_cast <char *> (yb_password_key.str ()))) == 0) {
2019-07-27 17:36:24 +03:00
client.flags |= ClientFlags::Admin;
game.print ("Player %s had gained full remote access to %s.", STRING (player->v.netname), PRODUCT_SHORT_NAME);
}
}
}
}
}
2019-07-27 17:36:24 +03:00
BotControl::BotControl () {
m_ent = nullptr;
m_isFromConsole = false;
m_isMenuFillCommand = false;
2019-07-27 17:36:24 +03:00
m_rapidOutput = false;
m_menuServerFillTeam = 5;
2019-07-27 17:36:24 +03:00
m_cmds.emplace ("add/addbot/add_ct/addbot_ct/add_t/addbot_t/addhs/addhs_t/addhs_ct", "add [difficulty[personality[team[model[name]]]]]", "Adding specific bot into the game.", &BotControl::cmdAddBot);
m_cmds.emplace ("kick/kickone/kick_ct/kick_t/kickbot_ct/kickbot_t", "kick [team]", "Kicks off the random bot from the game.", &BotControl::cmdKickBot);
m_cmds.emplace ("removebots/kickbots/kickall", "removebots [instant]", "Kicks all the bots from the game.", &BotControl::cmdKickBots);
m_cmds.emplace ("kill/killbots/killall/kill_ct/kill_t", "kill [team]", "Kills the specified team / all the bots.", &BotControl::cmdKillBots);
m_cmds.emplace ("fill/fillserver", "fill [team[count[difficulty[pesonality]]]]", "Fill the server (add bots) with specified parameters.", &BotControl::cmdFill);
m_cmds.emplace ("vote/votemap", "vote [map_id]", "Forces all the bot to vote to specified map.", &BotControl::cmdVote);
m_cmds.emplace ("weapons/weaponmode", "weapons [knife|pistol|shotgun|smg|rifle|sniper|standard]", "Sets the bots weapon mode to use", &BotControl::cmdWeaponMode);
m_cmds.emplace ("menu/botmenu", "menu [cmd]", "Opens the main bot menu, or command menu if specified.", &BotControl::cmdMenu);
m_cmds.emplace ("version/ver/about", "version [no arguments]", "Displays version information about bot build.", &BotControl::cmdVersion);
m_cmds.emplace ("graphmenu/wpmenu/wptmenu", "graphmenu [noarguments]", "Opens and displays bots graph edtior.", &BotControl::cmdNodeMenu);
m_cmds.emplace ("list/listbots", "list [noarguments]", "Lists the bots currently playing on server.", &BotControl::cmdList);
m_cmds.emplace ("graph/wp/wpt/waypoint", "graph [help]", "Handles graph operations.", &BotControl::cmdNode);
// declare the menus
createMenus ();
}
2019-07-27 17:36:24 +03:00
void BotControl::handleEngineCommands () {
ctrl.collectArgs ();
ctrl.setIssuer (game.getLocalEntity ());
ctrl.setFromConsole (true);
ctrl.executeCommands ();
}
bool BotControl::handleClientCommands (edict_t *ent) {
2019-07-27 17:36:24 +03:00
ctrl.collectArgs ();
setIssuer (ent);
setFromConsole (true);
return executeCommands ();
}
bool BotControl::handleMenuCommands (edict_t *ent) {
2019-07-27 17:36:24 +03:00
ctrl.collectArgs ();
setIssuer (ent);
setFromConsole (false);
return ctrl.executeMenus ();
}
void BotControl::enableDrawModels (bool enable) {
StringArray entities;
entities.push ("info_player_start");
entities.push ("info_player_deathmatch");
entities.push ("info_vip_start");
for (auto &entity : entities) {
edict_t *ent = nullptr;
while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", entity.chars ()))) {
if (enable) {
ent->v.effects &= ~EF_NODRAW;
}
else {
ent->v.effects |= EF_NODRAW;
}
}
}
}
2019-07-27 17:36:24 +03:00
void BotControl::createMenus () {
auto keys = [] (int numKeys) -> int {
int result = 0;
2019-07-27 17:36:24 +03:00
for (int i = 0; i < numKeys; ++i) {
result |= cr::bit (i);
}
result |= cr::bit (9);
return result;
};
// bots main menu
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::Main, keys (4),
"\\yMain Menu\\w\n\n"
"1. Control Bots\n"
"2. Features\n\n"
"3. Fill Server\n"
"4. End Round\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuMain);
// bots features menu
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::Features, keys (5),
"\\yBots Features\\w\n\n"
"1. Weapon Mode Menu\n"
"2. Waypoint Menu\n"
"3. Select Personality\n\n"
"4. Toggle Debug Mode\n"
"5. Command Menu\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuFeatures);
// bot control menu
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::Control, keys (5),
"\\yBots Control Menu\\w\n\n"
"1. Add a Bot, Quick\n"
"2. Add a Bot, Specified\n\n"
"3. Remove Random Bot\n"
"4. Remove All Bots\n\n"
"5. Remove Bot Menu\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuControl);
// weapon mode select menu
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::WeaponMode, keys (7),
"\\yBots Weapon Mode\\w\n\n"
"1. Knives only\n"
"2. Pistols only\n"
"3. Shotguns only\n"
"4. Machine Guns only\n"
"5. Rifles only\n"
"6. Sniper Weapons only\n"
"7. All Weapons\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuWeaponMode);
// personality select menu
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::Personality, keys (4),
"\\yBots Personality\\w\n\n"
"1. Random\n"
"2. Normal\n"
"3. Aggressive\n"
"4. Careful\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuPersonality);
// difficulty select menu
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::Difficulty, keys (5),
"\\yBots Difficulty Level\\w\n\n"
"1. Newbie\n"
"2. Average\n"
"3. Normal\n"
"4. Professional\n"
"5. Godlike\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuDifficulty);
// team select menu
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::TeamSelect, keys (5),
"\\ySelect a team\\w\n\n"
"1. Terrorist Force\n"
"2. Counter-Terrorist Force\n\n"
"5. Auto-select\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuTeamSelect);
// terrorist model select menu
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::TerroristSelect, keys (5),
"\\ySelect an appearance\\w\n\n"
"1. Phoenix Connexion\n"
"2. L337 Krew\n"
"3. Arctic Avengers\n"
"4. Guerilla Warfare\n\n"
"5. Auto-select\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuClassSelect);
// counter-terrorist model select menu
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::CTSelect, keys (5),
"\\ySelect an appearance\\w\n\n"
"1. Seal Team 6 (DEVGRU)\n"
"2. German GSG-9\n"
"3. UK SAS\n"
"4. French GIGN\n\n"
"5. Auto-select\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuClassSelect);
// command menu
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::Commands, keys (4),
"\\yBot Command Menu\\w\n\n"
"1. Make Double Jump\n"
"2. Finish Double Jump\n\n"
"3. Drop the C4 Bomb\n"
"4. Drop the Weapon\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuCommands);
// main waypoint menu
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::NodeMainPage1, keys (9),
"\\yWaypoint Operations (Page 1)\\w\n\n"
"1. Show/Hide waypoints\n"
"2. Cache waypoint\n"
"3. Create path\n"
"4. Delete path\n"
"5. Add waypoint\n"
"6. Delete waypoint\n"
"7. Set Autopath Distance\n"
"8. Set Radius\n\n"
"9. Next...\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuGraphPage1);
// main waypoint menu (page 2)
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::NodeMainPage2, keys (9),
"\\yWaypoint Operations (Page 2)\\w\n\n"
"1. Waypoint stats\n"
"2. Autowaypoint on/off\n"
"3. Set flags\n"
"4. Save waypoints\n"
"5. Save without checking\n"
"6. Load waypoints\n"
"7. Check waypoints\n"
"8. Noclip cheat on/off\n\n"
"9. Previous...\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuGraphPage2);
// select waypoint radius menu
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::NodeRadius, keys (9),
"\\yWaypoint Radius\\w\n\n"
"1. SetRadius 0\n"
"2. SetRadius 8\n"
"3. SetRadius 16\n"
"4. SetRadius 32\n"
"5. SetRadius 48\n"
"6. SetRadius 64\n"
"7. SetRadius 80\n"
"8. SetRadius 96\n"
"9. SetRadius 128\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuGraphRadius);
// waypoint add menu
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::NodeType, keys (9),
"\\yWaypoint Type\\w\n\n"
"1. Normal\n"
"\\r2. Terrorist Important\n"
"3. Counter-Terrorist Important\n"
"\\w4. Block with hostage / Ladder\n"
"\\y5. Rescue Zone\n"
"\\w6. Camping\n"
"7. Camp End\n"
"\\r8. Map Goal\n"
"\\w9. Jump\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuGraphType);
// set waypoint flag menu
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::NodeFlag, keys (5),
"\\yToggle Waypoint Flags\\w\n\n"
"1. Block with Hostage\n"
"2. Terrorists Specific\n"
"3. CTs Specific\n"
"4. Use Elevator\n"
"5. Sniper Point (\\yFor Camp Points Only!\\w)\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuGraphFlag);
// auto-path max distance
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::NodeAutoPath, keys (7),
"\\yAutoPath Distance\\w\n\n"
"1. Distance 0\n"
"2. Distance 100\n"
"3. Distance 130\n"
"4. Distance 160\n"
"5. Distance 190\n"
"6. Distance 220\n"
"7. Distance 250 (Default)\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuAutoPathDistance);
// path connections
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::NodePath, keys (3),
"\\yCreate Path (Choose Direction)\\w\n\n"
"1. Outgoing Path\n"
"2. Incoming Path\n"
"3. Bidirectional (Both Ways)\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuGraphPath);
// kick menus
2019-07-27 17:36:24 +03:00
m_menus.emplace (Menu::KickPage1, 0x0, "", &BotControl::menuKickPage1);
m_menus.emplace (Menu::KickPage2, 0x0, "", &BotControl::menuKickPage2);
m_menus.emplace (Menu::KickPage3, 0x0, "", &BotControl::menuKickPage3);
m_menus.emplace (Menu::KickPage4, 0x0, "", &BotControl::menuKickPage4);
}