yapb-noob-edition/src/control.cpp

2473 lines
67 KiB
C++
Raw Normal View History

//
// YaPB, based on PODBot by Markus Klinge ("CountFloyd").
// Copyright © YaPB Project Developers <yapb@jeefo.net>.
//
// SPDX-License-Identifier: MIT
//
#include <yapb.h>
ConVar cv_display_menu_text ("display_menu_text", "1", "Enables or disables display menu text, when players asks for menu. Useful only for Android.", true, 0.0f, 1.0f, Var::Xash3D);
ConVar cv_password ("password", "", "The value (password) for the setinfo key, if user sets correct password, he's gains access to bot commands and menus.", false, 0.0f, 0.0f, Var::Password);
ConVar cv_password_key ("password_key", "_ybpw", "The name of setinfo key used to store password to bot commands and menus.", false);
2019-07-27 17:36:24 +03:00
int BotControl::cmdAddBot () {
enum args { alias = 1, difficulty, personality, team, model, name, 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;
}
2020-06-12 18:52:38 +03:00
// give a chance to use additional args
m_args.resize (max);
// if team is specified, modify args to set team
aim: verify camp angles from nav data before using them aim: tweaked a bit grenade handling, so bots should use them more aim: reduce time between selecting grenade and throwing it away aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar aim: use direct enemy origin from visibility check, and not re-calculate it aim: update enemy prediction, so it now depends on frame interval for a bot aim: additional height offset are tweaked, and now used only for difficulty 4 nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain nav: do not check banned nodes, when bucket sizes re too low nav: cover nodes are now selected depending on total bots on server nav: let bot enter pause task after long jump nav: extend velocity by a little for a jump, like it was in first versions of bot nav: stuck checking is now taken in account lower minimal speed if bot is ducking fix: navigation reachability timers, so bots will have correct current node index while camping fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable fix: cover nodes are now calculated as they should fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard bot: minor refactoring and moving functions to correct source file bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight bot: camp buttons are now lightly depends on bot health chat: welcome chat message from bots is now sent during first freeze time period crlib: switch over to stdint.h and remove crlib-own types crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
if (strValue (alias).endsWith ("_ct")) {
m_args.set (team, "2");
}
aim: verify camp angles from nav data before using them aim: tweaked a bit grenade handling, so bots should use them more aim: reduce time between selecting grenade and throwing it away aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar aim: use direct enemy origin from visibility check, and not re-calculate it aim: update enemy prediction, so it now depends on frame interval for a bot aim: additional height offset are tweaked, and now used only for difficulty 4 nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain nav: do not check banned nodes, when bucket sizes re too low nav: cover nodes are now selected depending on total bots on server nav: let bot enter pause task after long jump nav: extend velocity by a little for a jump, like it was in first versions of bot nav: stuck checking is now taken in account lower minimal speed if bot is ducking fix: navigation reachability timers, so bots will have correct current node index while camping fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable fix: cover nodes are now calculated as they should fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard bot: minor refactoring and moving functions to correct source file bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight bot: camp buttons are now lightly depends on bot health chat: welcome chat message from bots is now sent during first freeze time period crlib: switch over to stdint.h and remove crlib-own types crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
else if (strValue (alias).endsWith ("_t")) {
m_args.set (team, "1");
}
// if high-skilled bot is requested set personality to rusher and max-out difficulty
aim: verify camp angles from nav data before using them aim: tweaked a bit grenade handling, so bots should use them more aim: reduce time between selecting grenade and throwing it away aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar aim: use direct enemy origin from visibility check, and not re-calculate it aim: update enemy prediction, so it now depends on frame interval for a bot aim: additional height offset are tweaked, and now used only for difficulty 4 nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain nav: do not check banned nodes, when bucket sizes re too low nav: cover nodes are now selected depending on total bots on server nav: let bot enter pause task after long jump nav: extend velocity by a little for a jump, like it was in first versions of bot nav: stuck checking is now taken in account lower minimal speed if bot is ducking fix: navigation reachability timers, so bots will have correct current node index while camping fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable fix: cover nodes are now calculated as they should fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard bot: minor refactoring and moving functions to correct source file bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight bot: camp buttons are now lightly depends on bot health chat: welcome chat message from bots is now sent during first freeze time period crlib: switch over to stdint.h and remove crlib-own types crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
if (strValue (alias).endsWith ("_hs")) {
m_args.set (difficulty, "4");
m_args.set (personality, "1");
}
2020-06-12 18:52:38 +03:00
bots.addbot (strValue (name), strValue (difficulty), strValue (personality), strValue (team), strValue (model), true);
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdKickBot () {
2020-06-12 18:52:38 +03:00
enum args { alias = 1, team };
// if team is specified, kick from specified tram
if (strValue (alias).endsWith ("_ct") || intValue (team) == 2 || strValue (team) == "ct") {
2019-07-27 17:36:24 +03:00
bots.kickFromTeam (Team::CT);
}
else if (strValue (alias).endsWith ("_t") || intValue (team) == 1 || strValue (team) == "t") {
2019-07-27 17:36:24 +03:00
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, team };
// check if we're need to remove bots instantly
const auto kickInstant = strValue (instant) == "instant";
// if team is specified, kick from specified tram
if (strValue (alias).endsWith ("_ct") || intValue (team) == 2 || strValue (team) == "ct") {
bots.kickFromTeam (Team::CT, true);
}
else if (strValue (alias).endsWith ("_t") || intValue (team) == 1 || strValue (team) == "t") {
bots.kickFromTeam (Team::Terrorist, true);
}
else {
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 };
// if team is specified, kick from specified tram
if (strValue (alias).endsWith ("_ct") || intValue (team) == 2 || strValue (team) == "ct") {
2019-07-27 17:36:24 +03:00
bots.killAllBots (Team::CT);
}
else if (strValue (alias).endsWith ("_t") || intValue (team) == 1 || strValue (team) == "t") {
2019-07-27 17:36:24 +03:00
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 () {
2020-06-12 18:52:38 +03:00
enum args { alias = 1, team, count, difficulty, personality };
if (!hasArg (team)) {
2019-07-27 17:36:24 +03:00
return BotCommandResult::BadFormat;
}
bots.serverFill (intValue (team), hasArg (personality) ? intValue (personality) : -1, hasArg (difficulty) ? intValue (difficulty) : -1, hasArg (count) ? intValue (count) - 1 : -1);
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdVote () {
2020-06-12 18:52:38 +03:00
enum args { alias = 1, mapid };
if (!hasArg (mapid)) {
2019-07-27 17:36:24 +03:00
return BotCommandResult::BadFormat;
}
const int mapID = intValue (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 () {
2020-06-12 18:52:38 +03:00
enum args { alias = 1, type };
if (!hasArg (type)) {
2019-07-27 17:36:24 +03:00
return BotCommandResult::BadFormat;
}
static HashMap <String, int> modes {
{ "knife", 1 },
{ "pistol", 2 },
{ "shotgun", 3 },
{ "smg", 4 },
{ "rifle", 5 },
{ "sniper", 6 },
{ "standard", 7 }
};
2020-06-12 18:52:38 +03:00
auto mode = strValue (type);
// check if selected mode exists
2023-04-15 04:10:09 +03:00
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 () {
const auto &build = product.build;
2020-10-07 10:25:31 +03:00
msg ("%s v%s (ID %s)", product.name, product.version, build.id);
2020-06-12 18:52:38 +03:00
msg (" by %s (%s)", product.author, product.email);
msg (" %s", product.url);
msg ("compiled: %s on %s with %s", product.dtime, build.machine, build.compiler);
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeMenu () {
2020-06-12 18:52:38 +03:00
enum args { alias = 1 };
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 () {
2020-06-12 18:52:38 +03:00
enum args { alias = 1, cmd };
// reset the current menu
closeMenu ();
2020-06-12 18:52:38 +03:00
if (strValue (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 () {
2020-06-12 18:52:38 +03:00
enum args { alias = 1 };
bots.listBots ();
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::cmdCvars () {
2020-06-12 18:52:38 +03:00
enum args { alias = 1, pattern };
2020-06-12 18:52:38 +03:00
const auto &match = strValue (pattern);
const bool isSaveMain = match == "save";
const bool isSaveMap = match == "save_map";
const bool isSave = isSaveMain || isSaveMap;
File cfg;
// if save requested, dump cvars to main config
if (isSave) {
auto cfgPath = strings.joinPath (game.getRunningModName (), folders.addons, folders.bot, folders.config, strings.format ("%s.%s", product.nameLower, kConfigExtension));
if (isSaveMap) {
cfgPath = strings.joinPath (game.getRunningModName (), folders.addons, folders.bot, folders.config, "maps", strings.format ("%s.%s", game.getMapName (), kConfigExtension));
}
cfg.open (cfgPath, "wt");
2020-06-12 18:52:38 +03:00
cfg.puts ("// Configuration file for %s\n\n", product.name);
}
else {
ctrl.setRapidOutput (true);
}
for (const auto &cvar : game.getCvars ()) {
if (cvar.info.empty () || !cvar.self || !cvar.self->ptr) {
continue;
}
2020-06-12 18:52:38 +03:00
if (!isSave && !match.empty () && !strstr (cvar.reg.name, match.chars ())) {
continue;
}
// prevent crash if file not accessible
if (isSave && !cfg) {
continue;
}
auto val = cvar.self->str ();
// float value ?
bool isFloat = !val.empty () && val.find (".") != StringRef::InvalidIndex;
if (isSave) {
cfg.puts ("//\n");
2020-06-12 18:52:38 +03:00
cfg.puts ("// %s\n", String::join (cvar.info.split ("\n"), "\n// "));
cfg.puts ("// ---\n");
if (cvar.bounded) {
if (isFloat) {
cfg.puts ("// Default: \"%.1f\", Min: \"%.1f\", Max: \"%.1f\"\n", cvar.initial, cvar.min, cvar.max);
}
else {
cfg.puts ("// Default: \"%i\", Min: \"%i\", Max: \"%i\"\n", static_cast <int> (cvar.initial), static_cast <int> (cvar.min), static_cast <int> (cvar.max));
}
}
else {
cfg.puts ("// Default: \"%s\"\n", cvar.self->str ());
}
cfg.puts ("// \n");
if (cvar.bounded) {
if (isFloat) {
cfg.puts ("%s \"%.1f\"\n", cvar.reg.name, cvar.self->float_ ());
}
else {
cfg.puts ("%s \"%i\"\n", cvar.reg.name, cvar.self->int_ ());
}
}
else {
cfg.puts ("%s \"%s\"\n", cvar.reg.name, cvar.self->str ());
}
cfg.puts ("\n");
}
else {
msg ("name: %s", cvar.reg.name);
msg ("info: %s", conf.translate (cvar.info));
msg (" ");
}
}
ctrl.setRapidOutput (false);
if (isSave) {
if (!cfg) {
msg ("Unable to write cvars to config file. File not accessible");
return BotCommandResult::Handled;
}
msg ("Bots cvars has been written to file.");
cfg.close ();
}
return BotCommandResult::Handled;
}
int BotControl::cmdShowCustom () {
enum args { alias = 1 };
conf.showCustomValues ();
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNode () {
2020-06-12 18:52:38 +03:00
enum args { root, alias, cmd, cmd2 };
static Array <StringRef> allowedOnDedicatedServer {
"acquire_editor",
"upload",
"save",
2020-08-31 14:30:09 +03:00
"load",
"help",
"erase",
"fileinfo"
};
// check if cmd is allowed on dedicated server
auto isAllowedOnDedicatedServer = [] (StringRef str) -> bool {
for (const auto &test : allowedOnDedicatedServer) {
if (test == str) {
return true;
}
}
return false;
};
2019-07-27 17:36:24 +03:00
// graph editor supported only with editor
if (game.isDedicated () && !graph.hasEditor () && !isAllowedOnDedicatedServer (strValue (cmd))) {
2019-07-27 17:36:24 +03:00
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?
static HashMap <String, BotCmd> commands;
2019-07-27 17:36:24 +03:00
static StringArray descriptions;
// fill only once
if (descriptions.empty ()) {
// separate function
2020-06-12 18:52:38 +03:00
auto addGraphCmd = [&] (String cmd, String format, String help, Handler handler) -> void {
BotCmd botCmd { cmd, cr::move (format), cr::move (help), cr::move (handler) };
2019-07-27 17:36:24 +03:00
commands[cmd] = cr::move (botCmd);
descriptions.push (cmd);
};
2019-07-27 17:36:24 +03:00
// add graph commands
2020-06-12 18:52:38 +03:00
addGraphCmd ("on", "on [display|auto|noclip|models]", "Enables displaying of graph, nodes, noclip cheat", &BotControl::cmdNodeOn);
addGraphCmd ("off", "off [display|auto|noclip|models]", "Disables displaying of graph, auto adding nodes, noclip cheat", &BotControl::cmdNodeOff);
2020-08-24 13:49:54 +06:00
addGraphCmd ("menu", "menu [noarguments]", "Opens and displays bots graph editor.", &BotControl::cmdNodeMenu);
2020-06-12 18:52:38 +03:00
addGraphCmd ("add", "add [noarguments]", "Opens and displays graph node add menu.", &BotControl::cmdNodeAdd);
addGraphCmd ("addbasic", "menu [noarguments]", "Adds basic nodes such as player spawn points, goals and ladders.", &BotControl::cmdNodeAddBasic);
addGraphCmd ("save", "save [noarguments]", "Save graph file to disk.", &BotControl::cmdNodeSave);
addGraphCmd ("load", "load [noarguments]", "Load graph file from disk.", &BotControl::cmdNodeLoad);
addGraphCmd ("erase", "erase [iamsure]", "Erases the graph file from disk.", &BotControl::cmdNodeErase);
addGraphCmd ("delete", "delete [nearest|index]", "Deletes single graph node from map.", &BotControl::cmdNodeDelete);
addGraphCmd ("check", "check [noarguments]", "Check if graph working correctly.", &BotControl::cmdNodeCheck);
addGraphCmd ("cache", "cache [nearest|index]", "Caching node for future use.", &BotControl::cmdNodeCache);
addGraphCmd ("clean", "clean [all|nearest|index]", "Clean useless path connections from all or single node.", &BotControl::cmdNodeClean);
addGraphCmd ("setradius", "setradius [radius] [nearest|index]", "Sets the radius for node.", &BotControl::cmdNodeSetRadius);
addGraphCmd ("flags", "flags [noarguments]", "Open and displays menu for modifying flags for nearest point.", &BotControl::cmdNodeSetFlags);
addGraphCmd ("teleport", "teleport [index]", "Teleports player to specified node index.", &BotControl::cmdNodeTeleport);
2020-10-08 15:25:19 +03:00
addGraphCmd ("upload", "upload", "Uploads created graph to graph database.", &BotControl::cmdNodeUpload);
addGraphCmd ("stats", "stats [noarguments]", "Shows the stats about node types on the map.", &BotControl::cmdNodeShowStats);
addGraphCmd ("fileinfo", "fileinfo [noarguments]", "Shows basic information about graph file.", &BotControl::cmdNodeFileInfo);
addGraphCmd ("adjust_height", "adjust_height [height offset]", "Modifies all the graph nodes height (z-component) with specified offset.", &BotControl::cmdAdjustHeight);
// add path commands
2020-06-12 18:52:38 +03:00
addGraphCmd ("path_create", "path_create [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdNodePathCreate);
addGraphCmd ("path_create_in", "path_create_in [noarguments]", "Creates incoming path connection from faced to nearest node.", &BotControl::cmdNodePathCreate);
addGraphCmd ("path_create_out", "path_create_out [noarguments]", "Creates outgoing path connection from nearest to faced node.", &BotControl::cmdNodePathCreate);
addGraphCmd ("path_create_both", "path_create_both [noarguments]", "Creates both-ways path connection between faced and nearest node.", &BotControl::cmdNodePathCreate);
addGraphCmd ("path_create_jump", "path_create_jump [noarguments]", "Creates jumping path connection from nearest to faced node.", &BotControl::cmdNodePathCreate);
addGraphCmd ("path_delete", "path_delete [noarguments]", "Deletes path from nearest to faced node.", &BotControl::cmdNodePathDelete);
2020-08-24 12:56:26 +06:00
addGraphCmd ("path_set_autopath", "path_set_autopath [max_distance]", "Opens menu for setting autopath maximum distance.", &BotControl::cmdNodePathSetAutoDistance);
addGraphCmd ("path_clean", "path_clean [index]", "Clean's up all types of connections from the node.", &BotControl::cmdNodePathCleanAll);
2019-07-27 17:36:24 +03:00
// camp points iterator
2020-06-12 18:52:38 +03:00
addGraphCmd ("iterate_camp", "iterate_camp [begin|end|next]", "Allows to go through all camp points on map.", &BotControl::cmdNodeIterateCamp);
2019-07-27 17:36:24 +03:00
// remote graph editing stuff
if (game.isDedicated ()) {
addGraphCmd ("acquire_editor", "acquire_editor [noarguments]", "Acquires rights to edit graph on dedicated server.", &BotControl::cmdNodeAcquireEditor);
2020-10-08 15:25:19 +03:00
addGraphCmd ("release_editor", "release_editor [noarguments]", "Releases graph editing rights.", &BotControl::cmdNodeReleaseEditor);
}
}
2023-04-15 04:10:09 +03:00
if (commands.exists (strValue (cmd))) {
const auto &item = commands[strValue (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:", m_args[root], m_args[alias], item.name);
msg ("\n\t%s\n", item.format);
msg ("Please use correct format.");
}
}
else {
2023-04-15 04:10:09 +03:00
if (strValue (cmd) == "help" && hasArg (cmd2) && commands.exists (strValue (cmd2))) {
2020-06-12 18:52:38 +03:00
auto &item = commands[strValue (cmd2)];
msg ("Command: \"%s %s %s\"", m_args[root], m_args[alias], item.name);
msg ("Format: %s", item.format);
msg ("Help: %s", conf.translate (item.help));
}
else {
for (auto &desc : descriptions) {
auto &item = commands[desc];
msg (" %s - %s", item.name, conf.translate (item.help));
}
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 () {
2020-06-12 18:52:38 +03:00
enum args { alias = 1, cmd, option };
// enable various features of editor
2020-06-12 18:52:38 +03:00
if (strValue (option).empty () || strValue (option) == "display" || strValue (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.");
}
2020-06-12 18:52:38 +03:00
else if (strValue (option) == "noclip") {
m_ent->v.movetype = MOVETYPE_NOCLIP;
if (graph.hasEditFlag (GraphEdit::On)) {
graph.setEditFlag (GraphEdit::Noclip);
msg ("Noclip mode enabled.");
}
else {
graph.setEditFlag (GraphEdit::On | GraphEdit::Noclip);
enableDrawModels (true);
msg ("Graph editor has been enabled with noclip mode.");
}
}
2020-06-12 18:52:38 +03:00
else if (strValue (option) == "auto") {
if (graph.hasEditFlag (GraphEdit::On)) {
graph.setEditFlag (GraphEdit::Auto);
msg ("Enabled auto nodes placement.");
}
else {
graph.setEditFlag (GraphEdit::On | GraphEdit::Auto);
enableDrawModels (true);
msg ("Graph editor has been enabled with auto add node mode.");
}
}
2019-07-27 17:36:24 +03:00
if (graph.hasEditFlag (GraphEdit::On)) {
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 () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd, option };
// enable various features of editor
2020-06-12 18:52:38 +03:00
if (strValue (option).empty () || strValue (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.");
}
2020-06-12 18:52:38 +03:00
else if (strValue (option) == "models") {
enableDrawModels (false);
2019-07-27 17:36:24 +03:00
msg ("Graph editor has disabled spawn points highlighting.");
}
2020-06-12 18:52:38 +03:00
else if (strValue (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.");
}
2020-06-12 18:52:38 +03:00
else if (strValue (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 () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd };
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 () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd };
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 () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd, option };
// if no check is set save anyway
2020-06-12 18:52:38 +03:00
if (strValue (option) == "nocheck") {
2019-07-27 17:36:24 +03:00
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 (strValue (option) == "old" || strValue (option) == "oldformat") {
if (graph.length () >= 1024) {
msg ("Unable to save POD-Bot Format waypoint file. Number of nodes exceeds 1024.");
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
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.\n*** Please don't forget to share your work by typing \"%s g upload\". Thank you! ***", product.cmdPri);
}
else {
msg ("Could not 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 () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd };
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 () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd, iamsure };
2019-07-27 17:36:24 +03:00
// prevent accidents when graph are deleted unintentionally
2020-06-12 18:52:38 +03:00
if (strValue (iamsure) == "iamsure") {
bstor.unlinkFromDisk ();
}
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 () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd, nearest };
2019-07-27 17:36:24 +03:00
// turn graph on
graph.setEditFlag (GraphEdit::On);
// if "nearest" or nothing passed delete nearest, else delete by index
2020-06-12 18:52:38 +03:00
if (strValue (nearest).empty () || strValue (nearest) == "nearest") {
2019-07-27 17:36:24 +03:00
graph.erase (kInvalidNodeIndex);
}
else {
2020-06-12 18:52:38 +03:00
int index = intValue (nearest);
// check for existence
2019-07-27 17:36:24 +03:00
if (graph.exists (index)) {
graph.erase (index);
2020-08-24 12:56:26 +06:00
msg ("Node %d has been deleted.", index);
}
else {
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 () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd };
// 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 () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd, nearest };
2019-07-27 17:36:24 +03:00
// turn graph on
graph.setEditFlag (GraphEdit::On);
// if "nearest" or nothing passed delete nearest, else delete by index
2020-06-12 18:52:38 +03:00
if (strValue (nearest).empty () || strValue (nearest) == "nearest") {
2019-07-27 17:36:24 +03:00
graph.cachePoint (kInvalidNodeIndex);
}
else {
const int index = intValue (nearest);
// check for existence
2019-07-27 17:36:24 +03:00
if (graph.exists (index)) {
graph.cachePoint (index);
}
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeClean () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd, option };
2019-07-27 17:36:24 +03:00
// turn graph on
graph.setEditFlag (GraphEdit::On);
// if "all" passed clean up all the paths
2020-06-12 18:52:38 +03:00
if (strValue (option) == "all") {
int removed = 0;
for (auto i = 0; i < graph.length (); ++i) {
2019-07-27 17:36:24 +03:00
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);
}
2020-06-12 18:52:38 +03:00
else if (strValue (option).empty () || strValue (option) == "nearest") {
int removed = graph.clearConnections (graph.getEditorNearest ());
msg ("Done. Processed node %d. %d useless paths was cleared.", graph.getEditorNearest (), removed);
}
else {
const int index = intValue (option);
// check for existence
2019-07-27 17:36:24 +03:00
if (graph.exists (index)) {
const int removed = graph.clearConnections (index);
msg ("Done. Processed node %d. %d useless paths was cleared.", index, removed);
}
else {
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 () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd, radius, index };
// 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;
2020-06-12 18:52:38 +03:00
if (strValue (index).empty () || strValue (index) == "nearest") {
radiusIndex = graph.getEditorNearest ();
}
else {
2020-06-12 18:52:38 +03:00
radiusIndex = intValue (index);
}
graph.setRadius (radiusIndex, strValue (radius).float_ ());
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeSetFlags () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd };
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 () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd, teleport_index };
if (!hasArg (teleport_index)) {
2019-07-27 17:36:24 +03:00
return BotCommandResult::BadFormat;
}
2020-06-12 18:52:38 +03:00
int index = intValue (teleport_index);
// check for existence
2019-07-27 17:36:24 +03:00
if (graph.exists (index)) {
engfuncs.pfnSetOrigin (graph.getEditor (), graph[index].origin);
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 {
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 () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd };
2019-07-27 17:36:24 +03:00
// turn graph on
graph.setEditFlag (GraphEdit::On);
// choose the direction for path creation
if (strValue (cmd).endsWith ("_jump")) {
graph.pathCreate (PathConnection::Jumping);
}
else if (strValue (cmd).endsWith ("_both")) {
2019-07-27 17:36:24 +03:00
graph.pathCreate (PathConnection::Bidirectional);
}
aim: verify camp angles from nav data before using them aim: tweaked a bit grenade handling, so bots should use them more aim: reduce time between selecting grenade and throwing it away aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar aim: use direct enemy origin from visibility check, and not re-calculate it aim: update enemy prediction, so it now depends on frame interval for a bot aim: additional height offset are tweaked, and now used only for difficulty 4 nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain nav: do not check banned nodes, when bucket sizes re too low nav: cover nodes are now selected depending on total bots on server nav: let bot enter pause task after long jump nav: extend velocity by a little for a jump, like it was in first versions of bot nav: stuck checking is now taken in account lower minimal speed if bot is ducking fix: navigation reachability timers, so bots will have correct current node index while camping fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable fix: cover nodes are now calculated as they should fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard bot: minor refactoring and moving functions to correct source file bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight bot: camp buttons are now lightly depends on bot health chat: welcome chat message from bots is now sent during first freeze time period crlib: switch over to stdint.h and remove crlib-own types crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
else if (strValue (cmd).endsWith ("_in")) {
2019-07-27 17:36:24 +03:00
graph.pathCreate (PathConnection::Incoming);
}
aim: verify camp angles from nav data before using them aim: tweaked a bit grenade handling, so bots should use them more aim: reduce time between selecting grenade and throwing it away aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar aim: use direct enemy origin from visibility check, and not re-calculate it aim: update enemy prediction, so it now depends on frame interval for a bot aim: additional height offset are tweaked, and now used only for difficulty 4 nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain nav: do not check banned nodes, when bucket sizes re too low nav: cover nodes are now selected depending on total bots on server nav: let bot enter pause task after long jump nav: extend velocity by a little for a jump, like it was in first versions of bot nav: stuck checking is now taken in account lower minimal speed if bot is ducking fix: navigation reachability timers, so bots will have correct current node index while camping fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable fix: cover nodes are now calculated as they should fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard bot: minor refactoring and moving functions to correct source file bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight bot: camp buttons are now lightly depends on bot health chat: welcome chat message from bots is now sent during first freeze time period crlib: switch over to stdint.h and remove crlib-own types crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
else if (strValue (cmd).endsWith ("_out")) {
2019-07-27 17:36:24 +03:00
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 () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd };
2019-07-27 17:36:24 +03:00
// turn graph on
graph.setEditFlag (GraphEdit::On);
// delete the path
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 () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd };
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;
}
int BotControl::cmdNodePathCleanAll () {
enum args { graph_cmd = 1, cmd, index };
auto requestedNode = kInvalidNodeIndex;
if (hasArg (index)) {
requestedNode = intValue (index);
}
graph.resetPath (requestedNode);
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::cmdNodeAcquireEditor () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1 };
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.", graph.getEditor ()->v.netname.chars ());
2019-07-27 17:36:24 +03:00
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 () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1 };
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 };
2019-07-27 17:36:24 +03:00
// do not allow to upload analyzed graphs
if (graph.isAnalyzed ()) {
msg ("Sorry, unable to upload graph that was generated automatically.");
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
// do not allow to upload bad graph
if (!graph.checkNodes (false)) {
msg ("Sorry, unable to upload graph file that contains errors. Please type \"graph check\" to verify graph consistency.");
return BotCommandResult::Handled;
2019-07-27 17:36:24 +03:00
}
String uploadUrlAddress = cv_graph_url_upload.str ();
// only allow to upload to non-https endpoint
if (uploadUrlAddress.startsWith ("http")) {
msg ("Value of \"%s\" cvar should not contain URL scheme, only the host name and path.", cv_graph_url_upload.name ());
return BotCommandResult::Handled;
}
String uploadUrl = strings.format ("http://%s", uploadUrlAddress);
2019-07-27 17:36:24 +03:00
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");
// try to upload the file
if (http.uploadFile (uploadUrl, bstor.buildPath (BotFile::Graph))) {
2020-06-12 18:52:38 +03:00
msg ("Graph file was successfully validated and uploaded to the YaPB Graph DB (%s).", product.download);
msg ("It will be available for download for all YaPB users in a few minutes.");
2019-07-27 17:36:24 +03:00
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);
}
2020-06-12 18:52:38 +03:00
msg ("Something went wrong with uploading. Come back later. (%s)", status);
2019-07-27 17:36:24 +03:00
msg ("\n");
2019-07-27 17:36:24 +03:00
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 something is totally wrong with");
2019-07-27 17:36:24 +03:00
msg ("your files, and they are not passed sanity checks. Sorry...");
}
msg ("\n");
}
return BotCommandResult::Handled;
}
int BotControl::cmdNodeIterateCamp () {
2020-06-12 18:52:38 +03:00
enum args { graph_cmd = 1, cmd, option };
// turn graph on
graph.setEditFlag (GraphEdit::On);
// get the option describing operation
2020-06-12 18:52:38 +03:00
auto op = strValue (option);
if (op != "begin" && op != "end" && op != "next") {
return BotCommandResult::BadFormat;
}
2019-08-30 11:10:22 +03:00
if ((op == "next" || op == "end") && m_campIterator.empty ()) {
msg ("Before calling for 'next' / 'end' camp point, you should hit 'begin'.");
return BotCommandResult::Handled;
}
2019-08-30 11:10:22 +03:00
else if (op == "begin" && !m_campIterator.empty ()) {
msg ("Before calling for 'begin' camp point, you should hit 'end'.");
return BotCommandResult::Handled;
}
if (op == "end") {
2019-08-30 11:10:22 +03:00
m_campIterator.clear ();
}
else if (op == "next") {
2019-08-30 11:10:22 +03:00
if (!m_campIterator.empty ()) {
Vector origin = graph[m_campIterator.first ()].origin;
2019-08-30 11:10:22 +03:00
if (graph[m_campIterator.first ()].flags & NodeFlag::Crouch) {
origin.z += 23.0f;
}
engfuncs.pfnSetOrigin (m_ent, origin);
2019-08-30 11:10:22 +03:00
// go to next
m_campIterator.shift ();
if (m_campIterator.empty ()) {
msg ("Finished iterating camp spots.");
}
}
}
else if (op == "begin") {
for (const auto &path : graph) {
if (path.flags & NodeFlag::Camp) {
m_campIterator.push (path.number);
}
}
if (!m_campIterator.empty ()) {
msg ("Ready for iteration. Type 'next' to go to first camp node.");
return BotCommandResult::Handled;
}
msg ("Unable to begin iteration, camp points is not set.");
}
return BotCommandResult::Handled;
}
int BotControl::cmdNodeShowStats () {
graph.showStats ();
return BotCommandResult::Handled;
}
int BotControl::cmdNodeFileInfo () {
graph.showFileInfo ();
return BotCommandResult::Handled;
}
2023-03-13 15:39:15 +03:00
int BotControl::cmdAdjustHeight () {
enum args { graph_cmd = 1, cmd, offset };
if (!hasArg (offset)) {
return BotCommandResult::BadFormat;
}
auto heightOffset = floatValue (offset);
// adjust the height for all the nodes (negative values possible)
for (auto &path : graph) {
path.origin.z += heightOffset;
}
return BotCommandResult::Handled;
}
int BotControl::menuMain (int item) {
closeMenu (); // 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:
closeMenu ();
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) {
closeMenu (); // reset menu display
auto autoAcquireEditorRights = [&] () {
if (!graph.hasEditor ()) {
graph.setEditor (m_ent);
}
return graph.hasEditor () && graph.getEditor () == m_ent ? Menu::NodeMainPage1 : Menu::Features;
};
switch (item) {
case 1:
2019-07-27 17:36:24 +03:00
showMenu (Menu::WeaponMode);
break;
case 2:
showMenu (autoAcquireEditorRights ());
break;
case 3:
2019-07-27 17:36:24 +03:00
showMenu (Menu::Personality);
break;
case 4:
2020-06-12 18:52:38 +03:00
cv_debug.set (cv_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 {
closeMenu (); // reset menu display
msg ("You're dead, and have no access to this menu");
}
break;
case 10:
closeMenu ();
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuControl (int item) {
closeMenu (); // 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:
closeMenu ();
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuWeaponMode (int item) {
closeMenu (); // 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:
closeMenu ();
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuPersonality (int item) {
if (m_isMenuFillCommand) {
closeMenu (); // reset menu display
switch (item) {
case 1:
case 2:
case 3:
case 4:
bots.serverFill (m_menuServerFillTeam, item - 2, m_interMenuData[0]);
closeMenu ();
break;
case 10:
closeMenu ();
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
closeMenu (); // 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:
closeMenu ();
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuDifficulty (int item) {
closeMenu (); // 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:
closeMenu ();
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) {
closeMenu (); // reset menu display
if (item < 3) {
// 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:
closeMenu ();
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
closeMenu (); // 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 if (game.is (GameFlags::ConditionZero)) {
showMenu (item == 1 ? Menu::TerroristSelectCZ : Menu::CTSelectCZ);
}
else {
2019-07-27 17:36:24 +03:00
showMenu (item == 1 ? Menu::TerroristSelect : Menu::CTSelect);
}
break;
case 10:
closeMenu ();
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuClassSelect (int item) {
closeMenu (); // reset menu display
switch (item) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
m_interMenuData[2] = item;
bots.addbot ("", m_interMenuData[0], m_interMenuData[3], m_interMenuData[1], m_interMenuData[2], true);
break;
case 10:
closeMenu ();
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuCommands (int item) {
closeMenu (); // reset menu display
Bot *nearest = nullptr;
switch (item) {
case 1:
case 2:
if (util.findNearestPlayer (reinterpret_cast <void **> (&m_djump), m_ent, 600.0f, true, true, true, true, false) && !m_djump->m_hasC4 && !m_djump->m_hasHostage) {
if (item == 1) {
m_djump->startDoubleJump (m_ent);
}
else {
if (m_djump) {
m_djump->resetDoubleJump ();
m_djump = nullptr;
}
}
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::Commands);
break;
case 3:
case 4:
if (util.findNearestPlayer (reinterpret_cast <void **> (&nearest), m_ent, 600.0f, true, true, true, true, item == 4 ? false : true)) {
nearest->dropWeaponForUser (m_ent, item == 4 ? false : true);
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::Commands);
break;
case 10:
closeMenu ();
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) {
closeMenu (); // 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:
closeMenu ();
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) {
closeMenu (); // reset menu display
switch (item) {
case 1:
graph.setEditFlag (GraphEdit::On);
showMenu (Menu::NodeDebug);
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);
}
if (graph.hasEditFlag (GraphEdit::Auto)) {
msg ("Enabled auto nodes placement.");
}
else {
msg ("Disabled auto nodes placement.");
}
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 ();
msg ("Graph successfully saved.");
}
else {
msg ("Graph not saved. There are errors, see console...");
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeMainPage2);
break;
case 5:
if (graph.saveGraphData ()) {
msg ("Graph successfully saved.");
}
else {
msg ("Could not save Graph. See console...");
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeMainPage2);
break;
case 6:
if (graph.loadGraphData ()) {
msg ("Graph successfully loaded.");
}
else {
msg ("Could not load Graph. See console...");
}
2019-07-27 17:36:24 +03:00
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:
graph.setEditFlag (GraphEdit::On);
if (graph.hasEditFlag (GraphEdit::Noclip)) {
graph.clearEditFlag (GraphEdit::Noclip);
msg ("Noclip mode disabled.");
}
else {
graph.setEditFlag (GraphEdit::Noclip);
msg ("Noclip mode enabled.");
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeMainPage2);
// update editor movetype based on flag
m_ent->v.movetype = graph.hasEditFlag (GraphEdit::Noclip) ? MOVETYPE_NOCLIP : MOVETYPE_WALK;
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) {
closeMenu (); // reset menu display
2019-07-27 17:36:24 +03:00
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) {
closeMenu (); // 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:
graph.add (NodeAddFlag::Goal);
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeType);
break;
case 9:
2019-07-27 17:36:24 +03:00
graph.startLearnJump ();
showMenu (Menu::NodeType);
break;
case 10:
closeMenu ();
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuGraphDebug (int item) {
closeMenu (); // reset menu display
switch (item) {
case 1:
cv_debug_goal.set (graph.getEditorNearest ());
if (cv_debug_goal.int_ () != kInvalidNodeIndex) {
msg ("Debug goal is set to node %d.", cv_debug_goal.int_ ());
}
else {
msg ("Cannot find the node. Debug goal is disabled.");
}
showMenu (Menu::NodeDebug);
break;
case 2:
cv_debug_goal.set (graph.getFacingIndex ());
if (cv_debug_goal.int_ () != kInvalidNodeIndex) {
msg ("Debug goal is set to node %d.", cv_debug_goal.int_ ());
}
else {
msg ("Cannot find the node. Debug goal is disabled.");
}
showMenu (Menu::NodeDebug);
break;
case 3:
cv_debug_goal.set (kInvalidNodeIndex);
msg ("Debug goal is disabled.");
showMenu (Menu::NodeDebug);
break;
case 10:
closeMenu ();
break;
}
return BotCommandResult::Handled;
}
2019-07-27 17:36:24 +03:00
int BotControl::menuGraphFlag (int item) {
closeMenu (); // reset menu display
int nearest = graph.getEditorNearest ();
switch (item) {
case 1:
2019-07-27 17:36:24 +03:00
graph.toggleFlags (NodeFlag::NoHostage);
showMenu (Menu::NodeFlag);
break;
case 2:
if (graph[nearest].flags & NodeFlag::CTOnly) {
graph.toggleFlags (NodeFlag::CTOnly);
graph.toggleFlags (NodeFlag::TerroristOnly);
}
else {
graph.toggleFlags (NodeFlag::TerroristOnly);
}
2019-07-27 17:36:24 +03:00
showMenu (Menu::NodeFlag);
break;
case 3:
if (graph[nearest].flags & NodeFlag::TerroristOnly) {
graph.toggleFlags (NodeFlag::TerroristOnly);
graph.toggleFlags (NodeFlag::CTOnly);
}
else {
graph.toggleFlags (NodeFlag::CTOnly);
}
2019-07-27 17:36:24 +03:00
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;
2023-03-13 15:39:15 +03:00
case 6:
graph.toggleFlags (NodeFlag::Goal);
showMenu (Menu::NodeFlag);
break;
2023-03-13 15:39:15 +03:00
case 7:
graph.toggleFlags (NodeFlag::Rescue);
showMenu (Menu::NodeFlag);
break;
case 8:
if (graph[nearest].flags != NodeFlag::Crouch) {
graph.toggleFlags (NodeFlag::Crouch);
graph[nearest].origin.z += -18.0f;
}
else {
graph.toggleFlags (NodeFlag::Crouch);
graph[nearest].origin.z += 18.0f;
}
2023-03-13 15:39:15 +03:00
showMenu (Menu::NodeFlag);
break;
2023-03-13 15:39:15 +03:00
case 9:
// if the node doesn't have a camp flag, set it and open the camp directions selection menu
if (!(graph[nearest].flags & NodeFlag::Camp)) {
graph.toggleFlags (NodeFlag::Camp);
showMenu (Menu::CampDirections);
break;
}
// otherwise remove the flag, and don't show the camp directions selection menu
else {
graph.toggleFlags (NodeFlag::Camp);
showMenu (Menu::NodeFlag);
break;
}
}
return BotCommandResult::Handled;
}
int BotControl::menuCampDirections (int item) {
closeMenu (); // reset menu display
switch (item) {
case 1:
graph.add (NodeAddFlag::Camp);
showMenu (Menu::CampDirections);
break;
case 2:
graph.add (NodeAddFlag::CampEnd);
showMenu (Menu::CampDirections);
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuAutoPathDistance (int item) {
closeMenu (); // reset menu display
constexpr float distances[] = { 0.0f, 100.0f, 130.0f, 160.0f, 190.0f, 220.0f, 250.0f };
if (item >= 1 && item <= 7) {
2020-08-23 11:08:27 +03:00
graph.setAutoPathDistance (distances[item - 1]);
}
2020-08-23 11:08:27 +03:00
switch (item) {
default:
showMenu (Menu::NodeAutoPath);
break;
2020-08-23 11:08:27 +03:00
case 10:
closeMenu ();
2020-08-23 11:08:27 +03:00
break;
}
2019-07-27 17:36:24 +03:00
return BotCommandResult::Handled;
}
int BotControl::menuKickPage1 (int item) {
closeMenu (); // 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) {
closeMenu (); // 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) {
closeMenu (); // 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) {
closeMenu (); // 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) {
closeMenu (); // 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 4:
graph.pathCreate (PathConnection::Jumping);
showMenu (Menu::NodePath);
break;
case 10:
closeMenu ();
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;
}
const auto &prefix = m_args.first ();
// no handling if not for us
2020-09-10 13:54:00 +03:00
if (prefix != product.cmdPri && prefix != product.cmdSec) {
return false;
}
const auto &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)) {
2020-06-12 18:52:38 +03:00
msg ("Access to %s commands is restricted.", product.name);
// reset issuer, but returns "true" to suppress "unknown command" message
setIssuer (nullptr);
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
if (hasArg (1) && strValue (1) == "help") {
2020-08-31 14:30:09 +03:00
const auto hasSecondArg = hasArg (2);
for (auto &item : m_cmds) {
2020-08-31 14:30:09 +03:00
if (!hasSecondArg) {
cmd = item.name.split ("/").first ();
2020-08-31 14:30:09 +03:00
}
if (!hasSecondArg || aliasMatch (item.name, strValue (2), cmd)) {
msg ("Command: \"%s %s\"", prefix, cmd);
msg ("Format: %s", item.format);
msg ("Help: %s", conf.translate (item.help));
2020-08-31 14:30:09 +03:00
auto aliases = item.name.split ("/");
if (aliases.length () > 1) {
msg ("Aliases: %s", String::join (aliases, ", "));
}
if (hasSecondArg) {
return true;
}
else {
msg ("\n");
}
}
}
2020-08-31 14:30:09 +03:00
if (!hasSecondArg) {
return true;
}
else {
msg ("No help found for \"%s\"", strValue (2));
}
return true;
}
cmd.clear ();
// if no args passed just print all the commands
if (m_args.length () == 1) {
2020-06-12 18:52:38 +03:00
msg ("usage %s <command> [arguments]", prefix);
msg ("valid commands are: ");
for (auto &item : m_cmds) {
msg (" %-14.11s - %s", item.name.split ("/").first (), String (conf.translate (item.help)).lowercase ());
}
return true;
}
// first search for a actual cmd
for (auto &item : m_cmds) {
if (aliasMatch (item.name, m_args[1], cmd)) {
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:
2020-06-12 18:52:38 +03:00
msg ("Command \"%s %s\" is only available from the listenserver console.", prefix, cmd);
break;
2019-07-27 17:36:24 +03:00
case BotCommandResult::BadFormat:
msg ("Incorrect usage of \"%s %s\" command. Correct usage is:", prefix, cmd);
msg ("\n\t%s\n", item.format);
msg ("Please type \"%s help %s\" to get more information.", prefix, cmd);
break;
}
m_isFromConsole = false;
return true;
}
}
2020-06-12 18:52:38 +03:00
msg ("Unknown command: %s", m_args[1]);
// clear all the arguments upon finish
m_args.clear ();
return true;
}
2019-07-27 17:36:24 +03:00
bool BotControl::executeMenus () {
if (!util.isPlayer (m_ent) || game.isBotCmd ()) {
return false;
}
const auto &issuer = util.getClient (game.indexOfPlayer (m_ent));
// check if it's menu select, and some key pressed
2020-06-12 18:52:38 +03:00
if (strValue (0) != "menuselect" || strValue (1).empty () || issuer.menu == Menu::None) {
return false;
}
// let's get handle
for (auto &menu : m_menus) {
if (menu.ident == issuer.menu) {
return (this->*menu.handler) (strValue (1).int_ ()) == BotCommandResult::Handled;
}
}
return false;
}
void BotControl::showMenu (int id) {
static bool menusParsed = false;
// make menus looks like we need only once
if (!menusParsed) {
m_ignoreTranslate = false; // always translate menus
for (auto &parsed : m_menus) {
2020-06-12 18:52:38 +03:00
StringRef translated = conf.translate (parsed.text);
// translate all the things
parsed.text = translated;
// make menu looks best
2019-07-27 17:36:24 +03:00
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));
}
}
}
menusParsed = true;
}
if (!util.isPlayer (m_ent)) {
return;
}
auto &client = util.getClient (game.indexOfPlayer (m_ent));
aim: verify camp angles from nav data before using them aim: tweaked a bit grenade handling, so bots should use them more aim: reduce time between selecting grenade and throwing it away aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar aim: use direct enemy origin from visibility check, and not re-calculate it aim: update enemy prediction, so it now depends on frame interval for a bot aim: additional height offset are tweaked, and now used only for difficulty 4 nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain nav: do not check banned nodes, when bucket sizes re too low nav: cover nodes are now selected depending on total bots on server nav: let bot enter pause task after long jump nav: extend velocity by a little for a jump, like it was in first versions of bot nav: stuck checking is now taken in account lower minimal speed if bot is ducking fix: navigation reachability timers, so bots will have correct current node index while camping fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable fix: cover nodes are now calculated as they should fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard bot: minor refactoring and moving functions to correct source file bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight bot: camp buttons are now lightly depends on bot health chat: welcome chat message from bots is now sent during first freeze time period crlib: switch over to stdint.h and remove crlib-own types crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
auto sendMenu = [&] (int32_t slots, bool last, StringRef text) {
MessageWriter (MSG_ONE, msgs.id (NetMsg::ShowMenu), nullptr, m_ent)
.writeShort (slots)
.writeChar (-1)
.writeByte (last ? HLFalse : HLTrue)
.writeString (text.chars ());
};
constexpr size_t maxMenuSentLength = 140;
for (const auto &display : m_menus) {
if (display.ident == id) {
String text = (game.is (GameFlags::Xash3D | GameFlags::Mobility) && !cv_display_menu_text.bool_ ()) ? " " : display.text.chars ();
// split if needed
if (text.length () > maxMenuSentLength) {
auto chunks = text.split (maxMenuSentLength);
// send in chunks
for (size_t i = 0; i < chunks.length (); ++i) {
sendMenu (display.slots, i == chunks.length () - 1, chunks[i]);
}
}
else {
sendMenu (display.slots, true, text);
}
client.menu = id;
engfuncs.pfnClientCommand (m_ent, "speak \"player/geiger1\"\n"); // stops others from hearing menu sounds..
break;
}
}
}
void BotControl::closeMenu () {
if (!util.isPlayer (m_ent)) {
return;
}
auto &client = util.getClient (game.indexOfPlayer (m_ent));
// do not reset menu if already none
if (client.menu == Menu::None) {
return;
}
MessageWriter (MSG_ONE, msgs.id (NetMsg::ShowMenu), nullptr, m_ent)
.writeShort (0)
.writeChar (0)
.writeByte (0)
.writeString ("");
client.menu = Menu::None;
}
void BotControl::kickBotByMenu (int page) {
if (page > 4 || page < 1) {
return;
}
static StringRef headerTitle = conf.translate ("Bots Remove Menu");
static StringRef notABot = conf.translate ("Not a Bot");
static StringRef backKey = conf.translate ("Back");
static StringRef moreKey = conf.translate ("More");
String menus;
menus.assignf ("\\y%s (%d/4):\\w\n\n", headerTitle, 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];
// check for fakeclient bit, since we're clear it upon kick, but actual bot struct destroyed after client disconnected
if (bot != nullptr && (bot->pev->flags & FL_FAKECLIENT)) {
menuKeys |= cr::bit (cr::abs (i - menuKey));
menus.appendf ("%1.1d. %s%s\n", i - menuKey + 1, bot->pev->netname.chars (), bot->m_team == Team::CT ? " \\y(CT)\\w" : " \\r(T)\\w");
}
else {
menus.appendf ("\\d %1.1d. %s\\w\n", i - menuKey + 1, notABot);
}
}
menus.appendf ("\n%s 0. %s", (page == 4) ? "" : strings.format (" 9. %s...\n", moreKey), backKey);
// force to clear current menu
closeMenu ();
2019-07-27 17:36:24 +03:00
auto id = Menu::KickPage1 - 1 + page;
for (auto &menu : m_menus) {
if (menu.ident == id) {
aim: verify camp angles from nav data before using them aim: tweaked a bit grenade handling, so bots should use them more aim: reduce time between selecting grenade and throwing it away aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar aim: use direct enemy origin from visibility check, and not re-calculate it aim: update enemy prediction, so it now depends on frame interval for a bot aim: additional height offset are tweaked, and now used only for difficulty 4 nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain nav: do not check banned nodes, when bucket sizes re too low nav: cover nodes are now selected depending on total bots on server nav: let bot enter pause task after long jump nav: extend velocity by a little for a jump, like it was in first versions of bot nav: stuck checking is now taken in account lower minimal speed if bot is ducking fix: navigation reachability timers, so bots will have correct current node index while camping fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable fix: cover nodes are now calculated as they should fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard bot: minor refactoring and moving functions to correct source file bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight bot: camp buttons are now lightly depends on bot health chat: welcome chat message from bots is now sent during first freeze time period crlib: switch over to stdint.h and remove crlib-own types crlib: fixed alignment in sse code
2023-04-07 14:46:49 +03:00
menu.slots = static_cast <int> (static_cast <uint32_t> (menuKeys) & static_cast <uint32_t> (-1));
menu.text = menus;
break;
}
}
showMenu (id);
}
void BotControl::assignAdminRights (edict_t *ent, char *infobuffer) {
if (!game.isDedicated () || util.isFakeClient (ent)) {
return;
}
2020-06-12 18:52:38 +03:00
StringRef key = cv_password_key.str ();
StringRef password = cv_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;
}
StringRef key = cv_password_key.str ();
StringRef password = cv_password.str ();
for (auto &client : util.getClients ()) {
if (!(client.flags & ClientFlags::Used) || util.isFakeClient (client.ent)) {
continue;
}
auto ent = client.ent;
if (client.flags & ClientFlags::Admin) {
if (key.empty () || password.empty ()) {
client.flags &= ~ClientFlags::Admin;
}
else if (password != engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (ent), key.chars ())) {
client.flags &= ~ClientFlags::Admin;
ctrl.msg ("Player %s had lost remote access to %s.", ent->v.netname.chars (), product.name);
}
}
else if (!(client.flags & ClientFlags::Admin) && !key.empty () && !password.empty ()) {
if (password == engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (ent), key.chars ())) {
client.flags |= ClientFlags::Admin;
ctrl.msg ("Player %s had gained full remote access to %s.", ent->v.netname.chars (), product.name);
}
}
}
}
void BotControl::flushPrintQueue () {
if (m_printQueueFlushTimestamp > game.time () || m_printQueue.empty ()) {
return;
}
auto printable = m_printQueue.popFront ();
// send to needed destination
if (printable.destination == PrintQueueDestination::ServerConsole) {
game.print (printable.text.chars ());
}
else if (!game.isNullEntity (m_ent)) {
game.clientPrint (m_ent, printable.text.chars ());
}
m_printQueueFlushTimestamp = game.time () + 0.05f;
}
2019-07-27 17:36:24 +03:00
BotControl::BotControl () {
m_ent = nullptr;
m_djump = nullptr;
m_ignoreTranslate = false;
m_isFromConsole = false;
m_isMenuFillCommand = false;
2019-07-27 17:36:24 +03:00
m_rapidOutput = false;
m_menuServerFillTeam = 5;
m_printQueueFlushTimestamp = 0.0f;
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);
2019-07-27 17:36:24 +03:00
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/kickall_ct/kickall_t", "removebots [instant] [team]", "Kicks all the bots from the game.", &BotControl::cmdKickBots);
2019-07-27 17:36:24 +03:00
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] [personality]", "Fill the server (add bots) with specified parameters.", &BotControl::cmdFill);
2019-07-27 17:36:24 +03:00
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 editor.", &BotControl::cmdNodeMenu);
2019-07-27 17:36:24 +03:00
m_cmds.emplace ("list/listbots", "list [noarguments]", "Lists the bots currently playing on server.", &BotControl::cmdList);
m_cmds.emplace ("graph/g/wp/wpt/waypoint", "graph [help]", "Handles graph operations.", &BotControl::cmdNode);
m_cmds.emplace ("cvars", "cvars [save|save_map|cvar]", "Display all the cvars with their descriptions.", &BotControl::cmdCvars);
2022-12-27 12:13:29 +00:00
m_cmds.emplace ("show_custom", "show_custom [noarguments]", "Shows the current values from custom.cfg.", &BotControl::cmdShowCustom);
// declare the menus
createMenus ();
}
2019-07-27 17:36:24 +03:00
void BotControl::handleEngineCommands () {
collectArgs ();
setIssuer (game.getLocalEntity ());
setFromConsole (true);
executeCommands ();
}
bool BotControl::handleClientSideCommandsWrapper (edict_t *ent, bool isMenus) {
collectArgs ();
setIssuer (ent);
setFromConsole (!isMenus);
auto result = isMenus ? executeMenus () : executeCommands ();
if (!result) {
setIssuer (nullptr);
}
return result;
}
bool BotControl::handleClientCommands (edict_t *ent) {
return handleClientSideCommandsWrapper (ent, false);
}
bool BotControl::handleMenuCommands (edict_t *ent) {
return handleClientSideCommandsWrapper (ent, true);
}
void BotControl::enableDrawModels (bool enable) {
StringArray entities;
entities.push ("info_player_start");
entities.push ("info_player_deathmatch");
entities.push ("info_vip_start");
if (enable) {
game.setPlayerStartDrawModels ();
}
for (auto &entity : entities) {
game.searchEntities ("classname", entity, [&enable] (edict_t *ent) {
if (enable) {
ent->v.effects &= ~EF_NODRAW;
}
else {
ent->v.effects |= EF_NODRAW;
}
return EntitySearchResult::Continue;
2023-03-13 15:39:15 +03:00
});
}
}
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);
// condition zero terrorist model select menu
m_menus.emplace (
Menu::TerroristSelectCZ, keys (6),
"\\ySelect an appearance\\w\n\n"
"1. Phoenix Connexion\n"
"2. L337 Krew\n"
"3. Arctic Avengers\n"
"4. Guerilla Warfare\n"
"5. Midwest Militia\n\n"
"6. Auto-select\n\n"
"0. Exit",
&BotControl::menuClassSelect);
// condition zero counter-terrorist model select menu
m_menus.emplace (
Menu::CTSelectCZ, keys (6),
"\\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"
"5. Russian Spetsnaz\n\n"
"6. Auto-select\n\n"
"0. Exit",
&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. Debug goal\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 nodes 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);
// nodes 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);
2023-03-13 15:39:15 +03:00
// debug goal menu
m_menus.emplace (
Menu::NodeDebug, keys (3),
"\\yDebug goal\\w\n\n"
"1. Debug nearest node\n"
"2. Debug facing node\n"
"3. Stop debugging\n\n"
"0. Exit",
&BotControl::menuGraphDebug);
// set waypoint flag menu
2019-07-27 17:36:24 +03:00
m_menus.emplace (
Menu::NodeFlag, keys (9),
"\\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"
"6. Map Goal\n"
"7. Rescue Zone\n"
"8. Crouch Down\n"
"9. Camp Point\n\n"
"0. Exit",
2019-07-27 17:36:24 +03:00
&BotControl::menuGraphFlag);
2023-03-13 15:39:15 +03:00
// set camp directions menu
m_menus.emplace (
Menu::CampDirections, keys (2),
"\\ySet Camp Point directions\\w\n\n"
"1. Camp Start\n"
"2. Camp End\n\n"
"0. Exit",
&BotControl::menuCampDirections);
// 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 (4),
"\\yCreate Path (Choose Direction)\\w\n\n"
"1. Outgoing Path\n"
"2. Incoming Path\n"
"3. Bidirectional (Both Ways)\n"
"4. Jumping Path\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);
}