diff --git a/base/src/server/gamerules_multiplayer.qc b/base/src/server/gamerules_multiplayer.qc index 77c1a696..183c87de 100644 --- a/base/src/server/gamerules_multiplayer.qc +++ b/base/src/server/gamerules_multiplayer.qc @@ -109,9 +109,6 @@ MultiplayerRules::ConsoleCommand(NSClientPlayer pp, string cmd) tokenize(cmd); switch (argv(0)) { - case "bot_add": - Bot_AddQuick(); - break; default: return (0); } @@ -129,4 +126,4 @@ Game_InitRules(void) } else { g_grMode = spawn(MultiplayerRules); } -} \ No newline at end of file +} diff --git a/platform/base_scripts.pk3dir/def/bot.def b/platform/base_scripts.pk3dir/def/bot.def new file mode 100644 index 00000000..6ff536ca --- /dev/null +++ b/platform/base_scripts.pk3dir/def/bot.def @@ -0,0 +1,4 @@ +entityDef bot +{ + spawnclass NSBot +} \ No newline at end of file diff --git a/platform/cvars.cfg b/platform/cvars.cfg index ab3926da..86e41926 100644 --- a/platform/cvars.cfg +++ b/platform/cvars.cfg @@ -1,5 +1,14 @@ -// common +// bots +seta bot_enable 1 // Enable (1) or disable (0) usage of bots in the game. +seta bot_pause 0 // Enable (1) or disable (0) an interrupt for the Bot AIs thinking. +seta bot_noChat 0 // Enable (1) or disable (0) a suppression of any bot chatter. +seta bot_fastChat 0 // Enable (1) or disable (0) bot chatter that does not stop other inputs. +seta bot_debug 0 // Enable (1) or disable (0) bot debug features that otherwise won't work. +seta bot_developer 0 // Enable (1) or disable (0) bot debug text in console. +seta bot_minClients -1 // When set, ensures to fill the server with this many players/bots. + +// common seta com_showFPS 0 // Draws the Frames Per Second counter. Has two values: 1 - Simple 2 - Detailed seta com_showTracers 0 // Debug display for tracelines, networking intensive. diff --git a/src/botlib/bot.h b/src/botlib/bot.h index 83a1113b..a56e7304 100644 --- a/src/botlib/bot.h +++ b/src/botlib/bot.h @@ -41,7 +41,7 @@ typedef enum /** Base class for the Bot AI. */ -class bot:player +class NSBot:player { /* routing */ int m_iNodes; @@ -74,7 +74,7 @@ class bot:player float m_flForceWeaponAttack; vector m_vecForceWeaponAttackPos; - void(void) bot; + void(void) NSBot; virtual void(botstate_t) SetState; virtual botstate_t(void) GetState; @@ -107,7 +107,7 @@ entity Bot_AddQuick(void); /** Applies random custom colors to the given bot entity. */ void -Bot_RandomColormap(bot target) +Bot_RandomColormap(NSBot target) { vector x = hsv2rgb(random() * 360, 100, 100); float top = x[2] + (x[1] << 8) + (x[0] << 16); diff --git a/src/botlib/bot.qc b/src/botlib/bot.qc index d71468ed..4f64e1d2 100644 --- a/src/botlib/bot.qc +++ b/src/botlib/bot.qc @@ -15,44 +15,44 @@ */ botstate_t -bot::GetState(void) +NSBot::GetState(void) { return m_bsState; } void -bot::SetState(botstate_t state) +NSBot::SetState(botstate_t state) { m_bsState = state; } botpersonality_t -bot::GetPersonality(void) +NSBot::GetPersonality(void) { return m_bpPersonality; } float -bot::GetWalkSpeed(void) +NSBot::GetWalkSpeed(void) { return 120; } float -bot::GetRunSpeed(void) +NSBot::GetRunSpeed(void) { return 240; } void -bot::RouteClear(void) +NSBot::RouteClear(void) { super::RouteClear(); m_flNodeGiveup = 0.0f; } void -bot::BrainThink(int enemyvisible, int enemydistant) +NSBot::BrainThink(int enemyvisible, int enemydistant) { /* we had a target and it's now dead. now what? */ if (m_eTarget) { @@ -67,7 +67,7 @@ bot::BrainThink(int enemyvisible, int enemydistant) } void -bot::UseButton(void) +NSBot::UseButton(void) { float bestDist; func_button bestButton = __NULL__; @@ -96,7 +96,7 @@ bot::UseButton(void) } void -bot::SeeThink(void) +NSBot::SeeThink(void) { NSGameRules rules = (NSGameRules)g_grMode; @@ -163,7 +163,7 @@ bot::SeeThink(void) } void -bot::CheckRoute(void) +NSBot::CheckRoute(void) { float flDist; vector vecEndPos; @@ -193,7 +193,7 @@ bot::CheckRoute(void) /* we're inside the radius */ if (flDist <= flRadius) { - NSLog("^2bot::^3CheckRoute^7: " \ + NSLog("^2NSBot::^3CheckRoute^7: " \ "%s reached node\n", this.targetname); m_iCurNode--; @@ -218,7 +218,7 @@ bot::CheckRoute(void) /* can we walk directly to our target destination? */ if (trace_fraction == 1.0) { - print("^2bot::^3CheckRoute^7: " \ + print("^2NSBot::^3CheckRoute^7: " \ "Walking directly to last node\n"); m_iCurNode = -1; } @@ -261,13 +261,13 @@ bot::CheckRoute(void) } void -bot::CreateObjective(void) +NSBot::CreateObjective(void) { RouteToPosition(Route_SelectDestination(this)); } void -bot::RunAI(void) +NSBot::RunAI(void) { vector aimDir, aimPos; bool enemyVisible, enemyDistant; @@ -294,7 +294,7 @@ bot::RunAI(void) if (!m_iNodes && autocvar_bot_aimless == 0) { CreateObjective(); - NSLog("bot::RunAI: %s is calculating first bot route", + NSLog("NSBot::RunAI: %s is calculating first bot route", this.netname); /* our route probably has not been processed yet */ @@ -500,12 +500,12 @@ bot::RunAI(void) } void -bot::PreFrame(void) +NSBot::PreFrame(void) { } void -bot::PostFrame(void) +NSBot::PostFrame(void) { #ifndef NEW_INVENTORY /* we've picked something new up */ @@ -518,7 +518,7 @@ bot::PostFrame(void) } void -bot::SetName(string nickname) +NSBot::SetName(string nickname) { if (autocvar_bot_prefix) forceinfokey(this, "name", sprintf("%s %s", autocvar_bot_prefix, nickname)); @@ -527,9 +527,66 @@ bot::SetName(string nickname) } void -bot::bot(void) +NSBot::NSBot(void) { classname = "player"; targetname = "_nuclide_bot_"; forceinfokey(this, "*bot", "1"); } + +void +Bot_KickRandom(void) +{ + for (entity e = world;(e = find(e, ::classname, "player"));) { + if (clienttype(e) == CLIENTTYPE_BOT) { + dropclient(e); + return; + } + } +} + +void +bot_spawner_think(void) +{ + int clientCount = 0i; + int botCount = 0i; + int minClientsCvar = (int)cvar("bot_minClients"); + + /* if -1, we are not managing _anything_ */ + if (minClientsCvar == -1) { + self.nextthink = time + 5.0f; + return; + } + + /* count total clients + bots */ + for (entity e = world;(e = find(e, ::classname, "player"));) { + if (clienttype(e) == CLIENTTYPE_BOT) { + clientCount++; + botCount++; + }else if (clienttype(e) == CLIENTTYPE_REAL) { + clientCount++; + } + } + + /* add remove as necessary */ + if (clientCount < cvar("bot_minClients")) { + BotProfile_AddRandom(); + } else if (clientCount > cvar("bot_minClients")) { + if (botCount > 0i) { + Bot_KickRandom(); + } + } + + self.nextthink = time + 1.0f; +} + +void +BotLib_Init(void) +{ + BotProfile_Init(); + + /* this spawner manages the active bots */ + entity bot_spawner = spawn(); + bot_spawner.think = bot_spawner_think; + bot_spawner.nextthink = time + 1.0f; +} diff --git a/src/botlib/bot_chat.qc b/src/botlib/bot_chat.qc index e784bbdf..3aae5ade 100644 --- a/src/botlib/bot_chat.qc +++ b/src/botlib/bot_chat.qc @@ -15,13 +15,13 @@ */ void -bot::ChatSay(string msg) +NSBot::ChatSay(string msg) { g_grMode.ChatMessageAll(this, msg); } void -bot::ChatSayTeam(string msg) +NSBot::ChatSayTeam(string msg) { g_grMode.ChatMessageTeam(this, msg); } diff --git a/src/botlib/bot_combat.qc b/src/botlib/bot_combat.qc index 212e46f1..f3af9d2c 100644 --- a/src/botlib/bot_combat.qc +++ b/src/botlib/bot_combat.qc @@ -15,7 +15,7 @@ */ void -bot::Pain(void) +NSBot::Pain(void) { NSGameRules rules = g_grMode; @@ -44,7 +44,7 @@ bot::Pain(void) } void -bot::SetEnemy(entity en) +NSBot::SetEnemy(entity en) { m_eTarget = en; @@ -56,7 +56,7 @@ bot::SetEnemy(entity en) } void -bot::WeaponThink(void) +NSBot::WeaponThink(void) { int r = Weapons_IsEmpty(this, activeweapon); @@ -74,7 +74,7 @@ bot::WeaponThink(void) } void -bot::WeaponAttack(void) +NSBot::WeaponAttack(void) { bool shouldAttack = false; @@ -130,7 +130,7 @@ bot::WeaponAttack(void) } void -bot::ForceWeaponAttack(vector attackPos, float attackTime) +NSBot::ForceWeaponAttack(vector attackPos, float attackTime) { m_vecForceWeaponAttackPos = attackPos; m_flForceWeaponAttack = attackTime + time; @@ -153,7 +153,7 @@ BotLib_Alert(vector pos, float radius, float t) if (vlen(pos - w.origin) > radius) continue; - bot f = (bot) w; + NSBot f = (NSBot) w; /* they already got a target of some kind */ if (f.m_eTarget) diff --git a/src/botlib/cmd.qc b/src/botlib/cmd.qc index e8b9240e..1c4ea1be 100644 --- a/src/botlib/cmd.qc +++ b/src/botlib/cmd.qc @@ -61,3 +61,30 @@ Bot_AddQuick(void) self = oself; return (newbot); } + +void +Bot_KillAllBots(void) +{ + entity oldSelf = self; + + for ( other = world; ( other = find( other, classname, "player" ) ); ) { + if ( clienttype( other ) == CLIENTTYPE_BOT ) { + self = other; + ClientKill(); + } + } + + self = oldSelf; +} + +void +Bot_ResetAllBotsGoals(void) +{ + for ( other = world; ( other = find( other, classname, "player" ) ); ) { + if ( clienttype( other ) == CLIENTTYPE_BOT ) { + NSBot theBot = (NSBot)other; + theBot.SetEnemy(__NULL__); + theBot.RouteClear(); + } + } +} diff --git a/src/botlib/defs.h b/src/botlib/defs.h index 4236081c..79d7186b 100644 --- a/src/botlib/defs.h +++ b/src/botlib/defs.h @@ -17,6 +17,7 @@ #include "bot.h" #include "botinfo.h" #include "cvar.h" +#include "profiles.h" -vector Route_SelectDestination( bot target ); +vector Route_SelectDestination( NSBot target ); diff --git a/src/botlib/include.src b/src/botlib/include.src index 14361923..f08b4660 100644 --- a/src/botlib/include.src +++ b/src/botlib/include.src @@ -2,6 +2,7 @@ #includelist defs.h +profiles.qc bot.qc bot_chat.qc bot_combat.qc diff --git a/src/botlib/profiles.h b/src/botlib/profiles.h new file mode 100644 index 00000000..3c4e4ed9 --- /dev/null +++ b/src/botlib/profiles.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 Vera Visions LLC. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/* BotScript + script/bots.txt + + Listing of various bot profiles + where infokeys can be set and interpreted + by the game-logic at will. + + The `name` keys has to _always_ be present. + The `funname` key is optional. + + Name acts as both an identifier as well + as a nickname when `funname` is not present. + + Anything else is considered to be extra. +*/ + +typedef struct +{ + string m_strName; + string m_strNetName; + string m_strExtra; +} botScript_t; + +#define BOTSCRIPT_MAX 32 +botScript_t g_bots[BOTSCRIPT_MAX]; +var int g_botScriptCount; \ No newline at end of file diff --git a/src/botlib/profiles.qc b/src/botlib/profiles.qc new file mode 100644 index 00000000..90fecf40 --- /dev/null +++ b/src/botlib/profiles.qc @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2023 Vera Visions LLC. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +bool +Bot_ExistsInServer(string botName) +{ + for (entity e = world;(e = find(e, ::classname, "player"));) { + if (clienttype(e) == CLIENTTYPE_BOT) { + if (e.netname == botName) { + return (true); + } + } + } + + return (false); +} + +bool +Bot_AddBot_f(string botName) +{ + int extraCount = 0i; + int foundID = -1i; + entity oldSelf; + NSBot newBot; + int i = 0i; + + if (!botName) { + print("Usage: Addbot [skill 1-5] [team] [msec delay] [altname]\n"); + return (false); + } + + if (!g_nodes_present) { + print("^1BotScript_Add^7: Can't add bot. No waypoints.\n"); + return (false); + } + + /* grab the right profile id */ + for (i = 0i; i < g_botScriptCount; i++) { + if (g_bots[i].m_strName == botName) { + foundID = i; + } + } + + if (foundID == -1i) { + print("^1BotScript_Add^7: Named profile not found.\n"); + return (false); + } + + oldSelf = self; + self = spawnclient(); + + if (!self) { + print("^1BotScript_Add^7: Can't add bot. Server is full\n"); + self = oldSelf; + return (false); + } + + newBot = (NSBot)self; + newBot.SetInfoKey("name", g_bots[foundID].m_strNetName); + + extraCount = tokenize(g_bots[foundID].m_strExtra); + + for (i = 0i; i < extraCount; i+=2) { + newBot.SetInfoKey(argv(i), argv(i+1)); + } + + ClientConnect(); + PutClientInServer(); + + self = oldSelf; + return (true); +} + +bool +BotProfile_AddRandom(void) +{ + int startValue = (int)floor(random(0, g_botScriptCount)); + int spawnBot = -1i; + + /* start at a random index */ + for (int i = startValue; i < g_botScriptCount; i++) { + if (Bot_ExistsInServer(g_bots[i].m_strNetName) == false) { + spawnBot = i; + break; + } + } + + /* still haven't found it. count down. */ + if (spawnBot == -1i) { + for (int i = startValue - 1i; i > 0i; i--) { + if (Bot_ExistsInServer(g_bots[i].m_strNetName) == false) { + spawnBot = i; + break; + } + } + } + + /* every bot exists already */ + if (spawnBot == -1i) { + print("^1BotProfile_AddRandom^7: Not enough profiles available.\n"); + return (false); + } + + Bot_AddBot_f(g_bots[spawnBot].m_strName); + return (true); +} + +void +BotProfile_Init(void) +{ + filestream botScript; + string tempString; + botScript_t currentDef; + int braceDepth = 0i; + + g_botScriptCount = 0i; + + if (autocvar(bot_enable, 1) == 0) { + return; + } + + botScript = fopen("scripts/bots.txt", FILE_READ); + + if (botScript < 0) { + return; + } + + /* line by line */ + while ((tempString = fgets(botScript))) { + int lineSegments = tokenize_console(tempString); + + /* word for word */ + for (int i = 0i; i < lineSegments; i++) { + string word = argv(i); + + switch (word) { + case "{": + braceDepth++; + break; + case "}": + braceDepth--; + + /* we've reached the end of a definition */ + if (braceDepth == 0) { + /* we have something somewhat valid I guess */ + if (currentDef.m_strName != "") { + g_bots[g_botScriptCount].m_strNetName = currentDef.m_strNetName; + g_bots[g_botScriptCount].m_strExtra = currentDef.m_strExtra; + + if (g_bots[g_botScriptCount].m_strNetName == "") { + g_bots[g_botScriptCount].m_strNetName = currentDef.m_strName; + } + + g_bots[g_botScriptCount].m_strName = strtolower(currentDef.m_strName); + + /* increment the def count */ + if (g_botScriptCount < BOTSCRIPT_MAX) + g_botScriptCount++; + } + currentDef.m_strName = ""; + currentDef.m_strNetName = ""; + currentDef.m_strExtra = ""; + } + break; + default: + if (braceDepth == 1) { + if (word == "name") { + currentDef.m_strName = argv(i+1); + i++; + } else if (word == "funname") { + currentDef.m_strNetName = argv(i+1); + i++; + } else { /* rest gets dumped into extra */ + currentDef.m_strExtra = strcat(currentDef.m_strExtra, "\"", word, "\"", " "); + } + } + } + } + } + + fclose(botScript); + print(sprintf("%i bots parsed\n", g_botScriptCount)); +} + +void +Bot_ListBotProfiles_f(void) +{ + if (!g_botScriptCount) { + print("no bot profiles found.\n"); + return; + } + + for (int i = 0; i < g_botScriptCount; i++) { + print(sprintf("%i: %s\n", i, g_bots[i].m_strName)); + print(sprintf("\t%S\n", g_bots[i].m_strNetName)); + print(sprintf("\t%s\n", g_bots[i].m_strExtra)); + } +} \ No newline at end of file diff --git a/src/botlib/route.qc b/src/botlib/route.qc index 8dbad11c..01db0844 100644 --- a/src/botlib/route.qc +++ b/src/botlib/route.qc @@ -159,7 +159,7 @@ Route_SelectRandomSpot(void) } vector -Route_SelectDestination(bot target) +Route_SelectDestination(NSBot target) { CGameRules rules; rules = (CGameRules)g_grMode; diff --git a/src/botlib/way.qc b/src/botlib/way.qc index bdf7d7b4..07c82519 100644 --- a/src/botlib/way.qc +++ b/src/botlib/way.qc @@ -479,8 +479,8 @@ Way_GoToPoint(entity pl) for (entity a = world; (a = find(a, classname, "player"));) { if (clienttype(a) != CLIENTTYPE_REAL) { - bot targ; - targ = (bot)a; + NSBot targ; + targ = (NSBot)a; targ.RouteClear(); targ.RouteToPosition(pl.origin); print(sprintf("Told bot to go to %v\n", trace_endpos)); diff --git a/src/client/cmd.qc b/src/client/cmd.qc index 81d6f367..b7827f40 100644 --- a/src/client/cmd.qc +++ b/src/client/cmd.qc @@ -432,6 +432,47 @@ Cmd_Parse(string sCMD) case "-menu_right": pSeat->m_iInputReload = FALSE; break; + /* client aliases for server commands */ + case "addBot": + localcmd(sprintf("sv addBot %s\n", argv(1))); + break; + case "killAllBots": + localcmd(sprintf("sv killAllBots %s\n", argv(1))); + break; + case "resetAllBotsGoals": + localcmd(sprintf("sv resetAllBotsGoals %s\n", argv(1))); + break; + case "killClass": + localcmd(sprintf("sv killClass %s\n", argv(1))); + break; + case "killMovables": + localcmd(sprintf("sv killMovables %s\n", argv(1))); + break; + case "trigger": + localcmd(sprintf("sv trigger %s\n", argv(1))); + break; + case "input": + localcmd(sprintf("sv input %s\n", argv(1))); + break; + case "listBotProfiles": + localcmd(sprintf("sv listBotProfiles %s\n", argv(1))); + break; + case "listTargets": + localcmd(sprintf("sv listTargets %s\n", argv(1))); + break; + case "teleport": + localcmd(sprintf("sv teleport %s\n", argv(1))); + break; + case "teleportToClass": + localcmd(sprintf("sv teleportToClass %s\n", argv(1))); + break; + case "respawnEntities": + localcmd(sprintf("sv respawnEntities %s\n", argv(1))); + break; + case "spawn": + localcmd(sprintf("sv spawn %s\n", argv(1))); + break; + default: return (false); } @@ -459,6 +500,21 @@ Cmd_Init(void) registercommand("listClientSoundDef"); registercommand("listServerSoundDef"); + /* server commands */ + registercommand("addBot"); + registercommand("killAllBots"); + registercommand("resetAllBotsGoals"); + registercommand("killClass"); + registercommand("killMovables"); + registercommand("trigger"); + registercommand("input"); + registercommand("listTargets"); + registercommand("teleport"); + registercommand("teleportToClass"); + registercommand("respawnEntities"); + registercommand("spawn"); + registercommand("listBotProfiles"); + registercommand("cleardecals"); registercommand("testLight"); registercommand("testPointLight"); diff --git a/src/server/entityDef.qc b/src/server/entityDef.qc index 44555cc7..ec20c429 100644 --- a/src/server/entityDef.qc +++ b/src/server/entityDef.qc @@ -493,10 +493,12 @@ EntityDef_SpawnClassname(string className) for (int i = 0i; i < g_entDefCount; i++) { if (className == g_entDefTable[i].entClass) { EntityDef_Precaches(i); + NSLog("Spawning eDef %S", className); return EntityDef_PrepareEntity(self, i); } } + NSLog("^1Failed spawning eDef %S", className); return __NULL__; } diff --git a/src/server/entry.qc b/src/server/entry.qc index 3875c0dc..7ceedd45 100644 --- a/src/server/entry.qc +++ b/src/server/entry.qc @@ -54,7 +54,8 @@ ClientConnect(void) /* bot needs special init */ #ifdef BOT_INCLUDED if (clienttype(self) == CLIENTTYPE_BOT) { - spawnfunc_bot(); + /* from now on we're of type NSBot */ + EntityDef_SpawnClassname("bot"); } else #endif @@ -182,7 +183,7 @@ PlayerPreThink(void) #ifdef BOT_INCLUDED if (clienttype(self) == CLIENTTYPE_BOT) { - ((bot)self).PreFrame(); + ((NSBot)self).PreFrame(); } #endif @@ -208,7 +209,7 @@ PlayerPostThink(void) #ifdef BOT_INCLUDED if (clienttype(self) == CLIENTTYPE_BOT) { - ((bot)self).PostFrame(); + ((NSBot)self).PostFrame(); } #endif @@ -458,10 +459,14 @@ initents(void) g_ents_initialized = TRUE; /* engine hacks for dedicated servers */ - cvar_set("s_nominaldistance", "1000"); + cvar_set("s_nominaldistance", "2048"); /* other engine hacks */ cvar_set("sv_nqplayerphysics", "0"); + +#ifdef BOT_INCLUDED + BotLib_Init(); +#endif } /** Any command executed on the server (either tty, rcon or `sv`) gets @@ -507,12 +512,40 @@ ConsoleCmd(string cmd) /* time to handle commands that apply to all games */ tokenize(cmd); switch (argv(0)) { - case "trigger_ent": + case "addBot": + Bot_AddBot_f(strtolower(argv(1))); + break; + case "killAllBots": + Bot_KillAllBots(); + break; + case "resetAllBotsGoals": + Bot_ResetAllBotsGoals(); + break; + case "listBotProfiles": + Bot_ListBotProfiles_f(); + break; + case "killClass": + string targetClass; + targetClass = argv(1); + + if (targetClass) + for (entity a = world; (a = find(a, ::classname, targetClass));) { + NSEntity t = (NSEntity)a; + t.Destroy(); + } + break; + case "killMovables": + for (entity a = world; (a = findfloat(a, ::movetype, MOVETYPE_PHYSICS));) { + NSEntity t = (NSEntity)a; + t.Destroy(); + } + break; + case "trigger": string targ; targ = argv(1); if (targ) - for (entity a = world; (a = find(a, ::targetname, argv(1)));) { + for (entity a = world; (a = find(a, ::targetname, targ));) { NSEntity t = (NSEntity)a; if (t.Trigger) @@ -530,34 +563,42 @@ ConsoleCmd(string cmd) print(sprintf("Sending input to %d, %S: %S\n", entNum, inputName, inputData)); } break; - case "goto_ent": + case "listTargets": + for (entity a = world; (a = findfloat(a, ::identity, 1));) { + if (a.targetname) { + print(sprintf("%d: %s (%s)\n", num_for_edict(a), a.targetname, a.classname)); + } + } + break; + case "teleport": + static entity targetFinder; + targetFinder = find(targetFinder, ::targetname, argv(1)); + + /* try at least one more time to skip world */ + if (!targetFinder) + targetFinder = find(targetFinder, ::targetname, argv(1)); + + if (targetFinder) + setorigin(pl, targetFinder.origin); + break; + case "teleportToClass": static entity finder; finder = find(finder, ::classname, argv(1)); + /* try at least one more time to skip world */ + if (!finder) + finder = find(finder, ::classname, argv(1)); + if (finder) setorigin(pl, finder.origin); break; - case "respawn_ents": + case "respawnEntities": for (entity a = world; (a = findfloat(a, ::identity, 1));) { NSEntity ent = (NSEntity)a; ent.Respawn(); } break; case "spawn": - entity eDef = spawn(); - eDef.classname = strcat("spawnfunc_", argv(1)); - self = eDef; - callfunction(self.classname); - self = pl; - - makevectors(pl.v_angle); - if (eDef.identity == 1) { - NSEntity ent = (NSEntity)eDef; - } - traceline(pl.origin, pl.origin + (v_forward * 1024), MOVE_NORMAL, pl); - setorigin(eDef, trace_endpos); - break; - case "spawndef": NSEntity unit = EntityDef_CreateClassname(argv(1)); makevectors(pl.v_angle); traceline(pl.origin, pl.origin + (v_forward * 1024), MOVE_NORMAL, pl); diff --git a/src/shared/NSClientPlayer.qc b/src/shared/NSClientPlayer.qc index d353a1c2..387dc20f 100644 --- a/src/shared/NSClientPlayer.qc +++ b/src/shared/NSClientPlayer.qc @@ -666,7 +666,7 @@ NSClientPlayer::ServerInputFrame(void) #ifdef BOT_INCLUDED /* wait a few seconds, as we may not have been spawned yet */ if (clienttype(this) == CLIENTTYPE_BOT) { - ((bot)this).RunAI(); + ((NSBot)this).RunAI(); } #endif