/*
===========================================================================
Wolfenstein: Enemy Territory GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
This file is part of the Wolfenstein: Enemy Territory GPL Source Code (Wolf ET Source Code).
Wolf ET Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Wolf ET Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Wolf ET Source Code. If not, see .
In addition, the Wolf: ET Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Wolf ET Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
//===========================================================================
//
// Name: ai_script_actions.c
// Function: Wolfenstein Bot Scripting
// Programmer: Ridah
// Tab Size: 4 (real tabs)
//===========================================================================
#include "../game/g_local.h"
#include "../game/q_shared.h"
#include "../game/botlib.h" //bot lib interface
#include "../game/be_aas.h"
#include "../game/be_ea.h"
#include "../game/be_ai_gen.h"
#include "../game/be_ai_goal.h"
#include "../game/be_ai_move.h"
#include "../botai/botai.h" //bot ai interface
#include "inv.h" //indexes into the inventory
#include "ai_main.h"
#include "ai_dmq3.h"
/*
Contains the code to handle the various commands available with an event script.
These functions will return true if the action has been performed, and the script
should proceed to the next item on the list.
*/
qboolean Bot_ScriptAction_SetSpeed( bot_state_t* bs, char *params ) {
vec3_t speed;
char* pString;
int i;
char* token;
pString = params;
for ( i = 0; i < 3; i++ ) {
token = COM_Parse( &pString );
if ( !token || !*token ) {
G_Error( "G_Scripting: syntax: setspeed \n" );
}
speed[i] = atoi( token );
}
VectorAdd( g_entities[bs->entitynum].client->ps.velocity, speed, g_entities[bs->entitynum].client->ps.velocity );
return qtrue;
}
/*
=================
Bot_ScriptError
=================
*/
void Bot_ScriptError( bot_state_t *bs, char *fmt, ... ) {
va_list ap;
char text[512];
//
va_start( ap, fmt );
Q_vsnprintf( text, sizeof( text ), fmt, ap );
if ( strlen( text ) >= sizeof( text ) ) {
text[sizeof( text ) - 1] = '\0';
}
//
G_Error( "BotScript (line %i): %s", bs->script.status.currentItem->lineNum, text );
//
va_end( ap );
}
/*
=================
Bot_ScriptAction_Print
syntax: print
Mostly for debugging purposes
=================
*/
qboolean Bot_ScriptAction_Print( bot_state_t *bs, char *params ) {
char *pString, *token, *printThis;
int printLevel = 0;
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "print requires some text" );
}
// Start parsing at the beginning of the string
pString = params;
// Default to printing whole string
printThis = params;
// See if the first parameter is a /N, where N is a number
if ( ( token = COM_ParseExt( &pString, qfalse ) ) && token[0] == '/' ) {
// Get the integer version of the print debug level
printLevel = atoi( &( token[1] ) );
// Just print what's left
printThis = pString;
}
// Only print if our debug level is as high as the print level
if ( g_scriptDebugLevel.integer >= printLevel ) {
// Print the statement
G_Printf( "(BotScript) %s-> %s\n", g_entities[bs->entitynum].client->pers.netname, printThis );
} // if (g_scriptDebugLevel.integer >= printLevel)...
return qtrue;
}
/*
=================
Bot_ScriptAction_SetAccumToPlayerCount
syntax: SetAccumToPlayerCount [[ ] [ ] ...]
condition list: team [axis/allies], class [medic/engineer/etc], weapon [flamethrower/sniperrifle/etc], within_range
=================
*/
qboolean Bot_ScriptAction_SetAccumToPlayerCount( bot_state_t *bs, char *params ) {
char *pStr, *pStrBackup, *token;
int count, i, val, accum, weapons[2];
gitem_t *item = NULL;
gentity_t *ent;
byte validPlayers[MAX_CLIENTS];
// setup the list of starting validPlayers
memset( validPlayers, 0, sizeof( validPlayers ) );
count = 0;
for ( i = 0; i < level.maxclients; i++ ) {
if ( !g_entities[i].inuse ) {
continue;
}
if ( !g_entities[i].client ) {
continue;
}
if ( !g_entities[i].client->pers.connected != CON_CONNECTED ) {
continue;
}
validPlayers[i] = 1;
count++;
}
pStr = params;
token = COM_ParseExt( &pStr, qfalse );
if ( !token || !token[0] || ( token[0] < '0' ) || ( token[0] > '9' ) ) {
Bot_ScriptError( bs, "accum buffer index expected, %s found: SetAccumToPlayerCount %s", token, params );
}
accum = atoi( token );
if ( accum < 0 || accum >= MAX_SCRIPT_ACCUM_BUFFERS ) {
Bot_ScriptError( bs, "accum buffer index out of range, %s found (range is 0 - %i): SetAccumToPlayerCount %s", token, MAX_SCRIPT_ACCUM_BUFFERS - 1, params );
}
// eliminate them with each condition not met
while ( qtrue ) {
val = 0;
//
token = COM_ParseExt( &pStr, qfalse );
if ( !token || !token[0] ) {
// we're done
break;
}
//
if ( token[0] != '/' ) {
Bot_ScriptError( bs, "condition identifier expected, %s found: SetAccumToPlayerCount %s", token, params );
}
//
if ( !Q_stricmp( token, "/team" ) ) {
token = COM_ParseExt( &pStr, qfalse );
if ( !token || !token[0] || token[0] == '/' ) {
Bot_ScriptError( bs, "unexpected end of command: SetAccumToPlayerCount %s", params );
}
//
if ( !Q_stricmp( token, "axis" ) ) {
val = TEAM_AXIS;
} else if ( !Q_stricmp( token, "allies" ) ) {
val = TEAM_ALLIES;
} else {
Bot_ScriptError( bs, "unknown team \"%s\": SetAccumToPlayerCount %s", token, params );
}
// eliminate players
for ( i = 0; i < level.maxclients; i++ ) {
if ( !validPlayers[i] ) {
continue;
}
if ( g_entities[i].client->sess.sessionTeam != val ) {
validPlayers[i] = 0;
count--;
}
}
} else
//
if ( !Q_stricmp( token, "/class" ) ) {
token = COM_ParseExt( &pStr, qfalse );
if ( !token || !token[0] || token[0] == '/' ) {
Bot_ScriptError( bs, "unexpected end of command: SetAccumToPlayerCount %s", params );
}
//
val = Team_ClassForString( token );
if ( val < 0 ) {
Bot_ScriptError( bs, "unknown class \"%s\": SetAccumToPlayerCount %s", token, params );
}
// eliminate players
for ( i = 0; i < level.maxclients; i++ ) {
if ( !validPlayers[i] ) {
continue;
}
if ( g_entities[i].client->sess.playerType != val ) {
validPlayers[i] = 0;
count--;
}
}
} else
//
if ( !Q_stricmp( token, "/weapon" ) ) {
memset( weapons, 0, sizeof( weapons ) );
// for each weapon
while ( qtrue ) {
// read the weapon
token = COM_ParseExt( &pStr, qfalse );
if ( !token || !token[0] || token[0] == '/' ) {
Bot_ScriptError( bs, "unexpected end of command: SetAccumToPlayerCount %s", params );
}
//
if ( ( item = BG_FindItem( token ) ) ) {
if ( !item->giTag ) {
Bot_ScriptError( bs, "unknown weapon \"%s\": SetAccumToPlayerCount %s", token, params );
}
COM_BitSet( weapons, item->giTag );
} else {
Bot_ScriptError( bs, "unknown weapon \"%s\": SetAccumToPlayerCount %s", token, params );
}
//
pStrBackup = pStr;
token = COM_ParseExt( &pStr, qfalse );
if ( !token[0] || ( Q_stricmp( token, "or" ) != 0 ) ) {
// not OR, so drop out of here
pStr = pStrBackup;
break;
}
}
// eliminate players
for ( i = 0; i < level.maxclients; i++ ) {
if ( !validPlayers[i] ) {
continue;
}
if ( !( g_entities[i].client->ps.weapons[0] & weapons[0] ) && !( g_entities[i].client->ps.weapons[1] & weapons[1] ) ) {
validPlayers[i] = 0;
count--;
}
}
} else
//
if ( !Q_stricmp( token, "/within_range" ) ) {
// targetname
token = COM_ParseExt( &pStr, qfalse );
if ( !token || !token[0] || token[0] == '/' ) {
Bot_ScriptError( bs, "unexpected end of command: SetAccumToPlayerCount %s", params );
}
ent = G_FindByTargetname( NULL, token );
if ( !ent ) {
Bot_ScriptError( bs, "unknown spawn point \"%s\": SetAccumToPlayerCount %s", token, params );
}
// range
token = COM_ParseExt( &pStr, qfalse );
if ( !token || !token[0] || token[0] == '/' ) {
Bot_ScriptError( bs, "range expected, not found: SetAccumToPlayerCount %s", params );
}
//
// eliminate players
for ( i = 0; i < level.maxclients; i++ ) {
if ( !validPlayers[i] ) {
continue;
}
if ( VectorDistanceSquared( g_entities[i].r.currentOrigin, ent->s.origin ) > SQR( atof( token ) ) ) {
validPlayers[i] = 0;
count--;
}
}
}
}
//
bs->script.accumBuffer[accum] = count;
//
return qtrue;
}
/*
======================
Bot_ScriptAction_SpawnBot
see Svcmd_SpawnBot()
======================
*/
qboolean Bot_ScriptAction_SpawnBot( bot_state_t *bs, char *params ) {
//trap_SendConsoleCommand( EXEC_APPEND, va("spawnbot %s\n", params) );
G_SpawnBot( params );
return qtrue;
}
/*
=================
Bot_ScriptAction_Accum
syntax: accum
Commands:
accum inc
accum abort_if_less_than
accum abort_if_greater_than
accum abort_if_not_equal
accum abort_if_equal
accum set_to
accum random
accum bitset
accum bitclear
accum abort_if_bitset
accum abort_if_not_bitset
=================
*/
qboolean Bot_ScriptAction_Trigger( bot_state_t *bs, char *params );
qboolean Bot_ScriptAction_Accum( bot_state_t *bs, char *params ) {
char *pString, *token, lastToken[MAX_QPATH];
int bufferIndex;
pString = params;
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
Bot_ScriptError( bs, "accum: without a buffer index" );
}
bufferIndex = atoi( token );
if ( bufferIndex >= MAX_SCRIPT_ACCUM_BUFFERS ) {
Bot_ScriptError( bs, "accum: buffer is outside range (0 - %i)", MAX_SCRIPT_ACCUM_BUFFERS );
}
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
Bot_ScriptError( bs, "accum: without a command" );
}
Q_strncpyz( lastToken, token, sizeof( lastToken ) );
token = COM_ParseExt( &pString, qfalse );
if ( !Q_stricmp( lastToken, "inc" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "accum:: %s requires a parameter", lastToken );
}
bs->script.accumBuffer[bufferIndex] += atoi( token );
} else if ( !Q_stricmp( lastToken, "abort_if_less_than" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "accum: %s requires a parameter", lastToken );
}
if ( bs->script.accumBuffer[bufferIndex] < atoi( token ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_greater_than" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "accum: %s requires a parameter", lastToken );
}
if ( bs->script.accumBuffer[bufferIndex] > atoi( token ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_not_equal" ) || !Q_stricmp( lastToken, "abort_if_not_equals" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "accum: %s requires a parameter", lastToken );
}
if ( bs->script.accumBuffer[bufferIndex] != atoi( token ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_equal" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "accum: %s requires a parameter", lastToken );
}
if ( bs->script.accumBuffer[bufferIndex] == atoi( token ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "bitset" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "accum: %s requires a parameter", lastToken );
}
bs->script.accumBuffer[bufferIndex] |= ( 1 << atoi( token ) );
} else if ( !Q_stricmp( lastToken, "bitclear" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "accum: %s requires a parameter", lastToken );
}
bs->script.accumBuffer[bufferIndex] &= ~( 1 << atoi( token ) );
} else if ( !Q_stricmp( lastToken, "abort_if_bitset" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "accum: %s requires a parameter", lastToken );
}
if ( bs->script.accumBuffer[bufferIndex] & ( 1 << atoi( token ) ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_not_bitset" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "accum: %s requires a parameter", lastToken );
}
if ( !( bs->script.accumBuffer[bufferIndex] & ( 1 << atoi( token ) ) ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "set_to" ) || !Q_stricmp( lastToken, "set" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "accum: %s requires a parameter", lastToken );
}
bs->script.accumBuffer[bufferIndex] = atoi( token );
} else if ( !Q_stricmp( lastToken, "random" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "accum: %s requires a parameter", lastToken );
}
bs->script.accumBuffer[bufferIndex] = rand() % atoi( token );
} else if ( !Q_stricmp( lastToken, "trigger_if_equal" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "accum: %s requires a parameter", lastToken );
}
if ( bs->script.accumBuffer[bufferIndex] == atoi( token ) ) {
return Bot_ScriptAction_Trigger( bs, pString );
}
} else {
Bot_ScriptError( bs, "accum: %s: unknown command", params );
}
return qtrue;
}
/*
======================
Bot_ScriptAction_Wait
======================
*/
qboolean Bot_ScriptAction_Wait( bot_state_t *bs, char *params ) {
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "Wait requires a duration." );
}
if ( !atoi( params ) ) {
Bot_ScriptError( bs, "Wait has invalid duration." );
}
//
// Gordon: check <= 0 instead of == 0?
return ( bs->script.status.stackChangeTime < level.time - atoi( params ) );
}
/*
======================
Bot_ScriptAction_MoveToMarker
======================
*/
qboolean Bot_ScriptAction_MoveToMarker( bot_state_t *bs, char *params ) {
char *pString, *token;
g_serverEntity_t *target;
vec3_t vec;
// Gordon: 24/10/02
float radius = 24;
// TAT 11/14/2002 - This causes a crash if you have guys on patrol routes
// Why did we need it?
// Gordon: 27/11/02: dont want to teleport dead players, can move further down,
// but i see no reason a general early out wouldn't be as good
// why does it crash?
/*
// Gordon: 6/11/02
// cant move if we're dead...
if(g_entities[bs->entitynum].health <= 0) {
return qtrue;
}
*/
// TAT 8/20/2002
// If we have received a player command that overrides our movement scripts,
// we should end the script
if ( bs->overrideMovementScripts ) {
// We're done with this script
return qtrue;
// return qfalse;
}
// Did this move fail?
if ( bs->movementFailedBadly ) {
// Bot_ScriptError( bs, "MoveToMarker failed." );
// Cancel the move
bs->movementFailedBadly = qfalse;
// We're done here
return qtrue;
} // if (bs->movementFailedBadly)...
//
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "MoveToMarker requires a targetname." );
}
//
pString = params;
token = COM_ParseExt( &pString, qfalse );
target = FindServerEntity( NULL, SE_FOFS( name ), token );
if ( !target ) {
Bot_ScriptError( bs, "MoveToMarker has unknown targetname: \"%s\"", token );
}
//
bs->script.frameFlags |= BSFFL_MOVETOTARGET;
bs->script.entityNum = target->number;
bs->script.moveType = BSMT_DEFAULT;
//
while ( ( token = COM_ParseExt( &pString, qfalse ) ) && token[0] ) {
if ( !Q_stricmp( token, "/WALKING" ) ) {
bs->script.moveType = BSMT_WALKING;
} else if ( !Q_stricmp( token, "/CROUCHING" ) ) {
bs->script.moveType = BSMT_CROUCHING;
} else if ( !Q_stricmp( token, "/DIRECT" ) ) {
bs->script.frameFlags |= BSFFL_DIRECTMOVE;
// Gordon: 24/10/02 Adding ability to set custom radius for movement goal
} else if ( !Q_stricmp( token, "radius" ) ) {
token = COM_ParseExt( &pString, qfalse );
if ( !*token ) {
Bot_ScriptError( bs, "MoveToMarker with radius has no value" );
} else {
radius = atof( token );
}
// Gordon: 24/10/02 Adding ability to teleport a bot somewhere
} else if ( !Q_stricmp( token, "instant" ) ) {
TeleportPlayer( &g_entities[bs->entitynum], target->origin, g_entities[bs->entitynum].client->ps.viewangles );
return qtrue;
// Gordon: end
}
}
// START Mad Doctor I changes, 8/19/2002
// The check was too constrictive and failed too often. I may have made it too loose, but it seems to work.
// if we have passed or are close enough to the marker, then return qtrue
// Gordon: added custom radius ability, default is 24 as Ian had set here
if ( VectorDistanceSquared( bs->origin, target->origin ) < SQR( radius ) ) {
// if (VectorDistanceSquared( bs->origin, target->s.origin ) < SQR(12)) {
// END Mad Doctor I changes, 8/19/2002
return qtrue;
} else if ( ( bs->script.status.stackChangeTime < level.time - 500 ) && VectorDistanceSquared( bs->origin, target->origin ) < SQR( 48 ) ) {
VectorSubtract( target->origin, bs->origin, vec );
if ( DotProduct( bs->cur_ps.velocity, vec ) < 0 ) {
return qtrue;
}
}
//
return qfalse;
}
/*
=====================
Bot_ScriptAction_Trigger
=====================
*/
qboolean Bot_ScriptAction_Trigger( bot_state_t *bs, char *params ) {
gentity_t *trent, *ent;
char *pString, name[MAX_QPATH], trigger[MAX_QPATH], *token;
int oldId, i;
qboolean terminate, found;
// get the cast name
pString = params;
token = COM_ParseExt( &pString, qfalse );
Q_strncpyz( name, token, sizeof( name ) );
if ( !name[0] ) {
G_Error( "G_Scripting: trigger must have a name and an identifier\n" );
}
token = COM_ParseExt( &pString, qfalse );
Q_strncpyz( trigger, token, sizeof( trigger ) );
if ( !trigger[0] ) {
G_Error( "G_Scripting: trigger must have a name and an identifier\n" );
}
ent = BotGetEntity( bs->client );
// START Mad Doctor I changes, 8/14/2002
// Changes to fix bugs caused if you used "trigger Foo FooAction" within bot Foo's
// scripts instead of using "trigger self FooAction".
if ( ( !Q_stricmp( name, "self" ) ) || ( !Q_stricmp( name, ent->scriptName ) ) ) {
trent = ent;
oldId = bs->script.status.id;
Bot_ScriptEvent( bs->client, "trigger", trigger );
// See if we've popped back to the original script
return ( oldId == bs->script.status.id );
// return (oldId == trent->scriptStatus.scriptId);
// END Mad Doctor I changes, 8/14/2002
} else if ( !Q_stricmp( name, "global" ) ) {
terminate = qfalse;
found = qfalse;
// for all entities/bots
trent = g_entities;
for ( i = 0; i < level.num_entities; i++, trent++ ) {
if ( !trent->inuse ) {
continue;
}
if ( !trent->scriptName ) {
continue;
}
if ( !trent->scriptName[0] ) {
continue;
}
found = qtrue;
if ( !( trent->r.svFlags & SVF_BOT ) ) {
G_Script_ScriptEvent( trent, "trigger", trigger );
} else {
oldId = bs->script.status.id;
Bot_ScriptEvent( bs->client, "trigger", trigger );
// if the script changed, return false so we don't muck with it's variables
if ( ( trent == ent ) && ( oldId != bs->script.status.id ) ) {
terminate = qtrue;
}
}
}
//
if ( terminate ) {
return qfalse;
}
if ( found ) {
return qtrue;
}
} else {
terminate = qfalse;
found = qfalse;
// for all entities/bots with this scriptName
trent = NULL;
while ( ( trent = BotFindEntity( trent, FOFS( scriptName ), name ) ) ) {
found = qtrue;
if ( !( trent->r.svFlags & SVF_BOT ) ) {
oldId = trent->scriptStatus.scriptId;
G_Script_ScriptEvent( trent, "trigger", trigger );
// if the script changed, return false so we don't muck with it's variables
if ( ( trent == ent ) && ( oldId != trent->scriptStatus.scriptId ) ) {
terminate = qtrue;
}
} else {
Bot_ScriptEvent( trent->s.number, "trigger", trigger );
}
}
//
if ( terminate ) {
return qfalse;
}
// Did we find a bot?
if ( found ) {
// We found one, and triggered the action, so keep going on with our script
return qtrue;
} // if (found) ...
} // else...
// G_Error( "G_Scripting: trigger has unknown name: %s\n", name );
G_Printf( "G_Scripting: trigger has unknown name: %s\n", name );
return qfalse; // shutup the compiler
}
/*
=====================
Bot_ScriptAction_Logging
=====================
*/
qboolean Bot_ScriptAction_Logging( bot_state_t *bs, char *params ) {
struct tm *localTime;
time_t long_time;
char filename[MAX_QPATH];
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "Logging requires an ON/OFF" );
}
if ( !Q_stricmp( params, "ON" ) ) {
if ( bs->script.flags & BSFL_LOGGING ) {
// logging already started
return qtrue;
}
bs->script.flags |= BSFL_LOGGING;
// get the time/date
time( &long_time );
localTime = localtime( &long_time );
Q_strncpyz( filename, va( "BotLog_%s_[%i]_[%4i_%2i_%2i]_[%2i_%2i_%2i].txt", g_entities[bs->client].aiName, bs->client, 1900 + localTime->tm_year, 1 + localTime->tm_mon, 1 + localTime->tm_mday, localTime->tm_hour, localTime->tm_min, localTime->tm_sec ), sizeof( filename ) );
// open the log file
if ( trap_FS_FOpenFile( filename, &bs->script.logFile, FS_APPEND ) < 0 ) {
Bot_ScriptError( bs, "Cannot open file for logging: %s", filename );
}
} else if ( !Q_stricmp( params, "OFF" ) ) {
if ( !( bs->script.flags & BSFL_LOGGING ) ) {
// logging already started
return qtrue;
}
bs->script.flags &= ~BSFL_LOGGING;
trap_FS_FCloseFile( bs->script.logFile );
bs->script.logFile = 0;
} else {
Bot_ScriptError( bs, "Logging has unknown parameter (%s), expected ON/OFF", params );
}
return qtrue;
}
/*
=====================
Bot_ScriptAction_AbortIfWarmup
=====================
*/
qboolean Bot_ScriptAction_AbortIfWarmup( bot_state_t *bs, char *params ) {
if ( level.warmupTime ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
//
return qtrue;
}
char *botAttributeStrings[] =
{
"BOT_REACTION_TIME",
"BOT_AIM_ACCURACY",
"BOT_WIMP_FACTOR",
NULL
};
/*
=============
Bot_ScriptAction_SetAttribute
returns qfalse if error
=============
*/
qboolean Bot_ScriptAction_SetAttribute( bot_state_t *bs, char *params ) {
int i;
char *pString, *token;
// get the attribString
pString = params;
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
Bot_ScriptError( bs, "attribute string required" );
}
for ( i = 0; botAttributeStrings[i]; i++ ) {
if ( !Q_stricmp( botAttributeStrings[i], token ) ) {
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
Bot_ScriptError( bs, "attribute value required" );
}
bs->attribs[i] = atof( token );
return qtrue;
}
}
// Let's give a big old error right here!
Bot_ScriptError( bs, "SetAttribute: Invalid attribute %s.", token );
return qfalse;
}
/*
===============
Bot_ScriptAction_MountMG42
===============
*/
qboolean Bot_ScriptAction_MountMG42( bot_state_t *bs, char *params ) {
gentity_t *mg42, *mg42Spot;
//
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "MountMG42 requires a targetname" );
}
// find the mg42
mg42 = NULL;
while ( ( mg42 = BotFindNextStaticEntity( mg42, BOTSTATICENTITY_MG42 ) ) ) {
if ( !Q_stricmp( mg42->targetname, params ) ) {
break;
}
}
//
if ( !mg42 ) {
Bot_ScriptError( bs, "MountMG42: targetname \"%s\" not found", params );
}
//
mg42Spot = mg42->melee;
//
if ( !mg42Spot ) {
Bot_ScriptError( bs, "MountMG42: (internal error) mg42 (\"%s\") has invalid mg42_spot", params );
}
//
bs->script.flags |= BSFL_MOUNT_MG42;
bs->script.mg42entnum = mg42Spot->s.number;
//
return qtrue;
}
// START Mad Doctor I changes, 8/19/2002
/*
===============
Bot_ScriptAction_PlaySoundAtPlayer
===============
*/
qboolean Bot_ScriptAction_PlaySoundAtPlayer( bot_state_t *bs, char *params ) {
// We need to find out who the player is
gentity_t *player = NULL;
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "PlaySound requires a soundname" );
}
// Look up the player entity by name
// NOTE: This would need adjusting for COOP play.
player = BotFindEntityForName( "player" );
// Bail if no player exists
if ( player == NULL ) {
return qtrue;
}
//
G_AddEvent( player, EV_GENERAL_SOUND, G_SoundIndex( params ) );
//
return qtrue;
} // qboolean Bot_ScriptAction_PlaySoundAtPlayer( bot_state_t *bs, char *params )...
// END Mad Doctor I changes, 8/19/2002
extern qboolean AddWeaponToPlayer( gclient_t *client, weapon_t weapon, int ammo, int ammoclip, qboolean setcurrent );
/*
===============
Bot_ScriptAction_SetWeapon
===============
*/
qboolean Bot_ScriptAction_SetWeapon( bot_state_t *bs, char *params ) {
char userinfo[MAX_INFO_STRING];
// Which class are we?
int playerClass = g_entities[bs->client].client->sess.playerType;
int weapon;
//
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "SetWeapon requires a weapon name" );
}
weapon = Bot_GetWeaponForClassAndTeam( playerClass, g_entities[bs->client].client->sess.sessionTeam, params );
if ( weapon == -1 ) {
// can't use this weapon
Bot_ScriptError( bs, "Bot %s on team %s cannot use weapon %s\n", g_entities[bs->client].aiName, ( g_entities[bs->client].client->sess.sessionTeam == TEAM_AXIS ) ? "Axis" : "Allies", params );
}
if ( weapon == WP_NONE ) {
trap_GetUserinfo( bs->client, userinfo, sizeof( userinfo ) );
Info_SetValueForKey( userinfo, "pWeapon", "NONE" );
trap_SetUserinfo( bs->client, userinfo );
ClientUserinfoChanged( bs->client );
// TAT 12/26/2002 - make sure the bot state knows we have no weapon
bs->weaponnum = WP_NONE;
} else
{
gentity_t *player;
int i;
// make sure we tell all the clients that we have a weapon
for ( i = 0; i < level.numConnectedClients; i++ )
{
// send the noweapon command with who as the 1st param, and 0 meaning we don't have no weapon
player = g_entities + level.sortedClients[i];
if ( player->inuse && player->client->sess.sessionTeam == bs->mpTeam ) {
trap_SendServerCommand( player->s.number, va( "nwp %i 0", bs->client ) );
}
}
/*
trap_GetUserinfo( bs->client, userinfo, sizeof(userinfo) );
Info_SetValueForKey( userinfo, "pWeapon", va("%i", weapon));
trap_SetUserinfo( bs->client, userinfo );
ClientUserinfoChanged(bs->client);
*/
}
// set the weapon
g_entities[bs->client].client->sess.playerWeapon = weapon;
g_entities[bs->client].client->ps.weapon = weapon;
g_entities[bs->client].s.weapon = weapon;
// use this new func: don't bother with the whole weaponSpawnNumber thing
SetWolfSpawnWeapons( g_entities[bs->client].client );
if ( weapon != WP_NONE ) {
// AddWeaponToPlayer( g_entities[bs->client].client, weapon, 1, 0, qtrue );
// Make sure this weapon is allowed
COM_BitSet( g_entities[bs->client].client->ps.weapons, weapon );
//@TODO set ammo with an extra toekn
// client->ps.ammoclip[BG_FindClipForWeapon(weapon)] = ammoclip;
// client->ps.ammo[BG_FindAmmoForWeapon(weapon)] = ammo;
// Make this the current weapon
g_entities[bs->client].client->ps.weapon = weapon;
} // if (weapon != WP_NONE)...
return qtrue;
}
/*
===============
Bot_ScriptAction_SetClass
===============
*/
qboolean Bot_ScriptAction_SetClass( bot_state_t *bs, char *params ) {
int val = -1;
char userinfo[MAX_INFO_STRING];
//
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "SetClass requires a class name" );
}
//
if ( !Q_stricmp( params, "ANY" ) ) {
val = -1;
} else if ( !Q_stricmp( params, "soldier" ) ) {
val = PC_SOLDIER;
} else if ( !Q_stricmp( params, "medic" ) ) {
val = PC_MEDIC;
} else if ( !Q_stricmp( params, "engineer" ) ) {
val = PC_ENGINEER;
} else if ( !Q_stricmp( params, "lieutenant" ) ) { // FIXME: remove this from missionpack? once all scripts have been updated
val = PC_FIELDOPS;
} else if ( !Q_stricmp( params, "fieldops" ) ) {
val = PC_FIELDOPS;
} else if ( !Q_stricmp( params, "covertops" ) ) {
val = PC_COVERTOPS;
} else {
Bot_ScriptError( bs, "unknown class \"%s\"", params );
}
//
trap_GetUserinfo( bs->client, userinfo, sizeof( userinfo ) );
Info_SetValueForKey( userinfo, "pClass", va( "%i", val ) );
trap_SetUserinfo( bs->client, userinfo );
//
return qtrue;
}
/*
=================
Bot_ScriptAction_GlobalAccum
syntax: globalAccum
Commands:
globalAccum inc
globalAccum abort_if_less_than
globalAccum abort_if_greater_than
globalAccum abort_if_not_equal
globalAccum abort_if_equal
globalAccum set_to
globalAccum random
globalAccum bitset
globalAccum bitclear
globalAccum abort_if_bitset
globalAccum abort_if_not_bitset
=================
*/
qboolean Bot_ScriptAction_GlobalAccum( bot_state_t *bs, char *params ) {
char *pString, *token, lastToken[MAX_QPATH];
int bufferIndex;
pString = params;
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
Bot_ScriptError( bs, "globalAccum: without a buffer index" );
}
bufferIndex = atoi( token );
if ( bufferIndex >= MAX_SCRIPT_ACCUM_BUFFERS ) {
Bot_ScriptError( bs, "globalAccum: buffer is outside range (0 - %i)", MAX_SCRIPT_ACCUM_BUFFERS );
}
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
Bot_ScriptError( bs, "globalAccum: without a command" );
}
Q_strncpyz( lastToken, token, sizeof( lastToken ) );
token = COM_ParseExt( &pString, qfalse );
if ( !Q_stricmp( lastToken, "inc" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "globalAccum:: %s requires a parameter", lastToken );
}
level.globalAccumBuffer[bufferIndex] += atoi( token );
} else if ( !Q_stricmp( lastToken, "abort_if_less_than" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "globalAccum: %s requires a parameter", lastToken );
}
if ( level.globalAccumBuffer[bufferIndex] < atoi( token ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_greater_than" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "globalAccum: %s requires a parameter", lastToken );
}
if ( level.globalAccumBuffer[bufferIndex] > atoi( token ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_not_equal" ) || !Q_stricmp( lastToken, "abort_if_not_equals" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "globalAccum: %s requires a parameter", lastToken );
}
if ( level.globalAccumBuffer[bufferIndex] != atoi( token ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_equal" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "globalAccum: %s requires a parameter", lastToken );
}
if ( level.globalAccumBuffer[bufferIndex] == atoi( token ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "bitset" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "globalAccum: %s requires a parameter", lastToken );
}
level.globalAccumBuffer[bufferIndex] |= ( 1 << atoi( token ) );
} else if ( !Q_stricmp( lastToken, "bitclear" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "globalAccum: %s requires a parameter", lastToken );
}
level.globalAccumBuffer[bufferIndex] &= ~( 1 << atoi( token ) );
} else if ( !Q_stricmp( lastToken, "abort_if_bitset" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "globalAccum: %s requires a parameter", lastToken );
}
if ( level.globalAccumBuffer[bufferIndex] & ( 1 << atoi( token ) ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_not_bitset" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "globalAccum: %s requires a parameter", lastToken );
}
if ( !( level.globalAccumBuffer[bufferIndex] & ( 1 << atoi( token ) ) ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "set_to" ) || !Q_stricmp( lastToken, "set" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "globalAccum: %s requires a parameter", lastToken );
}
level.globalAccumBuffer[bufferIndex] = atoi( token );
} else if ( !Q_stricmp( lastToken, "random" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "globalAccum: %s requires a parameter", lastToken );
}
level.globalAccumBuffer[bufferIndex] = rand() % atoi( token );
} else {
Bot_ScriptError( bs, "globalAccum: %s: unknown command", params );
}
return qtrue;
}
/*
=================
Bot_ScriptAction_FollowLeader
=================
*/
qboolean Bot_ScriptAction_FollowLeader( bot_state_t *bs, char *params ) {
char *pString, *token;
gentity_t *target;
// vec3_t vec;
int duration;
//
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "FollowLeader requires a name." );
}
// TAT 8/20/2002
// If we have received a player command that overrides our movement scripts,
// we should end the script
if ( bs->overrideMovementScripts ) {
return qfalse;
}
//
pString = params;
token = COM_ParseExt( &pString, qfalse );
target = BotFindEntityForName( token );
if ( !target ) {
if ( bs->script.status.stackChangeTime != level.time ) {
// Gordon: lets assume they have died...
return qtrue;
}
Bot_ScriptError( bs, "FollowLeader has unknown name: \"%s\"", token );
}
if ( target->health <= 0 ) {
// Gordon: dead, dont follow
return qtrue;
}
// read the duration
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
Bot_ScriptError( bs, "FollowLeader requires a duration" );
}
if ( !Q_stricmp( token, "forever" ) ) {
duration = (int)0x7fffffff;
} else {
duration = atoi( token );
}
//
bs->script.frameFlags |= BSFFL_FOLLOW_LEADER;
bs->script.entityNum = target->s.number;
bs->script.moveType = BSMT_DEFAULT;
//
while ( ( token = COM_ParseExt( &pString, qfalse ) ) && token[0] ) {
if ( !Q_stricmp( token, "/WALKING" ) ) {
bs->script.moveType = BSMT_WALKING;
} else if ( !Q_stricmp( token, "/CROUCHING" ) ) {
bs->script.moveType = BSMT_CROUCHING;
}
}
//
return ( bs->script.status.stackChangeTime < level.time - duration );
}
/*
===================
Bot_ScriptAction_Cvar
syntax: cvar
===================
*/
qboolean Bot_ScriptAction_Cvar( bot_state_t *bs, char *params ) {
char *pString, *token, lastToken[MAX_QPATH], name[MAX_QPATH], cvarName[MAX_QPATH];
int cvarValue;
qboolean terminate, found;
pString = params;
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
Bot_ScriptError( bs, "cvar without a cvar name\n" );
}
cvarValue = trap_Cvar_VariableIntegerValue( cvarName );
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
Bot_ScriptError( bs, "cvar without a command\n" );
}
Q_strncpyz( lastToken, token, sizeof( lastToken ) );
token = COM_ParseExt( &pString, qfalse );
if ( !Q_stricmp( lastToken, "inc" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "cvar %s requires a parameter\n", lastToken );
}
trap_Cvar_Set( cvarName, va( "%i", cvarValue + 1 ) );
} else if ( !Q_stricmp( lastToken, "abort_if_less_than" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "cvar %s requires a parameter\n", lastToken );
}
if ( cvarValue < atoi( token ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_greater_than" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "cvar %s requires a parameter\n", lastToken );
}
if ( cvarValue > atoi( token ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_not_equal" ) || !Q_stricmp( lastToken, "abort_if_not_equals" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "cvar %s requires a parameter\n", lastToken );
}
if ( cvarValue != atoi( token ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_equal" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "cvar %s requires a parameter\n", lastToken );
}
if ( cvarValue == atoi( token ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "bitset" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "cvar %s requires a parameter\n", lastToken );
}
cvarValue |= ( 1 << atoi( token ) );
} else if ( !Q_stricmp( lastToken, "bitreset" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "cvar %s requires a parameter\n", lastToken );
}
cvarValue &= ~( 1 << atoi( token ) );
} else if ( !Q_stricmp( lastToken, "abort_if_bitset" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "cvar %s requires a parameter\n", lastToken );
}
if ( cvarValue & ( 1 << atoi( token ) ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_not_bitset" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "cvar %s requires a parameter\n", lastToken );
}
if ( !( cvarValue & ( 1 << atoi( token ) ) ) ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "set" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "cvar %s requires a parameter\n", lastToken );
}
cvarValue = atoi( token );
} else if ( !Q_stricmp( lastToken, "random" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "cvar %s requires a parameter\n", lastToken );
}
cvarValue = rand() % atoi( token );
} else if ( !Q_stricmp( lastToken, "trigger_if_equal" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "cvar %s requires a parameter\n", lastToken );
}
if ( cvarValue == atoi( token ) ) {
gentity_t* trent;
int oldId;
token = COM_ParseExt( &pString, qfalse );
Q_strncpyz( lastToken, token, sizeof( lastToken ) );
if ( !*lastToken ) {
Bot_ScriptError( bs, "trigger must have a name and an identifier\n" );
}
token = COM_ParseExt( &pString, qfalse );
Q_strncpyz( name, token, sizeof( name ) );
if ( !*name ) {
Bot_ScriptError( bs, "trigger must have a name and an identifier\n" );
}
terminate = qfalse;
found = qfalse;
trent = NULL;
while ( ( trent = BotFindEntity( trent, FOFS( scriptName ), lastToken ) ) ) {
found = qtrue;
oldId = trent->scriptStatus.scriptId;
G_Script_ScriptEvent( trent, "trigger", name );
// if the script changed, return false so we don't muck with it's variables
if ( ( trent->s.number == bs->client ) && ( oldId != trent->scriptStatus.scriptId ) ) {
terminate = qtrue;
}
}
if ( terminate ) {
return qfalse;
}
if ( found ) {
return qtrue;
}
// Bot_ScriptError( bs, "trigger has unknown name: %s\n", name );
G_Printf( "trigger has unknown name: %s\n", name );
return qfalse;
}
} else if ( !Q_stricmp( lastToken, "wait_while_equal" ) ) {
if ( !token[0] ) {
Bot_ScriptError( bs, "cvar %s requires a parameter\n", lastToken );
}
if ( cvarValue == atoi( token ) ) {
return qfalse;
}
} else {
Bot_ScriptError( bs, "cvar %s: unknown command\n", params );
}
return qtrue;
}
/*
=================
Bot_ScriptAction_SetMovementAutonomy
=================
*/
qboolean Bot_ScriptAction_SetMovementAutonomy( bot_state_t *bs, char *params ) {
int mlevel;
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "SetMovementAutonomy requires a parameter" );
}
//
mlevel = BotMovementAutonomyForString( params );
if ( mlevel < 0 ) {
Bot_ScriptError( bs, "SetMovementAutonomy: unknown parameter \"%s\"", params );
}
bs->script.movementAutonomy = mlevel;
// TAT - why are there 2 of these vars? set both of them
bs->movementAutonomy = mlevel;
//
if ( bs->leader < 0 ) {
VectorCopy( level.clients[bs->client].ps.origin, bs->script.movementAutonomyPos );
VectorCopy( level.clients[bs->client].ps.origin, bs->movementAutonomyPos );
}
//
return qtrue;
}
/*
======================
Bot_ScriptAction_MovementAutonomy
======================
*/
qboolean Bot_ScriptAction_MovementAutonomy( bot_state_t *bs, char *params ) {
char *pString, *token, *operand;
int maLevel;
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "MovementAutonomy requires a parameter" );
}
//
pString = params;
//
// read the operand
token = COM_ParseExt( &pString, qfalse );
operand = va( "%s", token ); // RF, this is a cheap and nasty way of saving memory
//
if ( !operand[0] ) {
Bot_ScriptError( bs, "MovementAutonomy requires an operand" );
}
//
// read the level
token = COM_ParseExt( &pString, qfalse );
//
if ( !token[0] ) {
Bot_ScriptError( bs, "MovementAutonomy requires a level" );
}
//
maLevel = BotMovementAutonomyForString( token );
if ( maLevel < 0 ) {
Bot_ScriptError( bs, "SetMovementAutonomy: unknown movementAutonomy \"%s\"", params );
}
//
// apply the function
if ( !Q_stricmp( operand, "set" ) ) {
bs->script.movementAutonomy = maLevel;
VectorCopy( level.clients[bs->client].ps.origin, bs->script.movementAutonomyPos );
}
//
if ( !Q_stricmp( operand, "force" ) ) {
bs->script.movementAutonomy = maLevel;
VectorCopy( level.clients[bs->client].ps.origin, bs->script.movementAutonomyPos );
bs->leader = -1; // stop following others
//
bs->script.flags |= BSFL_FORCED_MOVEMENT_AUTONOMY; // force this level
}
//
if ( !Q_stricmp( operand, "unforce" ) ) {
bs->script.flags &= ~BSFL_FORCED_MOVEMENT_AUTONOMY; // turn it off
}
//
else if ( !Q_stricmp( operand, "abort_if_less_than" ) ) {
if ( bs->movementAutonomy < maLevel ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
}
//
else if ( !Q_stricmp( operand, "abort_if_greater_than" ) ) {
if ( bs->movementAutonomy > maLevel ) {
// abort the current script
bs->script.status.stackHead = bs->script.data->events[bs->script.status.eventIndex].stack.numItems;
}
}
//
return qtrue;
}
//
// START Mad Doctor I changes, 8/12/2002
// Adding some basic script functions for setting health, notarget, and accuracy
//
/*
=================
Bot_ScriptAction_NoTarget
syntax: notarget ON/OFF
=================
*/
qboolean Bot_ScriptAction_NoTarget
(
bot_state_t *bs,
char *params
) {
// The user needs to specify on or off
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "notarget requires ON or OFF as parameter" );
} // if (!params || !params[0]) ...
if ( !Q_stricmp( params, "ON" ) ) {
g_entities[bs->client].flags |= FL_NOTARGET;
} else if ( !Q_stricmp( params, "OFF" ) ) {
g_entities[bs->client].flags &= ~FL_NOTARGET;
} else
{
Bot_ScriptError( bs, "notarget requires ON or OFF as parameter" );
}
return qtrue;
} // qboolean Bot_ScriptAction_NoTarget( cast_state_t *cs, char *params) ...
/*
====================
Bot_ScriptAction_ResetScript
====================
*/
qboolean Bot_ScriptAction_ResetScript( bot_state_t *bs, char *params ) {
return qtrue;
}
// START Mad Doctor I changes, 8/14/2002
/*
=================
Bot_ScriptAction_SetFieldOfView
=================
*/
qboolean Bot_ScriptAction_SetFieldOfView
(
bot_state_t *bs,
char *params
) {
// Make sure we have a parameter to set the FOV to.
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "SetFieldOfView requires a FOV value" );
} // if (!params || !params[0]) ...
// Set the FOV
bs->scriptedFieldOfView = atof( params );
return qtrue;
} // qboolean Bot_ScriptAction_SetFieldOfView( cast_state_t *cs, char *params) ...
/*
=================
Bot_ScriptAction_SetHearingRange
=================
*/
qboolean Bot_ScriptAction_SetHearingRange
(
bot_state_t *bs,
char *params
) {
// Make sure we have a parameter to set the hearing range to.
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "SetHearingRange requires a range value" );
} // if (!params || !params[0]) ...
// Set the hearing range
bs->scriptedHearingRange = atof( params );
return qtrue;
} // qboolean Bot_ScriptAction_SetHearingRange( cast_state_t *cs, char *params) ...
// START xkan, 8/20/2002
/*
=================
Bot_ScriptAction_SetCrouch
syntax: SetCrouch
OnOffFlag - 1 crouch; 0 don't crouch.
=================
*/
qboolean Bot_ScriptAction_SetCrouch
(
bot_state_t *bs,
char *params
) {
char *pString, *token;
if ( !params || !params[0] ) {
G_Error( "Bot_ScriptAction_SetCrouch: syntax: SetCrouch \n" );
}
pString = params;
token = COM_Parse( &pString );
if ( !token || !token[0] ) {
G_Error( "Bot_ScriptAction_SetCrouch: syntax: SetCrouch \n" );
}
if ( !Q_stricmp( token, "on" ) ) {
bs->script.flags |= BSFL_CROUCH;
} else if ( !Q_stricmp( token, "off" ) ) {
bs->script.flags &= ~BSFL_CROUCH;
} else {
G_Error( "Bot_ScriptAction_SetCrouch: syntax: SetCrouch \n" );
}
// We're done here!
return qtrue;
} // Bot_ScriptAction_SetCrouch
qboolean Bot_ScriptAction_SetProne
(
bot_state_t *bs,
char *params
) {
char *pString, *token;
if ( !params || !params[0] ) {
G_Error( "Bot_ScriptAction_SetProne: syntax: SetProne \n" );
}
pString = params;
token = COM_Parse( &pString );
if ( !token || !token[0] ) {
G_Error( "Bot_ScriptAction_SetProne: syntax: SetProne \n" );
}
if ( !Q_stricmp( token, "on" ) ) {
bs->script.flags |= BSFL_PRONE;
} else if ( !Q_stricmp( token, "off" ) ) {
bs->script.flags &= ~BSFL_PRONE;
} else {
G_Error( "Bot_ScriptAction_SetProne: syntax: SetProne \n" );
}
// We're done here!
return qtrue;
} // Bot_ScriptAction_SetProne
// Mad Doc - TDF
/*
===================
G_ScriptAction_PrintAccum
syntax: printaccum
prints out the value of accum 'accumNumber'
===================
*/
qboolean Bot_ScriptAction_PrintAccum( bot_state_t *bs, char *params ) {
char *token, *pString;
gentity_t *ent;
int bufferIndex;
if ( !params || !params[0] ) {
G_Error( "Bot_ScriptAction_PrintAccum: syntax: PrintAccum \n" );
}
pString = params;
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
G_Error( "Bot_ScriptAction_PrintAccum: syntax: PrintAccum \n" );
}
bufferIndex = atoi( token );
if ( ( bufferIndex < 0 ) || ( bufferIndex >= MAX_SCRIPT_ACCUM_BUFFERS ) ) {
G_Error( "Bot_ScriptAction_PrintAccum: buffer is outside range (0 - %i)", MAX_SCRIPT_ACCUM_BUFFERS );
}
ent = BotGetEntity( bs->client );
G_Printf( "(BotScript) %s: Accum[%i] = %d\n", ent->scriptName, bufferIndex, ent->scriptAccumBuffer[bufferIndex] );
return qtrue;
}
// Mad Doc - TDF
/*
===================
Bot_ScriptAction_PrintGlobalAccum
syntax: printGlobalAccum
prints out the value of global accum 'globalaccumnumber'
===================
*/
qboolean Bot_ScriptAction_PrintGlobalAccum( gentity_t *ent, char *params ) {
char *token, *pString;
int bufferIndex;
if ( !params || !params[0] ) {
G_Error( "Bot_ScriptAction_PrintGlobalAccum: syntax: PrintGlobalAccum \n" );
}
pString = params;
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
G_Error( "Bot_ScriptAction_PrintGlobalAccum: syntax: PrintGlobalAccum \n" );
}
bufferIndex = atoi( token );
if ( ( bufferIndex < 0 ) || ( bufferIndex >= MAX_SCRIPT_ACCUM_BUFFERS ) ) {
G_Error( "PrintGlobalAccum: buffer is outside range (0 - %i)", MAX_SCRIPT_ACCUM_BUFFERS );
}
G_Printf( "(BotScript) GlobalAccum[%i] = %d\n", bufferIndex, level.globalAccumBuffer[bufferIndex] );
return qtrue;
}
// Mad Doc - TDF
/*
===================
Bot_ScriptAction_BotDebugging
syntax: BotDebugging ON/OFF
toggles bot debugging
===================
*/
qboolean Bot_ScriptAction_BotDebugging( gentity_t *ent, char *params ) {
char *token, *pString;
if ( !params || !params[0] ) {
G_Error( "Bot_ScriptAction_BotDebugging: syntax: BotDebugging \n" );
}
pString = params;
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
G_Error( "Bot_ScriptAction_BotDebugging: syntax: BotDebugging \n" );
}
if ( !Q_stricmp( token, "ON" ) ) {
trap_Cvar_Set( "bot_debug", "1" );
} else if ( !Q_stricmp( token, "OFF" ) ) {
trap_Cvar_Set( "bot_debug", "0" );
} else
{
G_Error( "Bot_ScriptAction_BotDebugging: syntax: BotDebugging \n" );
}
return qtrue;
}
// xkan, sets the fire rate for the bot
/*
=======================
Bot_ScriptAction_SetFireRate
Sets the fire rate. fire rate 1 is normal (continuous) firing. fire rate 0 means do not fire
at all. fire rate in between makes the bot fire a few bullets, wait a little bit, and then fire
a few more bullets.
=======================
*/
qboolean Bot_ScriptAction_SetFireRate( bot_state_t *bs, char *params ) {
char *pString, *token;
float fireRate;
if ( !params || !params[0] ) {
G_Error( "Bot_ScriptAction_SetFireRate: syntax: SetFireRate <0-1>\n" );
}
pString = params;
token = COM_Parse( &pString );
if ( !token || !token[0] ) {
G_Error( "Bot_ScriptAction_SetFireRate: syntax: SetFireRate <0-1>\n" );
}
fireRate = atof( token );
if ( fireRate < 0.0 || fireRate > 1.0 ) {
G_Error( "Bot_ScriptAction_SetFireRate: syntax: SetFireRate <0-1>\n" );
}
bs->fireRate = fireRate;
// We're done here!
return qtrue;
}
// xkan, sets the fire cycle time for the bot
/*
=======================
Bot_ScriptAction_SetFireCycleTime
Sets the minimum/maximum fire cycle time. Actual fire cycle time is a random number between
minimum cycle time and maximum cycle time.
during a fire cycle, the bot will fire for a duration equal to fireRate * cycleTime,
and hold fire for the rest of the cycle (which is equal to (1-fireRate)*cycleTime.)
=======================
*/
qboolean Bot_ScriptAction_SetFireCycleTime( bot_state_t *bs, char *params ) {
char *pString, *token;
if ( !params || !params[0] ) {
G_Error( "Bot_ScriptAction_SetFireCycleTime: syntax: SetFireCycleTime \n" );
}
pString = params;
token = COM_Parse( &pString );
if ( !token || !token[0] || token[0] < '0' || token[0] > '9' ) {
G_Error( "Bot_ScriptAction_SetFireCycleTime: syntax: SetFireCycleTime \n" );
}
bs->minFireRateCycleTime = atoi( token );
token = COM_Parse( &pString );
if ( !token || !token[0] || token[0] < '0' || token[0] > '9' ) {
G_Error( "Bot_ScriptAction_SetFireCycleTime: syntax: SetFireCycleTime \n" );
}
bs->maxFireRateCycleTime = atoi( token );
// We're done here!
return qtrue;
}
/*
=======================
Bot_ScriptAction_SetVisionRange
Mad Doctor I, 10/23/2002
Set a maximum vision range for bots to see you
=======================
*/
qboolean Bot_ScriptAction_SetVisionRange( bot_state_t *bs, char *params ) {
char *pString, *token;
float visionRange = 0;
if ( !params || !params[0] ) {
G_Error( "Bot_ScriptAction_SetVisionRange: syntax: SetVisionRange \n" );
}
pString = params;
token = COM_Parse( &pString );
if ( !token || !token[0] ) {
G_Error( "Bot_ScriptAction_SetVisionRange: syntax: SetVisionRange \n" );
}
visionRange = atof( token );
// Set the range for the bot
bs->visionRange = visionRange;
// We're done here!
return qtrue;
}
/*
=======================
Bot_ScriptAction_SetFarSeeingRange
Mad Doctor I, 10/23/2002
Set a maximum vision range for bots to "spot and report" not attack
=======================
*/
qboolean Bot_ScriptAction_SetFarSeeingRange( bot_state_t *bs, char *params ) {
char *pString, *token;
float farSeeingRange = 0;
if ( !params || !params[0] ) {
G_Error( "Bot_ScriptAction_SetFarSeeingRange: syntax: SetFarSeeingRange \n" );
}
pString = params;
token = COM_Parse( &pString );
if ( !token || !token[0] ) {
G_Error( "Bot_ScriptAction_SetFarSeeingRange: syntax: SetFarSeeingRange \n" );
}
farSeeingRange = atof( token );
// Set the range for the bot
bs->farSeeingRange = farSeeingRange;
// We're done here!
return qtrue;
}
/*
=======================
Bot_ScriptAction_SetCloseHearingRange
Mad Doctor I, 10/23/2002
When an enemy is this close, you can sense them outside FOV
=======================
*/
qboolean Bot_ScriptAction_SetCloseHearingRange( bot_state_t *bs, char *params ) {
char *pString, *token;
float closeHearingRange = 0;
if ( !params || !params[0] ) {
G_Error( "Bot_ScriptAction_SetCloseHearingRange: syntax: SetCloseHearingRange \n" );
}
pString = params;
token = COM_Parse( &pString );
if ( !token || !token[0] ) {
G_Error( "Bot_ScriptAction_SetCloseHearingRange: syntax: SetCloseHearingRange \n" );
}
closeHearingRange = atof( token );
// Set the range for the bot
bs->closeHearingRange = closeHearingRange;
// We're done here!
return qtrue;
}
/*
=======================
Bot_ScriptAction_SetSpeedCoefficient
Mad Doctor I, 11/26/2002. Set an individual bot's speed
=======================
*/
qboolean Bot_ScriptAction_SetSpeedCoefficient( bot_state_t *bs, char *params ) {
return qtrue;
}
// TAT 2/4/2003 - just force an update of current bot selection - used when who is selectable has changed
extern void UpdateSelectedBots( gentity_t *ent );
// TAT 11/16/2002 - Set the selected weapon of the bot - does NOT change which weapons the bot is holding
qboolean Bot_ScriptAction_SetActiveWeapon( bot_state_t *bs, char *params ) {
// Which class are we?
int playerClass = g_entities[bs->client].client->sess.playerType;
int weapon;
//
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "SetActiveWeapon requires a weapon name" );
}
weapon = Bot_GetWeaponForClassAndTeam( playerClass, g_entities[bs->client].client->sess.sessionTeam, params );
if ( weapon == -1 ) {
Bot_ScriptError( bs, "Bot %s on team %s cannot use weapon %s\n", g_entities[bs->client].aiName, ( g_entities[bs->client].client->sess.sessionTeam == TEAM_AXIS ) ? "Axis" : "Allies", params );
}
// if the bot doesn't have the weapon in question, error
if ( !COM_BitCheck( bs->cur_ps.weapons, weapon ) ) {
Bot_ScriptError( bs, "Bot %s on team %s doesn't have weapon %s\n", g_entities[bs->client].aiName, ( g_entities[bs->client].client->sess.sessionTeam == TEAM_AXIS ) ? "Axis" : "Allies", params );
}
// otherwise, set the commanded weapon
bs->commandedWeapon = weapon;
bs->weaponnum = weapon;
// and switch to it
trap_EA_SelectWeapon( bs->client, weapon );
// done
return qtrue;
}
/*
===================
Bot_ScriptAction_Announce
Gordon: same as the equivilant G_ScriptAction_Announce
syntax: wm_announce <"text to send to all clients">
===================
*/
qboolean Bot_ScriptAction_Announce( bot_state_t *bs, char *params ) {
char *pString, *token;
if ( level.intermissiontime ) {
return qtrue;
}
pString = params;
token = COM_Parse( &pString );
if ( !token[0] ) {
G_Error( "Bot_ScriptAction_Announce: statement parameter required\n" );
}
trap_SendServerCommand( -1, va( "cp \"%s\" 2", token ) );
return qtrue;
}
/*
=================
Bot_ScriptAction_FireAtTarget
syntax: fireattarget [duration]
=================
*/
qboolean Bot_ScriptAction_FireAtTarget( bot_state_t *bs, char *params ) {
gentity_t *ent;
vec3_t vec, org, src;
char *pString, *token;
float diff;
int i;
pString = params;
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
Bot_ScriptError( bs, "AI Scripting: fireattarget without a targetname\n" );
}
// find this targetname
ent = BotFindEntityForName( token );
if ( !ent ) {
ent = G_FindByTargetname( NULL, token );
if ( !ent ) {
Bot_ScriptError( bs, "AI Scripting: fireattarget cannot find targetname/aiName \"%s\"\n", token );
}
}
// if this is our first call for this fireattarget, record the ammo count
//if (bs->script.flags & BSFL_FIRST_CALL)
//{
// bs->last_fire = 0;
//}
// make sure we don't move or shoot while turning to our target
//if (bs->script.status.scriptNoAttackTime < level.time) {
// bs->script.status.scriptNoAttackTime = level.time + 500;
//}
// dont reload prematurely
//bs->noReloadTime = level.time + 1000;
// don't move while firing at all
//bs->castScriptStatus.scriptNoMoveTime = level.time + 500;
// stand still
//bs->script.frameFlags |= BSFFL_STAND;
// let us move our view, whether it looks bad or not
//bs->castScriptStatus.playAnimViewlockTime = 0;
// set the view angle manually
BG_EvaluateTrajectory( &ent->s.pos, level.time, org, qfalse, -1 );
VectorCopy( bs->origin, src );
src[2] += bs->cur_ps.viewheight;
VectorSubtract( org, src, vec );
VectorNormalize( vec );
vectoangles( vec, bs->ideal_viewangles );
if ( bs->weaponnum == WP_MORTAR_SET ) {
/* vec_t x, y, u, d, b, g, a;
vec3_t diff;
VectorSubtract( org, src, diff );
diff[2] = 0;
g = g_gravity.value;
x = VectorLength( diff );
y = org[2] - src[2];
u = MORTAR_SP_BOTSPEED;
d = (g * SQR(x)) / (2 * SQR(u));
b = (SQR(x) - (4 * d * (y + d)));
if(b < 0) {
return qfalse;
}
a = (vec_t)atan((-x - b) / (-2 * d));
bs->ideal_viewangles[PITCH] = (AngleMod(RAD2DEG(a)- 180) + 60);*/
float g = -g_gravity.value;
float uz = sqrt( -2 * 3072 * g );
float t = ( ( -uz ) / g ) * 2;
float ux = ( org[0] - src[0] ) / t;
float uy = ( org[1] - src[1] ) / t;
VectorSet( g_entities[bs->entitynum].gDelta, ux, uy, uz );
} else //if (bs->weaponnum != WP_MORTAR_SET)
{
for ( i = 0; i < 2; i++ ) {
diff = abs( AngleDifference( bs->cur_ps.viewangles[i], bs->ideal_viewangles[i] ) );
if ( VectorCompare( vec3_origin, ent->s.pos.trDelta ) ) {
if ( diff ) {
return qfalse; // not facing yet
}
} else {
if ( diff > 25 ) { // allow some slack when target is moving
return qfalse;
}
}
}
}
// force fire
trap_EA_Attack( bs->client );
//
bs->flags |= BFL_ATTACKED;
// if we haven't fired yet
//if (!bs->last_fire) {
// return qfalse;
//}
// do we need to stay and fire for a duration?
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
return qtrue; // no need to wait around
}
if ( !Q_stricmp( token, "forever" ) ) {
return qfalse;
}
// only return true if we've been firing for long enough
// TAT 1/29/2003 - the token in question was parsed right above this
// so can't just move this up to the top
// plus, if we don't pass in a duration, that means fire once, which that check above for no token will handle,
// but we WILL fire once at least
return ( ( bs->script.status.stackChangeTime + atoi( token ) ) < level.time );
}
/*
=======================
Bot_ScriptAction_SetScriptAutonomy
=======================
*/
qboolean Bot_ScriptAction_SetScriptAutonomy( bot_state_t *bs, char *params ) {
scriptAutonomy_t level;
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "SetScriptAutonomy requires a parameter" );
}
//
level = BotScriptAutonomyForString( params );
if ( level < 0 ) {
Bot_ScriptError( bs, "SetScriptAutonomy: unknown parameter \"%s\"", params );
}
bs->scriptAutonomy = level;
//
return qtrue;
}
// TAT 12/14/2002 - Set how much ammo we have for a particular weapon
// Doesn't change the current weapon loadout (which weapons we carry)
qboolean Bot_ScriptAction_SetAmmoAmount( bot_state_t *bs, char *params ) {
char *pString, *token;
int weapon, amount;
qboolean clipOnly = qfalse;
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "Bot_ScriptAction_SetAmmoAmmount: syntax: SetAmmoAmount " );
}
pString = params;
token = COM_Parse( &pString );
if ( !token[0] ) {
Bot_ScriptError( bs, "Bot_ScriptAction_SetAmmoAmmount: syntax: SetAmmoAmount " );
}
weapon = Bot_GetWeaponForClassAndTeam( g_entities[bs->client].client->sess.playerType, g_entities[bs->client].client->sess.sessionTeam, token );
if ( weapon == -1 ) {
// can't use this weapon
Bot_ScriptError( bs, "Bot %s on team %s cannot use weapon %s\n", g_entities[bs->client].aiName, ( g_entities[bs->client].client->sess.sessionTeam == TEAM_AXIS ) ? "Axis" : "Allies", token );
}
// Do we have this weapon?
if ( !COM_BitCheck( bs->cur_ps.weapons, weapon ) ) {
Bot_ScriptError( bs, "Bot_ScriptAction_SetAmmoAmount: Bot %s does not have weapon %s", g_entities[bs->client].aiName, token );
}
// what are we setting the ammo amount to?
token = COM_Parse( &pString );
if ( !token[0] ) {
Bot_ScriptError( bs, "Bot_ScriptAction_SetAmmoAmmount: syntax: SetAmmoAmount " );
}
amount = atoi( token );
// Some specials use the clip only
switch ( weapon )
{
case WP_AMMO:
case WP_MEDKIT:
case WP_LANDMINE:
case WP_MEDIC_SYRINGE:
case WP_GRENADE_LAUNCHER:
case WP_GRENADE_PINEAPPLE:
case WP_FLAMETHROWER:
case WP_MORTAR:
case WP_MORTAR_SET:
case WP_DYNAMITE:
clipOnly = qtrue;
break;
}
if ( clipOnly ) {
g_entities[bs->client].client->ps.ammoclip[BG_FindAmmoForWeapon( weapon )] = amount;
} else
{
g_entities[bs->client].client->ps.ammo[BG_FindAmmoForWeapon( weapon )] = amount;
}
// done
return qtrue;
}
qboolean Bot_ScriptAction_SetCivilian( bot_state_t *bs, char *params ) {
char *pString, *token;
if ( !params || !params[0] ) {
Bot_ScriptError( bs, "Bot_ScriptAction_SetCivilian: syntax: SetCivilian " );
}
pString = params;
token = COM_Parse( &pString );
if ( token[0] && !Q_stricmp( token, "yes" ) ) {
g_entities[bs->client].client->isCivilian = qtrue;
} else if ( token[0] && !Q_stricmp( token, "no" ) ) {
g_entities[bs->client].client->isCivilian = qfalse;
} else {
Bot_ScriptError( bs, "Bot_ScriptAction_SetCivilian: syntax: SetCivilian " );
}
// done
return qtrue;
}