/*
===========================================================================
Return to Castle Wolfenstein single player GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
This file is part of the Return to Castle Wolfenstein single player GPL Source Code (RTCW SP Source Code).
RTCW SP 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.
RTCW SP 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 RTCW SP Source Code. If not, see .
In addition, the RTCW SP 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 RTCW SP 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_cast_script_actions.c
// Function: Wolfenstein AI Character 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 "ai_cast.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.
*/
/*
===============
AICast_NoAttackIfNotHurtSinceLastScriptAction
Not an actual command, this is just used by the script code
===============
*/
void AICast_NoAttackIfNotHurtSinceLastScriptAction( cast_state_t *cs ) {
if ( cs->castScriptStatus.scriptNoAttackTime > level.time ) {
return;
}
// if not moving, we should attack
if ( VectorLength( cs->bs->velocity ) < 10 ) {
return;
}
// if our enemy is in the direction we are moving, don't hold back
if ( cs->enemyNum >= 0 && cs->castScriptStatus.scriptGotoEnt >= 0 ) {
vec3_t v;
VectorSubtract( g_entities[cs->enemyNum].r.currentOrigin, cs->bs->origin, v );
if ( DotProduct( cs->bs->velocity, v ) > 0 ) {
return;
}
}
if ( cs->lastPain < cs->castScriptStatus.castScriptStackChangeTime ) {
cs->castScriptStatus.scriptNoAttackTime = level.time + FRAMETIME;
}
}
/*
===============
AICast_ScriptAction_GotoMarker
syntax: gotomarker [firetarget [noattack]] [nostop] OR runtomarker [firetarget [noattack]] [nostop]
===============
*/
qboolean AICast_ScriptAction_GotoMarker( cast_state_t *cs, char *params ) {
#define SCRIPT_REACHGOAL_DIST 8
char *pString, *token;
gentity_t *ent;
vec3_t vec, org;
int i, diff;
qboolean slowApproach;
ent = NULL;
// if we are avoiding danger, then wait for the danger to pass
if ( cs->castScriptStatus.scriptGotoId < 0 && cs->dangerEntityValidTime > level.time ) {
return qfalse;
}
// if we are in a special func, then wait
if ( cs->aiFlags & AIFL_SPECIAL_FUNC ) {
return qfalse;
}
pString = params;
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
G_Error( "AI scripting: gotomarker must have an targetname\n" );
}
// if we already are going to the marker, just use that, and check if we're in range
if ( cs->castScriptStatus.scriptGotoEnt >= 0 && cs->castScriptStatus.scriptGotoId == cs->thinkFuncChangeTime ) {
ent = &g_entities[cs->castScriptStatus.scriptGotoEnt];
if ( ent->targetname && !Q_strcasecmp( ent->targetname, token ) ) {
// if we're not slowing down, then check for passing the marker, otherwise check distance only
VectorSubtract( ent->r.currentOrigin, cs->bs->origin, vec );
//
if ( cs->followSlowApproach && VectorLength( vec ) < cs->followDist ) {
cs->followTime = 0;
AIFunc_IdleStart( cs ); // resume normal AI
return qtrue;
} else if ( !cs->followSlowApproach && VectorLength( vec ) < 64 /*&& DotProduct(cs->bs->cur_ps.velocity, vec) < 0*/ ) {
cs->followTime = 0;
AIFunc_IdleStart( cs ); // resume normal AI
return qtrue;
} else
{
// do we have a firetarget ?
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] || !Q_stricmp( token,"nostop" ) ) {
AICast_NoAttackIfNotHurtSinceLastScriptAction( cs );
} else { // yes we do
// find this targetname
ent = G_Find( NULL, FOFS( targetname ), token );
if ( !ent ) {
ent = AICast_FindEntityForName( token );
if ( !ent ) {
G_Error( "AI Scripting: gotomarker cannot find targetname \"%s\"\n", token );
}
}
// set the view angle manually
BG_EvaluateTrajectory( &ent->s.pos, level.time, org );
VectorSubtract( org, cs->bs->origin, vec );
VectorNormalize( vec );
vectoangles( vec, cs->ideal_viewangles );
// noattack?
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] || Q_stricmp( token,"noattack" ) ) {
qboolean fire = qtrue;
// if it's an AI, and they aren't visible, dont shoot
if ( ent->r.svFlags & SVF_CASTAI ) {
if ( cs->vislist[ent->s.number].real_visible_timestamp != cs->vislist[ent->s.number].lastcheck_timestamp ) {
fire = qfalse;
}
}
if ( fire ) {
for ( i = 0; i < 2; i++ ) {
diff = abs( AngleDifference( cs->viewangles[i], cs->ideal_viewangles[i] ) );
if ( diff < 20 ) {
// dont reload prematurely
cs->noReloadTime = level.time + 1000;
// force fire
trap_EA_Attack( cs->bs->client );
//
cs->bFlags |= BFL_ATTACKED;
// dont reload prematurely
cs->noReloadTime = level.time + 200;
}
}
}
}
}
cs->followTime = level.time + 500;
return qfalse;
}
} else
{
ent = NULL;
}
}
// find the ai_marker with the given "targetname"
// TTimo: gcc: suggest () around assignment used as truth value
while ( ( ent = G_Find( ent, FOFS( classname ), "ai_marker" ) ) )
{
if ( ent->targetname && !Q_strcasecmp( ent->targetname, token ) ) {
break;
}
}
if ( !ent ) {
G_Error( "AI Scripting: can't find ai_marker with \"targetname\" = \"%s\"\n", token );
}
if ( Distance( cs->bs->origin, ent->r.currentOrigin ) < SCRIPT_REACHGOAL_DIST ) { // we made it
return qtrue;
}
cs->castScriptStatus.scriptNoMoveTime = 0;
cs->castScriptStatus.scriptGotoEnt = ent->s.number;
//
// slow approach to the goal?
if ( !params || !strstr( params," nostop" ) ) {
slowApproach = qtrue;
} else {
slowApproach = qfalse;
}
//
AIFunc_ChaseGoalStart( cs, ent->s.number, ( slowApproach ? SCRIPT_REACHGOAL_DIST : 32 ), slowApproach );
cs->followIsGoto = qtrue;
cs->followTime = 0x7fffffff; // make sure it gets through for the first frame
cs->castScriptStatus.scriptGotoId = cs->thinkFuncChangeTime;
AICast_NoAttackIfNotHurtSinceLastScriptAction( cs );
return qfalse;
}
/*
===============
AICast_ScriptAction_WalkToMarker
syntax: walktomarker [firetarget [noattack]] [nostop]
===============
*/
qboolean AICast_ScriptAction_WalkToMarker( cast_state_t *cs, char *params ) {
// if we are avoiding danger, then wait for the danger to pass
if ( cs->castScriptStatus.scriptGotoId < 0 && cs->dangerEntityValidTime > level.time ) {
return qfalse;
}
// if we are in a special func, then wait
if ( cs->aiFlags & AIFL_SPECIAL_FUNC ) {
return qfalse;
}
if ( !AICast_ScriptAction_GotoMarker( cs, params ) || ( !strstr( params, " nostop" ) && VectorLength( cs->bs->cur_ps.velocity ) ) ) {
cs->movestate = MS_WALK;
cs->movestateType = MSTYPE_TEMPORARY;
AICast_NoAttackIfNotHurtSinceLastScriptAction( cs );
return qfalse;
}
return qtrue;
}
/*
===============
AICast_ScriptAction_CrouchToMarker
syntax: crouchtomarker [firetarget [noattack]] [nostop]
===============
*/
qboolean AICast_ScriptAction_CrouchToMarker( cast_state_t *cs, char *params ) {
// if we are avoiding danger, then wait for the danger to pass
if ( cs->castScriptStatus.scriptGotoId < 0 && cs->dangerEntityValidTime > level.time ) {
return qfalse;
}
// if we are in a special func, then wait
if ( cs->aiFlags & AIFL_SPECIAL_FUNC ) {
return qfalse;
}
if ( !AICast_ScriptAction_GotoMarker( cs, params ) || ( !strstr( params, " nostop" ) && VectorLength( cs->bs->cur_ps.velocity ) ) ) {
cs->movestate = MS_CROUCH;
cs->movestateType = MSTYPE_TEMPORARY;
AICast_NoAttackIfNotHurtSinceLastScriptAction( cs );
return qfalse;
}
return qtrue;
}
/*
===============
AICast_ScriptAction_GotoCast
syntax: gotocast [firetarget [noattack]] OR runtocast [firetarget [noattack]]
===============
*/
qboolean AICast_ScriptAction_GotoCast( cast_state_t *cs, char *params ) {
#define SCRIPT_REACHCAST_DIST 64
char *pString, *token;
gentity_t *ent;
vec3_t vec, org;
int i, diff;
ent = NULL;
// if we are avoiding danger, then wait for the danger to pass
if ( cs->castScriptStatus.scriptGotoId < 0 && cs->dangerEntityValidTime > level.time ) {
return qfalse;
}
// if we are in a special func, then wait
if ( cs->aiFlags & AIFL_SPECIAL_FUNC ) {
return qfalse;
}
pString = params;
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
G_Error( "AI scripting: gotocast must have an ainame\n" );
}
// if we already are going to the marker, just use that, and check if we're in range
if ( cs->castScriptStatus.scriptGotoEnt >= 0 && cs->castScriptStatus.scriptGotoId == cs->thinkFuncChangeTime ) {
ent = &g_entities[cs->castScriptStatus.scriptGotoEnt];
if ( ent->targetname && !Q_strcasecmp( ent->targetname, token ) ) {
if ( Distance( cs->bs->origin, ent->r.currentOrigin ) < cs->followDist ) {
cs->followTime = 0;
AIFunc_IdleStart( cs ); // resume normal AI
return qtrue;
} else
{
// do we have a firetarget ?
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
AICast_NoAttackIfNotHurtSinceLastScriptAction( cs );
} else { // yes we do
// find this targetname
ent = G_Find( NULL, FOFS( targetname ), token );
if ( !ent ) {
ent = AICast_FindEntityForName( token );
if ( !ent ) {
G_Error( "AI Scripting: gotomarker cannot find targetname \"%s\"\n", token );
}
}
// set the view angle manually
BG_EvaluateTrajectory( &ent->s.pos, level.time, org );
VectorSubtract( org, cs->bs->origin, vec );
VectorNormalize( vec );
vectoangles( vec, cs->ideal_viewangles );
// noattack?
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] || Q_stricmp( token,"noattack" ) ) {
qboolean fire = qtrue;
// if it's an AI, and they aren't visible, dont shoot
if ( ent->r.svFlags & SVF_CASTAI ) {
if ( cs->vislist[ent->s.number].real_visible_timestamp != cs->vislist[ent->s.number].lastcheck_timestamp ) {
fire = qfalse;
}
}
if ( fire ) {
for ( i = 0; i < 2; i++ ) {
diff = abs( AngleDifference( cs->viewangles[i], cs->ideal_viewangles[i] ) );
if ( diff < 20 ) {
// dont reload prematurely
cs->noReloadTime = level.time + 1000;
// force fire
trap_EA_Attack( cs->bs->client );
//
cs->bFlags |= BFL_ATTACKED;
// dont reload prematurely
cs->noReloadTime = level.time + 200;
}
}
}
}
}
cs->followTime = level.time + 500;
return qfalse;
}
} else
{
ent = NULL;
}
}
// find the cast/player with the given "name"
ent = AICast_FindEntityForName( token );
if ( !ent ) {
G_Error( "AI Scripting: can't find AI cast with \"ainame\" = \"%s\"\n", token );
}
if ( Distance( cs->bs->origin, ent->r.currentOrigin ) < SCRIPT_REACHCAST_DIST ) { // we made it
return qtrue;
}
if ( !ent ) {
G_Error( "AI Scripting: can't find ai_marker with \"targetname\" = \"%s\"\n", token );
}
cs->castScriptStatus.scriptNoMoveTime = 0;
cs->castScriptStatus.scriptGotoEnt = ent->s.number;
//
AIFunc_ChaseGoalStart( cs, ent->s.number, SCRIPT_REACHCAST_DIST, qtrue );
cs->followTime = 0x7fffffff;
AICast_NoAttackIfNotHurtSinceLastScriptAction( cs );
cs->castScriptStatus.scriptGotoId = cs->thinkFuncChangeTime;
return qfalse;
}
/*
===============
AICast_ScriptAction_WalkToCast
syntax: walktocast [firetarget [noattack]]
===============
*/
qboolean AICast_ScriptAction_WalkToCast( cast_state_t *cs, char *params ) {
// if we are avoiding danger, then wait for the danger to pass
if ( cs->castScriptStatus.scriptGotoId < 0 && cs->dangerEntityValidTime > level.time ) {
return qfalse;
}
// if we are in a special func, then wait
if ( cs->aiFlags & AIFL_SPECIAL_FUNC ) {
return qfalse;
}
if ( !AICast_ScriptAction_GotoCast( cs, params ) ) {
cs->movestate = MS_WALK;
cs->movestateType = MSTYPE_TEMPORARY;
AICast_NoAttackIfNotHurtSinceLastScriptAction( cs );
return qfalse;
}
return qtrue;
}
/*
===============
AICast_ScriptAction_CrouchToCast
syntax: crouchtocast [firetarget [noattack]]
===============
*/
qboolean AICast_ScriptAction_CrouchToCast( cast_state_t *cs, char *params ) {
// if we are avoiding danger, then wait for the danger to pass
if ( cs->castScriptStatus.scriptGotoId < 0 && cs->dangerEntityValidTime > level.time ) {
return qfalse;
}
// if we are in a special func, then wait
if ( cs->aiFlags & AIFL_SPECIAL_FUNC ) {
return qfalse;
}
if ( !AICast_ScriptAction_GotoCast( cs, params ) ) {
cs->movestate = MS_CROUCH;
cs->movestateType = MSTYPE_TEMPORARY;
AICast_NoAttackIfNotHurtSinceLastScriptAction( cs );
return qfalse;
}
return qtrue;
}
/*
==============
AICast_ScriptAction_AbortIfLoadgame
==============
*/
qboolean AICast_ScriptAction_AbortIfLoadgame( cast_state_t *cs, char *params ) {
char loading[4];
trap_Cvar_VariableStringBuffer( "savegame_loading", loading, sizeof( loading ) );
if ( strlen( loading ) > 0 && atoi( loading ) != 0 ) {
// abort the current script
cs->castScriptStatus.castScriptStackHead = cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack.numItems;
}
return qtrue;
}
/*
=================
AICast_ScriptAction_Wait
syntax: wait [moverange] [facetarget]
moverange defaults to 200, allows some monouverability to avoid fire or attack
=================
*/
qboolean AICast_ScriptAction_Wait( cast_state_t *cs, char *params ) {
char *pString, *token, *facetarget;
int duration;
float moverange;
float dist;
gentity_t *ent;
vec3_t org, vec;
// if we are in a special func, then wait
if ( cs->aiFlags & AIFL_SPECIAL_FUNC ) {
return qfalse;
}
// EXPERIMENTAL: if they are on fire, or avoiding danger, let them loose until it passes (or they die)
if ( cs->dangerEntityValidTime > level.time ) {
cs->castScriptStatus.scriptNoMoveTime = -1;
return qfalse;
}
if ( ( cs->castScriptStatus.scriptFlags & SFL_FIRST_CALL ) && cs->bs ) {
// first call, init the waitPos
VectorCopy( cs->bs->origin, cs->castScriptStatus.scriptWaitPos );
}
// get the duration
pString = params;
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
G_Error( "AI scripting: wait must have a duration\n" );
}
if ( !Q_stricmp( token, "forever" ) ) {
duration = level.time + 10000;
} else {
duration = atoi( token );
}
// if this is for the player, don't worry about enforcing the moverange
if ( !cs->bs ) {
return ( cs->castScriptStatus.castScriptStackChangeTime + duration < level.time );
}
token = COM_ParseExt( &pString, qfalse );
// if this token is a number, then assume it is the moverange, otherwise we have a default moverange with a facetarget
moverange = -999;
facetarget = NULL;
if ( token[0] ) {
if ( toupper( token[0] ) >= 'A' && toupper( token[0] ) <= 'Z' ) {
facetarget = token;
} else { // we found a moverange
moverange = atof( token );
token = COM_ParseExt( &pString, qfalse );
if ( token[0] ) {
facetarget = token;
}
}
}
// default to no moverange
//if (moverange == -999) {
// moverange = 200;
//}
if ( moverange != 0 ) { // default to 200 if no range given
if ( moverange > 0 ) {
dist = Distance( cs->bs->origin, cs->castScriptStatus.scriptWaitPos );
// if we are able to move, and have an enemy
if ( ( cs->castScriptStatus.scriptWaitMovetime < level.time )
&& ( cs->enemyNum >= 0 ) ) {
// if we can attack them, or they can't attack us, stay here
// TTimo: gcc: warning: suggest parentheses around && within ||
if ( AICast_CheckAttack( cs, cs->enemyNum, qfalse )
|| ( !AICast_EntityVisible( AICast_GetCastState( cs->enemyNum ), cs->entityNum, qfalse )
&& !AICast_CheckAttack( AICast_GetCastState( cs->enemyNum ), cs->entityNum, qfalse ) ) ) {
cs->castScriptStatus.scriptNoMoveTime = level.time + 200;
}
}
// if outside range, move towards waitPos
if ( ( !cs->bs || !cs->bs->cur_ps.legsTimer ) && ( ( ( cs->castScriptStatus.scriptWaitMovetime > level.time ) && ( dist > 32 ) ) || ( dist > moverange ) ) ) {
cs->castScriptStatus.scriptNoMoveTime = 0;
AICast_MoveToPos( cs, cs->castScriptStatus.scriptWaitPos, 0 );
if ( dist > 64 ) {
cs->castScriptStatus.scriptWaitMovetime = level.time + 600;
}
} else
// if we are reloading, look for somewhere to hide
if ( cs->castScriptStatus.scriptWaitHideTime > level.time || cs->bs->cur_ps.weaponTime > 500 ) {
if ( cs->castScriptStatus.scriptWaitHideTime < level.time ) {
// look for a hide pos within the wait range
}
cs->castScriptStatus.scriptWaitHideTime = level.time + 500;
}
}
} else {
cs->castScriptStatus.scriptNoMoveTime = cs->castScriptStatus.castScriptStackChangeTime + duration;
}
// do we have a facetarget ?
if ( facetarget ) { // yes we do
// find this targetname
ent = G_Find( NULL, FOFS( targetname ), facetarget );
if ( !ent ) {
ent = AICast_FindEntityForName( facetarget );
if ( !ent ) {
G_Error( "AI Scripting: wait cannot find targetname \"%s\"\n", token );
}
}
// set the view angle manually
BG_EvaluateTrajectory( &ent->s.pos, level.time, org );
VectorSubtract( org, cs->bs->origin, vec );
VectorNormalize( vec );
vectoangles( vec, cs->ideal_viewangles );
}
return ( cs->castScriptStatus.castScriptStackChangeTime + duration < level.time );
}
/*
=================
AICast_ScriptAction_Trigger
syntax: trigger
Calls the specified trigger for the given ai character
=================
*/
qboolean AICast_ScriptAction_Trigger( cast_state_t *cs, char *params ) {
gentity_t *ent;
char *pString, *token;
int oldId;
// get the cast name
pString = params;
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
G_Error( "AI scripting: trigger must have a name and an identifier\n" );
}
ent = AICast_FindEntityForName( token );
if ( !ent ) {
ent = G_Find( &g_entities[MAX_CLIENTS], FOFS( scriptName ), token );
if ( !ent ) {
if ( trap_Cvar_VariableIntegerValue( "developer" ) ) {
G_Printf( "AI Scripting: can't find AI cast with \"ainame\" = \"%s\"\n", params );
}
return qtrue;
}
}
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
G_Error( "AI scripting: trigger must have a name and an identifier\n" );
}
oldId = cs->castScriptStatus.scriptId;
if ( ent->client ) {
AICast_ScriptEvent( AICast_GetCastState( ent->s.number ), "trigger", token );
} else {
G_Script_ScriptEvent( ent, "trigger", token );
}
// if the script changed, return false so we don't muck with it's variables
return ( oldId == cs->castScriptStatus.scriptId );
}
/*
===================
AICast_ScriptAction_FollowCast
syntax: followcast
===================
*/
qboolean AICast_ScriptAction_FollowCast( cast_state_t *cs, char *params ) {
gentity_t *ent;
// find the cast/player with the given "name"
ent = AICast_FindEntityForName( params );
if ( !ent ) {
G_Error( "AI Scripting: can't find AI cast with \"ainame\" = \"%s\"\n", params );
}
AIFunc_ChaseGoalStart( cs, ent->s.number, 64, qtrue );
return qtrue;
};
/*
================
AICast_ScriptAction_PlaySound
syntax: playsound
Currently only allows playing on the VOICE channel, unless you use a sound script (yay)
================
*/
qboolean AICast_ScriptAction_PlaySound( cast_state_t *cs, char *params ) {
if ( !params ) {
G_Error( "AI Scripting: syntax error\n\nplaysound \n" );
}
G_AddEvent( &g_entities[cs->bs->entitynum], EV_GENERAL_SOUND, G_SoundIndex( params ) );
// assume we are talking
cs->aiFlags |= AIFL_TALKING;
// randomly choose idle animation
if ( cs->aiFlags & AIFL_STAND_IDLE2 ) {
if ( cs->lastEnemy < 0 && cs->aiFlags & AIFL_TALKING ) {
g_entities[cs->entityNum].client->ps.eFlags |= EF_STAND_IDLE2;
} else {
g_entities[cs->entityNum].client->ps.eFlags &= ~EF_STAND_IDLE2;
}
}
return qtrue;
}
/*
=================
AICast_ScriptAction_NoAttack
syntax: noattack
=================
*/
qboolean AICast_ScriptAction_NoAttack( cast_state_t *cs, char *params ) {
if ( !params ) {
G_Error( "AI Scripting: syntax error\n\nnoattack \n" );
}
cs->castScriptStatus.scriptNoAttackTime = level.time + atoi( params );
return qtrue;
}
/*
=================
AICast_ScriptAction_Attack
syntax: attack [ainame]
Resumes attacking after a noattack was issued
if ainame is given, we will attack only that entity as long as they are alive
=================
*/
qboolean AICast_ScriptAction_Attack( cast_state_t *cs, char *params ) {
gentity_t *ent;
cs->castScriptStatus.scriptNoAttackTime = 0;
// if we have specified an aiName, then we should attack only this person
if ( params ) {
ent = AICast_FindEntityForName( params );
if ( !ent ) {
G_Error( "AI Scripting: \"attack\" command unable to find aiName \"%s\"", params );
}
cs->castScriptStatus.scriptAttackEnt = ent->s.number;
cs->enemyNum = ent->s.number;
} else {
cs->castScriptStatus.scriptAttackEnt = -1;
}
return qtrue;
}
/*
=================
AICast_ScriptAction_PlayAnim
syntax: playanim [legs/torso/both] [HOLDFRAME] [numLoops/FOREVER] [target]
NOTE: any new animations that are needed by the scripting system, will need to be added here
=================
*/
qboolean AICast_ScriptAction_PlayAnim( cast_state_t *cs, char *params ) {
char *pString, *token, tokens[3][MAX_QPATH];
int i, endtime, duration, numLoops;
gclient_t *client;
gentity_t *ent;
vec3_t org, vec;
qboolean forever = qfalse, setAngles = qfalse;
qboolean holdframe = qfalse;
pString = params;
client = &level.clients[cs->entityNum];
if ( level.animScriptData.modelInfo[level.animScriptData.clientModels[cs->entityNum] - 1]->version > 1 ) { // new (scripted) model
// read the name
token = COM_ParseExt( &pString, qfalse );
if ( !token || !token[0] ) {
G_Error( "AI Scripting: syntax error\n\nplayanim \n" );
}
Q_strncpyz( tokens[0], token, sizeof( tokens[0] ) );
Q_strlwr( tokens[0] );
// read the body part
token = COM_ParseExt( &pString, qfalse );
if ( !token || !token[0] ) {
G_Error( "AI Scripting: syntax error\n\nplayanim \n" );
}
Q_strncpyz( tokens[1], token, sizeof( tokens[1] ) );
Q_strlwr( tokens[1] );
// read the HOLDFRAME (optional)
token = COM_ParseExt( &pString, qfalse );
if ( token && token[0] && !Q_strcasecmp( token, "holdframe" ) ) {
holdframe = qtrue;
// read the numLoops (optional)
token = COM_ParseExt( &pString, qfalse );
}
// token is parsed above
if ( token && token[0] ) {
if ( !Q_strcasecmp( token, "forever" ) ) {
forever = qtrue;
numLoops = -1;
// read the target (optional)
token = COM_ParseExt( &pString, qfalse );
} else {
numLoops = atoi( token );
if ( !numLoops ) { // must be the target, so set loops to 1
numLoops = 1;
} else {
// read the target (optional)
token = COM_ParseExt( &pString, qfalse );
}
}
if ( token && token[0] ) {
// find this targetname
ent = G_Find( NULL, FOFS( targetname ), token );
if ( !ent ) {
ent = AICast_FindEntityForName( token );
if ( !ent ) {
G_Error( "AI Scripting: playanim cannot find targetname \"%s\"\n", token );
}
}
// set the view angle manually
BG_EvaluateTrajectory( &ent->s.pos, level.time, org );
VectorSubtract( org, cs->bs->origin, vec );
VectorNormalize( vec );
vectoangles( vec, cs->ideal_viewangles );
VectorCopy( cs->ideal_viewangles, cs->castScriptStatus.playanim_viewangles );
setAngles = qtrue;
}
} else {
numLoops = 1;
}
if ( cs->castScriptStatus.scriptFlags & SFL_FIRST_CALL ) {
// first time in here, play the anim
duration = BG_PlayAnimName( &( client->ps ), tokens[0], BG_IndexForString( tokens[1], animBodyPartsStr, qfalse ), qtrue, qfalse, qtrue );
if ( numLoops == -1 ) {
cs->scriptAnimTime = 0x7fffffff; // maximum time allowed
} else {
cs->scriptAnimTime = level.time + ( numLoops * duration );
}
if ( !strcmp( tokens[1], "torso" ) ) {
cs->scriptAnimNum = client->ps.torsoAnim & ~ANIM_TOGGLEBIT;
// adjust the duration according to numLoops
if ( numLoops > 1 ) {
client->ps.torsoTimer += duration * numLoops;
} else if ( numLoops == -1 ) {
client->ps.torsoTimer = 9999;
}
} else { // dont move
cs->scriptAnimNum = client->ps.legsAnim & ~ANIM_TOGGLEBIT;
cs->castScriptStatus.scriptNoMoveTime = cs->scriptAnimTime;
// lock the viewangles
if ( !cs->castScriptStatus.playAnimViewlockTime || cs->castScriptStatus.playAnimViewlockTime < level.time ) {
VectorCopy( cs->ideal_viewangles, cs->castScriptStatus.playanim_viewangles );
}
cs->castScriptStatus.playAnimViewlockTime = cs->scriptAnimTime;
// adjust the duration according to numLoops
if ( numLoops > 1 ) {
client->ps.legsTimer += duration * ( numLoops - 1 );
if ( !strcmp( tokens[1], "both" ) ) {
client->ps.torsoTimer += duration * ( numLoops - 1 );
}
} else if ( numLoops == -1 ) {
client->ps.legsTimer = 9999;
if ( !strcmp( tokens[1], "both" ) ) {
client->ps.torsoTimer = 9999;
}
}
}
} else {
if ( holdframe ) {
// make sure it doesn't stop before the next command can be processed
if ( !strcmp( tokens[1], "torso" ) ) {
if ( client->ps.torsoTimer < 400 ) {
client->ps.torsoTimer = 400;
}
} else if ( !strcmp( tokens[1], "legs" ) ) {
if ( client->ps.legsTimer < 400 ) {
client->ps.legsTimer = 400;
}
} else if ( !strcmp( tokens[1], "both" ) ) {
if ( client->ps.torsoTimer < 400 ) {
client->ps.torsoTimer = 400;
}
if ( client->ps.legsTimer < 400 ) {
client->ps.legsTimer = 400;
}
}
}
// keep it looping if forever
if ( forever ) {
if ( !strcmp( tokens[1], "torso" ) ) {
client->ps.torsoTimer = 9999;
} else {
client->ps.legsTimer = 9999;
if ( !strcmp( tokens[1], "both" ) ) {
client->ps.torsoTimer = 9999;
}
}
return qfalse;
}
// wait for the anim to stop playing
if ( cs->scriptAnimTime <= level.time ) {
return qtrue;
}
}
return qfalse;
} else { // old model
for ( i = 0; i < 3; i++ ) {
token = COM_ParseExt( &pString, qfalse );
if ( !token || !token[0] ) {
//G_Error("AI Scripting: syntax error\n\nplayanim [legs/torso/both]\n");
G_Printf( "AI Scripting: syntax error\n\nplayanim \n" );
return qtrue;
} else {
Q_strncpyz( tokens[i], token, sizeof( tokens[i] ) );
}
}
Q_strlwr( tokens[2] );
endtime = cs->castScriptStatus.castScriptStackChangeTime + atoi( tokens[1] );
duration = endtime - level.time + 200; // so animations don't run out before starting a next animation
if ( duration > 2000 ) {
duration = 2000;
}
cs->scriptAnimTime = level.time;
if ( duration < 200 ) {
return qtrue; // done playing animation
}
// call the anims directly based on the animation token
//if (cs->castScriptStatus.castScriptStackChangeTime == level.time) {
for ( i = 0; i < MAX_ANIMATIONS; i++ ) {
if ( !Q_strcasecmp( tokens[0], animStrings[i] ) ) {
if ( !Q_strcasecmp( tokens[2],"torso" ) ) {
if ( ( client->ps.torsoAnim & ~ANIM_TOGGLEBIT ) != i ) {
client->ps.torsoAnim = ( ( client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | i;
}
client->ps.torsoTimer = duration;
} else if ( !Q_strcasecmp( tokens[2],"legs" ) ) {
if ( ( client->ps.legsAnim & ~ANIM_TOGGLEBIT ) != i ) {
client->ps.legsAnim = ( ( client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | i;
}
client->ps.legsTimer = duration;
} else if ( !Q_strcasecmp( tokens[2],"both" ) ) {
if ( ( client->ps.torsoAnim & ~ANIM_TOGGLEBIT ) != i ) {
client->ps.torsoAnim = ( ( client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | i;
}
client->ps.torsoTimer = duration;
if ( ( client->ps.legsAnim & ~ANIM_TOGGLEBIT ) != i ) {
client->ps.legsAnim = ( ( client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | i;
}
client->ps.legsTimer = duration;
} else {
G_Printf( "AI Scripting: syntax error\n\nplayanim \n" );
}
break;
}
}
if ( i == MAX_ANIMATIONS ) {
G_Printf( "AI Scripting: playanim has unknown or invalid animation \"%s\"\n", tokens[0] );
}
//}
if ( !strcmp( tokens[2], "torso" ) ) {
cs->scriptAnimNum = client->ps.torsoAnim & ~ANIM_TOGGLEBIT;
} else {
cs->scriptAnimNum = client->ps.legsAnim & ~ANIM_TOGGLEBIT;
}
if ( cs->castScriptStatus.scriptNoMoveTime < level.time + 300 ) {
cs->castScriptStatus.scriptNoMoveTime = level.time + 300;
}
if ( cs->castScriptStatus.scriptNoAttackTime < level.time + 300 ) {
cs->castScriptStatus.scriptNoAttackTime = level.time + 300;
}
return qfalse;
}
};
/*
=================
AICast_ScriptAction_ClearAnim
stops any animation that is currently playing
=================
*/
qboolean AICast_ScriptAction_ClearAnim( cast_state_t *cs, char *params ) {
gclient_t *client;
client = &level.clients[cs->entityNum];
client->ps.torsoTimer = 0;
client->ps.legsTimer = 0;
// let us move again
cs->castScriptStatus.scriptNoMoveTime = 0;
return qtrue;
}
/*
=================
AICast_ScriptAction_SetAmmo
syntax: setammo
=================
*/
qboolean AICast_ScriptAction_SetAmmo( cast_state_t *cs, char *params ) {
char *pString, *token;
int weapon;
int i;
pString = params;
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
G_Error( "AI Scripting: setammo without ammo identifier\n" );
}
weapon = WP_NONE;
for ( i = 1; bg_itemlist[i].classname; i++ )
{
//----(SA) first try the name they see in the editor, then the pickup name
if ( !Q_strcasecmp( token, bg_itemlist[i].classname ) ) {
weapon = bg_itemlist[i].giTag;
break;
}
if ( !Q_strcasecmp( token, bg_itemlist[i].pickup_name ) ) {
weapon = bg_itemlist[i].giTag;
break;
}
}
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
G_Error( "AI Scripting: setammo without ammo count\n" );
}
if ( weapon != WP_NONE ) {
// give them the ammo
if ( atoi( token ) ) {
int amt;
amt = atoi( token );
if ( amt > 50 + ammoTable[BG_FindAmmoForWeapon( weapon )].maxammo ) {
amt = 999; // unlimited
}
Add_Ammo( &g_entities[cs->entityNum], weapon, amt, qtrue );
} else {
// remove ammo for this weapon
g_entities[cs->entityNum].client->ps.ammo[BG_FindAmmoForWeapon( weapon )] = 0;
g_entities[cs->entityNum].client->ps.ammoclip[BG_FindClipForWeapon( weapon )] = 0;
}
} else {
if ( g_cheats.integer ) {
G_Printf( "--SCRIPTER WARNING-- AI Scripting: setammo: unknown ammo \"%s\"", params );
}
return qfalse; // (SA) temp as scripts transition to new names
}
return qtrue;
};
/*
=================
AICast_ScriptAction_SetClip
syntax: setclip
=================
*/
qboolean AICast_ScriptAction_SetClip( cast_state_t *cs, char *params ) {
char *pString, *token;
int weapon;
int i;
pString = params;
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
G_Error( "AI Scripting: setclip without weapon identifier\n" );
}
weapon = WP_NONE;
for ( i = 1; bg_itemlist[i].classname; i++ )
{
//----(SA) first try the name they see in the editor, then the pickup name
if ( !Q_strcasecmp( token, bg_itemlist[i].classname ) ) {
weapon = bg_itemlist[i].giTag;
break;
}
if ( !Q_strcasecmp( token, bg_itemlist[i].pickup_name ) ) {
weapon = bg_itemlist[i].giTag;
break;
}
}
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
G_Error( "AI Scripting: setclip without ammo count\n" );
}
if ( weapon != WP_NONE ) {
int spillover = atoi( token ) - ammoTable[weapon].maxclip;
if ( spillover > 0 ) {
// there was excess, put it in storage and fill the clip
g_entities[cs->entityNum].client->ps.ammo[BG_FindAmmoForWeapon( weapon )] += spillover;
g_entities[cs->entityNum].client->ps.ammoclip[BG_FindClipForWeapon( weapon )] = ammoTable[weapon].maxclip;
} else {
// set the clip amount to the exact specified value
g_entities[cs->entityNum].client->ps.ammoclip[weapon] = atoi( token );
}
} else {
// G_Printf( "--SCRIPTER WARNING-- AI Scripting: setclip: unknown weapon \"%s\"", params );
return qfalse; // (SA) temp as scripts transition to new names
// G_Error( "AI Scripting: setclip: unknown weapon \"%s\"", params );
}
return qtrue;
};
/*
==============
AICast_ScriptAction_SuggestWeapon
==============
*/
qboolean AICast_ScriptAction_SuggestWeapon( cast_state_t *cs, char *params ) {
int weapon;
int i;
//int suggestedweaps = 0; // TTimo: unused
weapon = WP_NONE;
for ( i = 1; bg_itemlist[i].classname; i++ )
{
//----(SA) first try the name they see in the editor, then the pickup name
if ( !Q_strcasecmp( params, bg_itemlist[i].classname ) ) {
weapon = bg_itemlist[i].giTag;
break;
}
if ( !Q_strcasecmp( params, bg_itemlist[i].pickup_name ) ) {
weapon = bg_itemlist[i].giTag;
break;
}
}
if ( weapon != WP_NONE ) {
G_AddEvent( &g_entities[cs->entityNum], EV_SUGGESTWEAP, weapon );
} else {
G_Error( "AI Scripting: suggestweapon: unknown weapon \"%s\"", params );
}
return qtrue;
}
/*
=================
AICast_ScriptAction_SelectWeapon
syntax: selectweapon
=================
*/
qboolean AICast_ScriptAction_SelectWeapon( cast_state_t *cs, char *params ) {
int weapon;
int i;
weapon = WP_NONE;
for ( i = 1; bg_itemlist[i].classname; i++ )
{
//----(SA) first try the name they see in the editor, then the pickup name
if ( !Q_strcasecmp( params, bg_itemlist[i].classname ) ) {
weapon = bg_itemlist[i].giTag;
break;
}
if ( !Q_strcasecmp( params, bg_itemlist[i].pickup_name ) ) {
weapon = bg_itemlist[i].giTag;
break;
}
}
if ( weapon != WP_NONE ) {
if ( cs->bs ) {
cs->weaponNum = weapon;
}
cs->castScriptStatus.scriptFlags |= SFL_NOCHANGEWEAPON;
g_entities[cs->entityNum].client->ps.weapon = weapon;
g_entities[cs->entityNum].client->ps.weaponstate = WEAPON_READY;
if ( !cs->aiCharacter ) { // only do this for player
//vmCvar_t cvar;
//
g_entities[cs->entityNum].client->ps.weaponTime = 750; // (SA) HACK: FIXME: TODO: delay to catch initial weapon reload
// tell it which weapon to use after spawning in
//trap_Cvar_Register( &cvar, "cg_loadWeaponSelect", "0", CVAR_ROM );
//trap_Cvar_Set( "cg_loadWeaponSelect", va("%i", g_entities[cs->entityNum].client->ps.weapon ) );
}
} else {
// G_Printf( "--SCRIPTER WARNING-- AI Scripting: selectweapon: unknown weapon \"%s\"", params );
// return qfalse; // (SA) temp as scripts transition to new names
G_Error( "AI Scripting: selectweapon: unknown weapon \"%s\"", params );
}
return qtrue;
};
//----(SA) added
/*
==============
AICast_ScriptAction_GiveArmor
syntax: setarmor
==============
*/
qboolean AICast_ScriptAction_SetArmor( cast_state_t *cs, char *params ) {
if ( !params || !params[0] ) {
G_Error( "AI Scripting: setarmor requires an armor value" );
}
g_entities[cs->entityNum].client->ps.stats[STAT_ARMOR] += atoi( params );
return qtrue;
}
/*
==============
AICast_ScriptAction_GiveArmor
syntax: givearmor
will probably be more like:
syntax: givearmor
==============
*/
qboolean AICast_ScriptAction_GiveArmor( cast_state_t *cs, char *params ) {
int i;
gitem_t *item = 0;
for ( i = 1; bg_itemlist[i].classname; i++ ) {
//----(SA) first try the name they see in the editor, then the pickup name
if ( !Q_strcasecmp( params, bg_itemlist[i].classname ) ) {
item = &bg_itemlist[i];
}
if ( !Q_strcasecmp( params, bg_itemlist[i].pickup_name ) ) {
item = &bg_itemlist[i];
}
}
if ( !item ) { // item not found
G_Error( "AI Scripting: givearmor%s, unknown item", params );
}
if ( item->giType == IT_ARMOR ) {
g_entities[cs->entityNum].client->ps.stats[STAT_ARMOR] += item->quantity;
if ( g_entities[cs->entityNum].client->ps.stats[STAT_ARMOR] > 100 ) {
g_entities[cs->entityNum].client->ps.stats[STAT_ARMOR] = 100;
}
}
return qtrue;
}
//----(SA) end
/*
=================
AICast_ScriptAction_GiveWeapon
syntax: giveweapon
=================
*/
qboolean AICast_ScriptAction_GiveWeapon( cast_state_t *cs, char *params ) {
int weapon;
int i;
gentity_t *ent = &g_entities[cs->entityNum];
weapon = WP_NONE;
for ( i = 1; bg_itemlist[i].classname; i++ )
{
//----(SA) first try the name they see in the editor, then the pickup name
if ( !Q_strcasecmp( params, bg_itemlist[i].classname ) ) {
weapon = bg_itemlist[i].giTag;
break;
}
if ( !Q_strcasecmp( params, bg_itemlist[i].pickup_name ) ) {
weapon = bg_itemlist[i].giTag;
}
}
if ( weapon == WP_COLT ) {
// if you had the colt already, now you've got two!
if ( COM_BitCheck( g_entities[cs->entityNum].client->ps.weapons, WP_COLT ) ) {
weapon = WP_AKIMBO;
}
}
if ( weapon != WP_NONE ) {
COM_BitSet( g_entities[cs->entityNum].client->ps.weapons, weapon );
//----(SA) some weapons always go together (and they share a clip, so this is okay)
if ( weapon == WP_GARAND ) {
COM_BitSet( g_entities[cs->entityNum].client->ps.weapons, WP_SNOOPERSCOPE );
}
if ( weapon == WP_SNOOPERSCOPE ) {
COM_BitSet( g_entities[cs->entityNum].client->ps.weapons, WP_GARAND );
}
if ( weapon == WP_FG42 ) {
COM_BitSet( g_entities[cs->entityNum].client->ps.weapons, WP_FG42SCOPE );
}
if ( weapon == WP_SNIPERRIFLE ) {
COM_BitSet( g_entities[cs->entityNum].client->ps.weapons, WP_MAUSER );
}
//----(SA) end
// monsters have full ammo for their attacks
// knife gets infinite ammo too
if ( !Q_strncasecmp( params, "monsterattack", 13 ) || weapon == WP_KNIFE ) {
g_entities[cs->entityNum].client->ps.ammo[BG_FindAmmoForWeapon( weapon )] = 999;
Fill_Clip( &g_entities[cs->entityNum].client->ps, weapon ); //----(SA) added
}
// conditional flags
if ( ent->aiCharacter == AICHAR_ZOMBIE ) {
if ( COM_BitCheck( ent->client->ps.weapons, WP_MONSTER_ATTACK1 ) ) {
cs->aiFlags |= AIFL_NO_FLAME_DAMAGE;
SET_FLAMING_ZOMBIE( ent->s, 1 );
}
}
} else {
G_Error( "AI Scripting: giveweapon %s, unknown weapon", params );
}
return qtrue;
};
/*
=================
AICast_ScriptAction_TakeWeapon
syntax: takeweapon
=================
*/
qboolean AICast_ScriptAction_TakeWeapon( cast_state_t *cs, char *params ) {
int weapon;
int i;
weapon = WP_NONE;
if ( !Q_stricmp( params, "all" ) ) {
// clear out all weapons
memset( g_entities[cs->entityNum].client->ps.weapons, 0, sizeof( g_entities[cs->entityNum].client->ps.weapons ) );
memset( g_entities[cs->entityNum].client->ps.ammo, 0, sizeof( g_entities[cs->entityNum].client->ps.ammo ) );
memset( g_entities[cs->entityNum].client->ps.ammoclip, 0, sizeof( g_entities[cs->entityNum].client->ps.ammoclip ) );
cs->weaponNum = WP_NONE;
} else {
for ( i = 1; bg_itemlist[i].classname; i++ )
{
//----(SA) first try the name they see in the editor, then the pickup name
if ( !Q_strcasecmp( params, bg_itemlist[i].classname ) ) {
weapon = bg_itemlist[i].giTag;
break;
}
if ( !Q_strcasecmp( params, bg_itemlist[i].pickup_name ) ) {
weapon = bg_itemlist[i].giTag;
break;
}
}
if ( weapon != WP_NONE ) {
qboolean clear;
if ( weapon == WP_AKIMBO ) {
// take both the colt /and/ the akimbo weapons when 'akimbo' is specified
COM_BitClear( g_entities[cs->entityNum].client->ps.weapons, WP_COLT );
} else if ( weapon == WP_COLT ) {
// take 'akimbo' first if it's there, then take 'colt'
if ( COM_BitCheck( g_entities[cs->entityNum].client->ps.weapons, WP_AKIMBO ) ) {
weapon = WP_AKIMBO;
}
}
//
COM_BitClear( g_entities[cs->entityNum].client->ps.weapons, weapon );
// also remove the ammo for this weapon
// but first make sure we dont have any other weapons that use the same ammo
clear = qtrue;
for ( i = 0; i < WP_NUM_WEAPONS; i++ ) {
if ( BG_FindAmmoForWeapon( weapon ) != BG_FindAmmoForWeapon( i ) ) {
continue;
}
if ( COM_BitCheck( g_entities[cs->entityNum].client->ps.weapons, i ) ) {
clear = qfalse;
}
}
if ( clear ) {
// (SA) temp only. commented out for pistol guys in escape1
// g_entities[cs->entityNum].client->ps.ammo[BG_FindAmmoForWeapon(weapon)] = 0;
}
} else {
G_Error( "AI Scripting: takeweapon %s, unknown weapon", params );
}
}
if ( !g_entities[cs->entityNum].client->ps.weapons ) {
if ( cs->bs ) {
cs->weaponNum = WP_NONE;
} else {
g_entities[cs->entityNum].client->ps.weapon = WP_NONE;
}
}
return qtrue;
};
//----(SA) added
/*
==============
AICast_ScriptAction_GiveInventory
==============
*/
qboolean AICast_ScriptAction_GiveInventory( cast_state_t *cs, char *params ) {
int i;
gitem_t *item = 0;
for ( i = 1; bg_itemlist[i].classname; i++ ) {
//----(SA) first try the name they see in the editor, then the pickup name
if ( !Q_strcasecmp( params, bg_itemlist[i].classname ) ) {
item = &bg_itemlist[i];
}
if ( !Q_strcasecmp( params, bg_itemlist[i].pickup_name ) ) {
item = &bg_itemlist[i];
}
}
if ( !item ) { // item not found
G_Error( "AI Scripting: giveinventory %s, unknown item", params );
}
if ( item->giType == IT_KEY ) {
g_entities[cs->entityNum].client->ps.stats[STAT_KEYS] |= ( 1 << item->giTag );
} else if ( item->giType == IT_HOLDABLE ) {
// (SA) TODO
}
return qtrue;
};
//----(SA) end
/*
=================
AICast_ScriptAction_Movetype
syntax: movetype
Sets this character's movement type, will exist until another movetype command is called
=================
*/
qboolean AICast_ScriptAction_Movetype( cast_state_t *cs, char *params ) {
if ( !Q_strcasecmp( params, "walk" ) ) {
cs->movestate = MS_WALK;
cs->movestateType = MSTYPE_PERMANENT;
return qtrue;
}
if ( !Q_strcasecmp( params, "run" ) ) {
cs->movestate = MS_RUN;
cs->movestateType = MSTYPE_PERMANENT;
return qtrue;
}
if ( !Q_strcasecmp( params, "crouch" ) ) {
cs->movestate = MS_CROUCH;
cs->movestateType = MSTYPE_PERMANENT;
return qtrue;
}
if ( !Q_strcasecmp( params, "default" ) ) {
cs->movestate = MS_DEFAULT;
cs->movestateType = MSTYPE_NONE;
return qtrue;
}
return qtrue;
}
/*
=================
AICast_ScriptAction_AlertEntity
syntax: alertentity
=================
*/
qboolean AICast_ScriptAction_AlertEntity( cast_state_t *cs, char *params ) {
gentity_t *ent;
if ( !params || !params[0] ) {
G_Error( "AI Scripting: alertentity without targetname\n" );
}
// find this targetname
ent = G_Find( NULL, FOFS( targetname ), params );
if ( !ent ) {
ent = G_Find( NULL, FOFS( aiName ), params ); // look for an AI
if ( !ent || !ent->client ) { // accept only AI for aiName check
G_Error( "AI Scripting: alertentity cannot find targetname \"%s\"\n", params );
}
}
// call this entity's AlertEntity function
if ( !ent->AIScript_AlertEntity ) {
if ( !ent->client && ent->use && !Q_stricmp( ent->classname, "ai_trigger" ) ) {
ent->use( ent, NULL, NULL );
return qtrue;
}
if ( aicast_debug.integer ) {
G_Printf( "AI Scripting: alertentity \"%s\" (classname = %s) doesn't have an \"AIScript_AlertEntity\" function\n", params, ent->classname );
}
//G_Error( "AI Scripting: alertentity \"%s\" (classname = %s) doesn't have an \"AIScript_AlertEntity\" function\n", params, ent->classname );
return qtrue;
}
ent->AIScript_AlertEntity( ent );
return qtrue;
}
/*
=================
AICast_ScriptAction_SaveGame
NOTE: only use this command in "player" scripts, not for AI
syntax: savegame
=================
*/
qboolean AICast_ScriptAction_SaveGame( cast_state_t *cs, char *params ) {
char *pString, *saveName;
pString = params;
if ( cs->bs ) {
G_Error( "AI Scripting: savegame attempted on a non-player" );
}
//----(SA) check for parameter
saveName = COM_ParseExt( &pString, qfalse );
if ( !saveName[0] ) {
G_SaveGame( NULL ); // save the default "current" savegame
} else {
G_SaveGame( saveName );
}
//----(SA) end
return qtrue;
}
/*
=================
AICast_ScriptAction_FireAtTarget
syntax: fireattarget [duration]
=================
*/
qboolean AICast_ScriptAction_FireAtTarget( cast_state_t *cs, 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] ) {
G_Error( "AI Scripting: fireattarget without a targetname\n" );
}
if ( !cs->bs ) {
G_Error( "AI Scripting: fireattarget called for non-AI character\n" );
}
// find this targetname
ent = G_Find( NULL, FOFS( targetname ), token );
if ( !ent ) {
ent = AICast_FindEntityForName( token );
if ( !ent ) {
G_Error( "AI Scripting: fireattarget cannot find targetname/aiName \"%s\"\n", token );
}
}
// if this is our first call for this fireattarget, record the ammo count
if ( cs->castScriptStatus.scriptFlags & SFL_FIRST_CALL ) {
cs->lastWeaponFired = 0;
}
// make sure we don't move or shoot while turning to our target
if ( cs->castScriptStatus.scriptNoAttackTime < level.time ) {
cs->castScriptStatus.scriptNoAttackTime = level.time + 500;
}
// dont reload prematurely
cs->noReloadTime = level.time + 1000;
// don't move while firing at all
//if (cs->castScriptStatus.scriptNoMoveTime < level.time) {
cs->castScriptStatus.scriptNoMoveTime = level.time + 500;
//}
// let us move our view, whether it looks bad or not
cs->castScriptStatus.playAnimViewlockTime = 0;
// set the view angle manually
BG_EvaluateTrajectory( &ent->s.pos, level.time, org );
VectorCopy( cs->bs->origin, src );
src[2] += cs->bs->cur_ps.viewheight;
VectorSubtract( org, src, vec );
VectorNormalize( vec );
vectoangles( vec, cs->ideal_viewangles );
for ( i = 0; i < 2; i++ ) {
diff = abs( AngleDifference( cs->bs->cur_ps.viewangles[i], cs->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( cs->bs->client );
//
cs->bFlags |= BFL_ATTACKED;
//
// if we haven't fired yet
if ( !cs->lastWeaponFired ) {
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
}
// only return true if we've been firing for long enough
return ( ( cs->castScriptStatus.castScriptStackChangeTime + atoi( token ) ) < level.time );
}
/*
=================
AICast_ScriptAction_GodMode
syntax: godmode
=================
*/
qboolean AICast_ScriptAction_GodMode( cast_state_t *cs, char *params ) {
if ( !params || !params[0] ) {
G_Error( "AI Scripting: godmode requires an on/off specifier\n" );
}
if ( !Q_stricmp( params, "on" ) ) {
g_entities[cs->bs->entitynum].flags |= FL_GODMODE;
} else if ( !Q_stricmp( params, "off" ) ) {
g_entities[cs->bs->entitynum].flags &= ~FL_GODMODE;
} else {
G_Error( "AI Scripting: godmode requires an on/off specifier\n" );
}
return qtrue;
}
/*
=================
AICast_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
accum random
accum bitset
accum bitreset
accum abort_if_bitset
accum abort_if_not_bitset
=================
*/
qboolean AICast_ScriptAction_Accum( cast_state_t *cs, char *params ) {
char *pString, *token, lastToken[MAX_QPATH];
int bufferIndex;
pString = params;
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
G_Error( "AI Scripting: accum without a buffer index\n" );
}
bufferIndex = atoi( token );
if ( bufferIndex >= MAX_SCRIPT_ACCUM_BUFFERS ) {
G_Error( "AI Scripting: accum buffer is outside range (0 - %i)\n", MAX_SCRIPT_ACCUM_BUFFERS );
}
token = COM_ParseExt( &pString, qfalse );
if ( !token[0] ) {
G_Error( "AI Scripting: accum without a command\n" );
}
Q_strncpyz( lastToken, token, sizeof( lastToken ) );
token = COM_ParseExt( &pString, qfalse );
if ( !Q_stricmp( lastToken, "inc" ) ) {
if ( !token[0] ) {
G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken );
}
cs->scriptAccumBuffer[bufferIndex] += atoi( token );
} else if ( !Q_stricmp( lastToken, "abort_if_less_than" ) ) {
if ( !token[0] ) {
G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken );
}
if ( cs->scriptAccumBuffer[bufferIndex] < atoi( token ) ) {
// abort the current script
cs->castScriptStatus.castScriptStackHead = cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_greater_than" ) ) {
if ( !token[0] ) {
G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken );
}
if ( cs->scriptAccumBuffer[bufferIndex] > atoi( token ) ) {
// abort the current script
cs->castScriptStatus.castScriptStackHead = cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_not_equal" ) ) {
if ( !token[0] ) {
G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken );
}
if ( cs->scriptAccumBuffer[bufferIndex] != atoi( token ) ) {
// abort the current script
cs->castScriptStatus.castScriptStackHead = cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_equal" ) ) {
if ( !token[0] ) {
G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken );
}
if ( cs->scriptAccumBuffer[bufferIndex] == atoi( token ) ) {
// abort the current script
cs->castScriptStatus.castScriptStackHead = cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "bitset" ) ) {
if ( !token[0] ) {
G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken );
}
cs->scriptAccumBuffer[bufferIndex] |= ( 1 << atoi( token ) );
} else if ( !Q_stricmp( lastToken, "bitreset" ) ) {
if ( !token[0] ) {
G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken );
}
cs->scriptAccumBuffer[bufferIndex] &= ~( 1 << atoi( token ) );
} else if ( !Q_stricmp( lastToken, "abort_if_bitset" ) ) {
if ( !token[0] ) {
G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken );
}
if ( cs->scriptAccumBuffer[bufferIndex] & ( 1 << atoi( token ) ) ) {
// abort the current script
cs->castScriptStatus.castScriptStackHead = cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "abort_if_not_bitset" ) ) {
if ( !token[0] ) {
G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken );
}
if ( !( cs->scriptAccumBuffer[bufferIndex] & ( 1 << atoi( token ) ) ) ) {
// abort the current script
cs->castScriptStatus.castScriptStackHead = cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack.numItems;
}
} else if ( !Q_stricmp( lastToken, "set" ) ) {
if ( !token[0] ) {
G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken );
}
cs->scriptAccumBuffer[bufferIndex] = atoi( token );
} else if ( !Q_stricmp( lastToken, "random" ) ) {
if ( !token[0] ) {
G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken );
}
cs->scriptAccumBuffer[bufferIndex] = rand() % atoi( token );
} else {
G_Error( "AI Scripting: accum %s: unknown command\n", params );
}
return qtrue;
}
/*
=================
AICast_ScriptAction_SpawnCast
syntax: spawncast
is the entity marker which has the position and angles of where we want the new
cast AI to spawn
=================
*/
qboolean AICast_ScriptAction_SpawnCast( cast_state_t *cs, char *params ) {
// char *pString, *token;
// char *classname;
// gentity_t *targetEnt, *newCast;
G_Error( "AI Scripting: spawncast is no longer functional. Use trigger_spawn instead.\n" );
return qfalse;
/*
if (!params[0]) {
G_Error( "AI Scripting: spawncast without a classname\n" );
}
pString = params;
token = COM_ParseExt( &pString, qfalse );
if (!token[0]) {
G_Error( "AI Scripting: spawncast without a classname\n" );
}
classname = G_Alloc( strlen(token)+1 );
Q_strncpyz( classname, token, strlen(token)+1 );
token = COM_ParseExt( &pString, qfalse );
if (!token[0]) {
G_Error( "AI Scripting: spawncast without a targetname\n" );
}
targetEnt = G_Find( NULL, FOFS(targetname), token );
if (!targetEnt) {
G_Error( "AI Scripting: cannot find targetname \"%s\"\n", token );
}
token = COM_ParseExt( &pString, qfalse );
if (!token[0]) {
G_Error( "AI Scripting: spawncast without an ainame\n" );
}
newCast = G_Spawn();
newCast->classname = classname;
VectorCopy( targetEnt->s.origin, newCast->s.origin );
VectorCopy( targetEnt->s.angles, newCast->s.angles );
newCast->aiName = G_Alloc( strlen(token)+1 );
Q_strncpyz( newCast->aiName, token, strlen(token)+1 );
if (!G_CallSpawn( newCast )) {
G_Error( "AI Scripting: spawncast for unknown entity \"%s\"\n", newCast->classname );
}
return qtrue;
*/
}
/*
=================
AICast_ScriptAction_MissionFailed
syntax: missionfailed