Add a whole bunch of new bot features, including bot profiles.
Developers: the internal class `bot` is now `NSBot`, and an entityDef of the old name will be instantiated instead. Override defs/bot.def in your mod and make it use any custom spawnclass you wish. Now games don't have to override `addbot` or `bot_add` inside the multiplayer game rules. There's also more console commands. Clients now have access to: addBot, killAllBots, killClass [classname], killMovables, trigger [targetname], input [entnum] [input] [data], listBotProfiles, listTargets, teleport [targetname], teleportToClass [classname], respawnEntities, spawn
This commit is contained in:
parent
ba4ddbd3f6
commit
4a8f4a6082
18 changed files with 510 additions and 62 deletions
|
@ -109,9 +109,6 @@ MultiplayerRules::ConsoleCommand(NSClientPlayer pp, string cmd)
|
||||||
tokenize(cmd);
|
tokenize(cmd);
|
||||||
|
|
||||||
switch (argv(0)) {
|
switch (argv(0)) {
|
||||||
case "bot_add":
|
|
||||||
Bot_AddQuick();
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
|
@ -129,4 +126,4 @@ Game_InitRules(void)
|
||||||
} else {
|
} else {
|
||||||
g_grMode = spawn(MultiplayerRules);
|
g_grMode = spawn(MultiplayerRules);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
4
platform/base_scripts.pk3dir/def/bot.def
Normal file
4
platform/base_scripts.pk3dir/def/bot.def
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
entityDef bot
|
||||||
|
{
|
||||||
|
spawnclass NSBot
|
||||||
|
}
|
|
@ -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_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.
|
seta com_showTracers 0 // Debug display for tracelines, networking intensive.
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ typedef enum
|
||||||
|
|
||||||
/** Base class for the Bot AI.
|
/** Base class for the Bot AI.
|
||||||
*/
|
*/
|
||||||
class bot:player
|
class NSBot:player
|
||||||
{
|
{
|
||||||
/* routing */
|
/* routing */
|
||||||
int m_iNodes;
|
int m_iNodes;
|
||||||
|
@ -74,7 +74,7 @@ class bot:player
|
||||||
float m_flForceWeaponAttack;
|
float m_flForceWeaponAttack;
|
||||||
vector m_vecForceWeaponAttackPos;
|
vector m_vecForceWeaponAttackPos;
|
||||||
|
|
||||||
void(void) bot;
|
void(void) NSBot;
|
||||||
|
|
||||||
virtual void(botstate_t) SetState;
|
virtual void(botstate_t) SetState;
|
||||||
virtual botstate_t(void) GetState;
|
virtual botstate_t(void) GetState;
|
||||||
|
@ -107,7 +107,7 @@ entity Bot_AddQuick(void);
|
||||||
|
|
||||||
/** Applies random custom colors to the given bot entity. */
|
/** Applies random custom colors to the given bot entity. */
|
||||||
void
|
void
|
||||||
Bot_RandomColormap(bot target)
|
Bot_RandomColormap(NSBot target)
|
||||||
{
|
{
|
||||||
vector x = hsv2rgb(random() * 360, 100, 100);
|
vector x = hsv2rgb(random() * 360, 100, 100);
|
||||||
float top = x[2] + (x[1] << 8) + (x[0] << 16);
|
float top = x[2] + (x[1] << 8) + (x[0] << 16);
|
||||||
|
|
|
@ -15,44 +15,44 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
botstate_t
|
botstate_t
|
||||||
bot::GetState(void)
|
NSBot::GetState(void)
|
||||||
{
|
{
|
||||||
return m_bsState;
|
return m_bsState;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::SetState(botstate_t state)
|
NSBot::SetState(botstate_t state)
|
||||||
{
|
{
|
||||||
m_bsState = state;
|
m_bsState = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
botpersonality_t
|
botpersonality_t
|
||||||
bot::GetPersonality(void)
|
NSBot::GetPersonality(void)
|
||||||
{
|
{
|
||||||
return m_bpPersonality;
|
return m_bpPersonality;
|
||||||
}
|
}
|
||||||
|
|
||||||
float
|
float
|
||||||
bot::GetWalkSpeed(void)
|
NSBot::GetWalkSpeed(void)
|
||||||
{
|
{
|
||||||
return 120;
|
return 120;
|
||||||
}
|
}
|
||||||
|
|
||||||
float
|
float
|
||||||
bot::GetRunSpeed(void)
|
NSBot::GetRunSpeed(void)
|
||||||
{
|
{
|
||||||
return 240;
|
return 240;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::RouteClear(void)
|
NSBot::RouteClear(void)
|
||||||
{
|
{
|
||||||
super::RouteClear();
|
super::RouteClear();
|
||||||
m_flNodeGiveup = 0.0f;
|
m_flNodeGiveup = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::BrainThink(int enemyvisible, int enemydistant)
|
NSBot::BrainThink(int enemyvisible, int enemydistant)
|
||||||
{
|
{
|
||||||
/* we had a target and it's now dead. now what? */
|
/* we had a target and it's now dead. now what? */
|
||||||
if (m_eTarget) {
|
if (m_eTarget) {
|
||||||
|
@ -67,7 +67,7 @@ bot::BrainThink(int enemyvisible, int enemydistant)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::UseButton(void)
|
NSBot::UseButton(void)
|
||||||
{
|
{
|
||||||
float bestDist;
|
float bestDist;
|
||||||
func_button bestButton = __NULL__;
|
func_button bestButton = __NULL__;
|
||||||
|
@ -96,7 +96,7 @@ bot::UseButton(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::SeeThink(void)
|
NSBot::SeeThink(void)
|
||||||
{
|
{
|
||||||
NSGameRules rules = (NSGameRules)g_grMode;
|
NSGameRules rules = (NSGameRules)g_grMode;
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ bot::SeeThink(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::CheckRoute(void)
|
NSBot::CheckRoute(void)
|
||||||
{
|
{
|
||||||
float flDist;
|
float flDist;
|
||||||
vector vecEndPos;
|
vector vecEndPos;
|
||||||
|
@ -193,7 +193,7 @@ bot::CheckRoute(void)
|
||||||
|
|
||||||
/* we're inside the radius */
|
/* we're inside the radius */
|
||||||
if (flDist <= flRadius) {
|
if (flDist <= flRadius) {
|
||||||
NSLog("^2bot::^3CheckRoute^7: " \
|
NSLog("^2NSBot::^3CheckRoute^7: " \
|
||||||
"%s reached node\n", this.targetname);
|
"%s reached node\n", this.targetname);
|
||||||
m_iCurNode--;
|
m_iCurNode--;
|
||||||
|
|
||||||
|
@ -218,7 +218,7 @@ bot::CheckRoute(void)
|
||||||
|
|
||||||
/* can we walk directly to our target destination? */
|
/* can we walk directly to our target destination? */
|
||||||
if (trace_fraction == 1.0) {
|
if (trace_fraction == 1.0) {
|
||||||
print("^2bot::^3CheckRoute^7: " \
|
print("^2NSBot::^3CheckRoute^7: " \
|
||||||
"Walking directly to last node\n");
|
"Walking directly to last node\n");
|
||||||
m_iCurNode = -1;
|
m_iCurNode = -1;
|
||||||
}
|
}
|
||||||
|
@ -261,13 +261,13 @@ bot::CheckRoute(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::CreateObjective(void)
|
NSBot::CreateObjective(void)
|
||||||
{
|
{
|
||||||
RouteToPosition(Route_SelectDestination(this));
|
RouteToPosition(Route_SelectDestination(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::RunAI(void)
|
NSBot::RunAI(void)
|
||||||
{
|
{
|
||||||
vector aimDir, aimPos;
|
vector aimDir, aimPos;
|
||||||
bool enemyVisible, enemyDistant;
|
bool enemyVisible, enemyDistant;
|
||||||
|
@ -294,7 +294,7 @@ bot::RunAI(void)
|
||||||
if (!m_iNodes && autocvar_bot_aimless == 0) {
|
if (!m_iNodes && autocvar_bot_aimless == 0) {
|
||||||
CreateObjective();
|
CreateObjective();
|
||||||
|
|
||||||
NSLog("bot::RunAI: %s is calculating first bot route",
|
NSLog("NSBot::RunAI: %s is calculating first bot route",
|
||||||
this.netname);
|
this.netname);
|
||||||
|
|
||||||
/* our route probably has not been processed yet */
|
/* our route probably has not been processed yet */
|
||||||
|
@ -500,12 +500,12 @@ bot::RunAI(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::PreFrame(void)
|
NSBot::PreFrame(void)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::PostFrame(void)
|
NSBot::PostFrame(void)
|
||||||
{
|
{
|
||||||
#ifndef NEW_INVENTORY
|
#ifndef NEW_INVENTORY
|
||||||
/* we've picked something new up */
|
/* we've picked something new up */
|
||||||
|
@ -518,7 +518,7 @@ bot::PostFrame(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::SetName(string nickname)
|
NSBot::SetName(string nickname)
|
||||||
{
|
{
|
||||||
if (autocvar_bot_prefix)
|
if (autocvar_bot_prefix)
|
||||||
forceinfokey(this, "name", sprintf("%s %s", autocvar_bot_prefix, nickname));
|
forceinfokey(this, "name", sprintf("%s %s", autocvar_bot_prefix, nickname));
|
||||||
|
@ -527,9 +527,66 @@ bot::SetName(string nickname)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::bot(void)
|
NSBot::NSBot(void)
|
||||||
{
|
{
|
||||||
classname = "player";
|
classname = "player";
|
||||||
targetname = "_nuclide_bot_";
|
targetname = "_nuclide_bot_";
|
||||||
forceinfokey(this, "*bot", "1");
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -15,13 +15,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::ChatSay(string msg)
|
NSBot::ChatSay(string msg)
|
||||||
{
|
{
|
||||||
g_grMode.ChatMessageAll(this, msg);
|
g_grMode.ChatMessageAll(this, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::ChatSayTeam(string msg)
|
NSBot::ChatSayTeam(string msg)
|
||||||
{
|
{
|
||||||
g_grMode.ChatMessageTeam(this, msg);
|
g_grMode.ChatMessageTeam(this, msg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::Pain(void)
|
NSBot::Pain(void)
|
||||||
{
|
{
|
||||||
NSGameRules rules = g_grMode;
|
NSGameRules rules = g_grMode;
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ bot::Pain(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::SetEnemy(entity en)
|
NSBot::SetEnemy(entity en)
|
||||||
{
|
{
|
||||||
m_eTarget = en;
|
m_eTarget = en;
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ bot::SetEnemy(entity en)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::WeaponThink(void)
|
NSBot::WeaponThink(void)
|
||||||
{
|
{
|
||||||
int r = Weapons_IsEmpty(this, activeweapon);
|
int r = Weapons_IsEmpty(this, activeweapon);
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ bot::WeaponThink(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::WeaponAttack(void)
|
NSBot::WeaponAttack(void)
|
||||||
{
|
{
|
||||||
bool shouldAttack = false;
|
bool shouldAttack = false;
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ bot::WeaponAttack(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
bot::ForceWeaponAttack(vector attackPos, float attackTime)
|
NSBot::ForceWeaponAttack(vector attackPos, float attackTime)
|
||||||
{
|
{
|
||||||
m_vecForceWeaponAttackPos = attackPos;
|
m_vecForceWeaponAttackPos = attackPos;
|
||||||
m_flForceWeaponAttack = attackTime + time;
|
m_flForceWeaponAttack = attackTime + time;
|
||||||
|
@ -153,7 +153,7 @@ BotLib_Alert(vector pos, float radius, float t)
|
||||||
if (vlen(pos - w.origin) > radius)
|
if (vlen(pos - w.origin) > radius)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
bot f = (bot) w;
|
NSBot f = (NSBot) w;
|
||||||
|
|
||||||
/* they already got a target of some kind */
|
/* they already got a target of some kind */
|
||||||
if (f.m_eTarget)
|
if (f.m_eTarget)
|
||||||
|
|
|
@ -61,3 +61,30 @@ Bot_AddQuick(void)
|
||||||
self = oself;
|
self = oself;
|
||||||
return (newbot);
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "bot.h"
|
#include "bot.h"
|
||||||
#include "botinfo.h"
|
#include "botinfo.h"
|
||||||
#include "cvar.h"
|
#include "cvar.h"
|
||||||
|
#include "profiles.h"
|
||||||
|
|
||||||
|
|
||||||
vector Route_SelectDestination( bot target );
|
vector Route_SelectDestination( NSBot target );
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#includelist
|
#includelist
|
||||||
defs.h
|
defs.h
|
||||||
|
profiles.qc
|
||||||
bot.qc
|
bot.qc
|
||||||
bot_chat.qc
|
bot_chat.qc
|
||||||
bot_combat.qc
|
bot_combat.qc
|
||||||
|
|
42
src/botlib/profiles.h
Normal file
42
src/botlib/profiles.h
Normal file
|
@ -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;
|
211
src/botlib/profiles.qc
Normal file
211
src/botlib/profiles.qc
Normal file
|
@ -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 <botname> [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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -159,7 +159,7 @@ Route_SelectRandomSpot(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
vector
|
vector
|
||||||
Route_SelectDestination(bot target)
|
Route_SelectDestination(NSBot target)
|
||||||
{
|
{
|
||||||
CGameRules rules;
|
CGameRules rules;
|
||||||
rules = (CGameRules)g_grMode;
|
rules = (CGameRules)g_grMode;
|
||||||
|
|
|
@ -479,8 +479,8 @@ Way_GoToPoint(entity pl)
|
||||||
|
|
||||||
for (entity a = world; (a = find(a, classname, "player"));) {
|
for (entity a = world; (a = find(a, classname, "player"));) {
|
||||||
if (clienttype(a) != CLIENTTYPE_REAL) {
|
if (clienttype(a) != CLIENTTYPE_REAL) {
|
||||||
bot targ;
|
NSBot targ;
|
||||||
targ = (bot)a;
|
targ = (NSBot)a;
|
||||||
targ.RouteClear();
|
targ.RouteClear();
|
||||||
targ.RouteToPosition(pl.origin);
|
targ.RouteToPosition(pl.origin);
|
||||||
print(sprintf("Told bot to go to %v\n", trace_endpos));
|
print(sprintf("Told bot to go to %v\n", trace_endpos));
|
||||||
|
|
|
@ -432,6 +432,47 @@ Cmd_Parse(string sCMD)
|
||||||
case "-menu_right":
|
case "-menu_right":
|
||||||
pSeat->m_iInputReload = FALSE;
|
pSeat->m_iInputReload = FALSE;
|
||||||
break;
|
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:
|
default:
|
||||||
return (false);
|
return (false);
|
||||||
}
|
}
|
||||||
|
@ -459,6 +500,21 @@ Cmd_Init(void)
|
||||||
registercommand("listClientSoundDef");
|
registercommand("listClientSoundDef");
|
||||||
registercommand("listServerSoundDef");
|
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("cleardecals");
|
||||||
registercommand("testLight");
|
registercommand("testLight");
|
||||||
registercommand("testPointLight");
|
registercommand("testPointLight");
|
||||||
|
|
|
@ -493,10 +493,12 @@ EntityDef_SpawnClassname(string className)
|
||||||
for (int i = 0i; i < g_entDefCount; i++) {
|
for (int i = 0i; i < g_entDefCount; i++) {
|
||||||
if (className == g_entDefTable[i].entClass) {
|
if (className == g_entDefTable[i].entClass) {
|
||||||
EntityDef_Precaches(i);
|
EntityDef_Precaches(i);
|
||||||
|
NSLog("Spawning eDef %S", className);
|
||||||
return EntityDef_PrepareEntity(self, i);
|
return EntityDef_PrepareEntity(self, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NSLog("^1Failed spawning eDef %S", className);
|
||||||
return __NULL__;
|
return __NULL__;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,8 @@ ClientConnect(void)
|
||||||
/* bot needs special init */
|
/* bot needs special init */
|
||||||
#ifdef BOT_INCLUDED
|
#ifdef BOT_INCLUDED
|
||||||
if (clienttype(self) == CLIENTTYPE_BOT) {
|
if (clienttype(self) == CLIENTTYPE_BOT) {
|
||||||
spawnfunc_bot();
|
/* from now on we're of type NSBot */
|
||||||
|
EntityDef_SpawnClassname("bot");
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -182,7 +183,7 @@ PlayerPreThink(void)
|
||||||
|
|
||||||
#ifdef BOT_INCLUDED
|
#ifdef BOT_INCLUDED
|
||||||
if (clienttype(self) == CLIENTTYPE_BOT) {
|
if (clienttype(self) == CLIENTTYPE_BOT) {
|
||||||
((bot)self).PreFrame();
|
((NSBot)self).PreFrame();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -208,7 +209,7 @@ PlayerPostThink(void)
|
||||||
|
|
||||||
#ifdef BOT_INCLUDED
|
#ifdef BOT_INCLUDED
|
||||||
if (clienttype(self) == CLIENTTYPE_BOT) {
|
if (clienttype(self) == CLIENTTYPE_BOT) {
|
||||||
((bot)self).PostFrame();
|
((NSBot)self).PostFrame();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -458,10 +459,14 @@ initents(void)
|
||||||
g_ents_initialized = TRUE;
|
g_ents_initialized = TRUE;
|
||||||
|
|
||||||
/* engine hacks for dedicated servers */
|
/* engine hacks for dedicated servers */
|
||||||
cvar_set("s_nominaldistance", "1000");
|
cvar_set("s_nominaldistance", "2048");
|
||||||
|
|
||||||
/* other engine hacks */
|
/* other engine hacks */
|
||||||
cvar_set("sv_nqplayerphysics", "0");
|
cvar_set("sv_nqplayerphysics", "0");
|
||||||
|
|
||||||
|
#ifdef BOT_INCLUDED
|
||||||
|
BotLib_Init();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Any command executed on the server (either tty, rcon or `sv`) gets
|
/** 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 */
|
/* time to handle commands that apply to all games */
|
||||||
tokenize(cmd);
|
tokenize(cmd);
|
||||||
switch (argv(0)) {
|
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;
|
string targ;
|
||||||
targ = argv(1);
|
targ = argv(1);
|
||||||
|
|
||||||
if (targ)
|
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;
|
NSEntity t = (NSEntity)a;
|
||||||
|
|
||||||
if (t.Trigger)
|
if (t.Trigger)
|
||||||
|
@ -530,34 +563,42 @@ ConsoleCmd(string cmd)
|
||||||
print(sprintf("Sending input to %d, %S: %S\n", entNum, inputName, inputData));
|
print(sprintf("Sending input to %d, %S: %S\n", entNum, inputName, inputData));
|
||||||
}
|
}
|
||||||
break;
|
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;
|
static entity finder;
|
||||||
finder = find(finder, ::classname, argv(1));
|
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)
|
if (finder)
|
||||||
setorigin(pl, finder.origin);
|
setorigin(pl, finder.origin);
|
||||||
break;
|
break;
|
||||||
case "respawn_ents":
|
case "respawnEntities":
|
||||||
for (entity a = world; (a = findfloat(a, ::identity, 1));) {
|
for (entity a = world; (a = findfloat(a, ::identity, 1));) {
|
||||||
NSEntity ent = (NSEntity)a;
|
NSEntity ent = (NSEntity)a;
|
||||||
ent.Respawn();
|
ent.Respawn();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "spawn":
|
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));
|
NSEntity unit = EntityDef_CreateClassname(argv(1));
|
||||||
makevectors(pl.v_angle);
|
makevectors(pl.v_angle);
|
||||||
traceline(pl.origin, pl.origin + (v_forward * 1024), MOVE_NORMAL, pl);
|
traceline(pl.origin, pl.origin + (v_forward * 1024), MOVE_NORMAL, pl);
|
||||||
|
|
|
@ -666,7 +666,7 @@ NSClientPlayer::ServerInputFrame(void)
|
||||||
#ifdef BOT_INCLUDED
|
#ifdef BOT_INCLUDED
|
||||||
/* wait a few seconds, as we may not have been spawned yet */
|
/* wait a few seconds, as we may not have been spawned yet */
|
||||||
if (clienttype(this) == CLIENTTYPE_BOT) {
|
if (clienttype(this) == CLIENTTYPE_BOT) {
|
||||||
((bot)this).RunAI();
|
((NSBot)this).RunAI();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue