/* =========================================================================== 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.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" /* Scripting that allows the designers to control the behaviour of AI characters according to each different scenario. */ // action functions need to be declared here so they can be accessed in the scriptAction table qboolean AICast_ScriptAction_GotoMarker( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_WalkToMarker( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_CrouchToMarker( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_GotoCast( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_WalkToCast( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_CrouchToCast( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_Wait( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_AbortIfLoadgame( cast_state_t *cs, char *params ); //----(SA) added qboolean AICast_ScriptAction_Trigger( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_FollowCast( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_PlaySound( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_NoAttack( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_Attack( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_PlayAnim( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_ClearAnim( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_SetAmmo( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_SetClip( cast_state_t *cs, char *params ); //----(SA) added qboolean AICast_ScriptAction_SelectWeapon( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_GiveArmor( cast_state_t *cs, char *params ); //----(SA) added qboolean AICast_ScriptAction_SetArmor( cast_state_t *cs, char *params ); //----(SA) added qboolean AICast_ScriptAction_SuggestWeapon( cast_state_t *cs, char *params ); //----(SA) added qboolean AICast_ScriptAction_GiveWeapon( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_GiveInventory( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_TakeWeapon( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_Movetype( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_AlertEntity( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_SaveGame( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_FireAtTarget( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_GodMode( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_Accum( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_SpawnCast( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_MissionFailed( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_ObjectiveMet( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_ObjectivesNeeded( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_NoAIDamage( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_Print( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_FaceTargetAngles( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_ResetScript( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_Mount( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_Unmount( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_SavePersistant( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_ChangeLevel( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_EndGame( cast_state_t *cs, char *params ); //----(SA) added qboolean AICast_ScriptAction_Teleport( cast_state_t *cs, char *params ); //----(SA) added qboolean AICast_ScriptAction_FoundSecret( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_NoSight( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_Sight( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_NoAvoid( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_Avoid( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_Attrib( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_DenyAction( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_LightningDamage( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_Headlook( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_BackupScript( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_RestoreScript( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_StateType( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_KnockBack( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_Zoom( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_Parachute( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_Cigarette( cast_state_t *cs, char *params ); //----(SA) added qboolean AICast_ScriptAction_StartCam( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_StopCam( cast_state_t *cs, char *params ); //----(SA) added qboolean AICast_ScriptAction_StartCamBlack( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_EntityScriptName( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_AIScriptName( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_SetHealth( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_NoTarget( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_Cvar( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_MusicStart( cast_state_t *cs, char *params ); //----(SA) qboolean AICast_ScriptAction_MusicPlay( cast_state_t *cs, char *params ); //----(SA) qboolean AICast_ScriptAction_MusicStop( cast_state_t *cs, char *params ); //----(SA) qboolean AICast_ScriptAction_MusicFade( cast_state_t *cs, char *params ); //----(SA) qboolean AICast_ScriptAction_MusicQueue( cast_state_t *cs, char *params ); //----(SA) qboolean AICast_ScriptAction_ExplicitRouting( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_LockPlayer( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_AnimCondition( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_PushAway( cast_state_t *cs, char *params ); qboolean AICast_ScriptAction_CatchFire( cast_state_t *cs, char *params ); // these are the actions that each event can call cast_script_stack_action_t scriptActions[] = { {"gotomarker", AICast_ScriptAction_GotoMarker}, {"runtomarker", AICast_ScriptAction_GotoMarker}, {"walktomarker", AICast_ScriptAction_WalkToMarker}, {"crouchtomarker", AICast_ScriptAction_CrouchToMarker}, {"gotocast", AICast_ScriptAction_GotoCast}, {"runtocast", AICast_ScriptAction_GotoCast}, {"walktocast", AICast_ScriptAction_WalkToCast}, {"crouchtocast", AICast_ScriptAction_CrouchToCast}, {"followcast", AICast_ScriptAction_FollowCast}, {"playsound", AICast_ScriptAction_PlaySound}, {"playanim", AICast_ScriptAction_PlayAnim}, {"clearanim", AICast_ScriptAction_ClearAnim}, {"wait", AICast_ScriptAction_Wait}, {"abort_if_loadgame",AICast_ScriptAction_AbortIfLoadgame}, //----(SA) added {"trigger", AICast_ScriptAction_Trigger}, {"setammo", AICast_ScriptAction_SetAmmo}, {"setclip", AICast_ScriptAction_SetClip}, //----(SA) added {"selectweapon", AICast_ScriptAction_SelectWeapon}, {"noattack", AICast_ScriptAction_NoAttack}, {"suggestweapon", AICast_ScriptAction_SuggestWeapon}, //----(SA) added {"attack", AICast_ScriptAction_Attack}, {"givearmor", AICast_ScriptAction_GiveArmor}, //----(SA) added {"setarmor", AICast_ScriptAction_SetArmor}, //----(SA) added {"giveinventory", AICast_ScriptAction_GiveInventory}, {"giveweapon", AICast_ScriptAction_GiveWeapon}, {"takeweapon", AICast_ScriptAction_TakeWeapon}, {"movetype", AICast_ScriptAction_Movetype}, {"alertentity", AICast_ScriptAction_AlertEntity}, {"savegame", AICast_ScriptAction_SaveGame}, {"fireattarget", AICast_ScriptAction_FireAtTarget}, {"godmode", AICast_ScriptAction_GodMode}, {"accum", AICast_ScriptAction_Accum}, {"spawncast", AICast_ScriptAction_SpawnCast}, {"missionfailed", AICast_ScriptAction_MissionFailed}, {"missionsuccess", AICast_ScriptAction_ObjectiveMet}, {"objectivemet", AICast_ScriptAction_ObjectiveMet}, // dupe of missionsuccess so scripts can changeover to a more logical name {"objectivesneeded",AICast_ScriptAction_ObjectivesNeeded}, {"noaidamage", AICast_ScriptAction_NoAIDamage}, {"print", AICast_ScriptAction_Print}, {"facetargetangles",AICast_ScriptAction_FaceTargetAngles}, {"resetscript", AICast_ScriptAction_ResetScript}, {"mount", AICast_ScriptAction_Mount}, {"unmount", AICast_ScriptAction_Unmount}, {"savepersistant", AICast_ScriptAction_SavePersistant}, {"changelevel", AICast_ScriptAction_ChangeLevel}, {"endgame", AICast_ScriptAction_EndGame}, //----(SA) added {"teleport", AICast_ScriptAction_Teleport}, //----(SA) added {"foundsecret", AICast_ScriptAction_FoundSecret}, {"nosight", AICast_ScriptAction_NoSight}, {"sight", AICast_ScriptAction_Sight}, {"noavoid", AICast_ScriptAction_NoAvoid}, {"avoid", AICast_ScriptAction_Avoid}, {"attrib", AICast_ScriptAction_Attrib}, {"denyactivate", AICast_ScriptAction_DenyAction}, {"lightningdamage", AICast_ScriptAction_LightningDamage}, {"deny", AICast_ScriptAction_DenyAction}, {"headlook", AICast_ScriptAction_Headlook}, {"backupscript", AICast_ScriptAction_BackupScript}, {"restorescript", AICast_ScriptAction_RestoreScript}, {"statetype", AICast_ScriptAction_StateType}, {"knockback", AICast_ScriptAction_KnockBack}, {"zoom", AICast_ScriptAction_Zoom}, {"parachute", AICast_ScriptAction_Parachute}, {"cigarette", AICast_ScriptAction_Cigarette}, //----(SA) added {"startcam", AICast_ScriptAction_StartCam}, {"startcamblack", AICast_ScriptAction_StartCamBlack}, {"stopcam", AICast_ScriptAction_StopCam}, //----(SA) added {"entityscriptname",AICast_ScriptAction_EntityScriptName}, {"aiscriptname", AICast_ScriptAction_AIScriptName}, {"sethealth", AICast_ScriptAction_SetHealth}, {"notarget", AICast_ScriptAction_NoTarget}, {"cvar", AICast_ScriptAction_Cvar}, //----(SA) added some music interface {"mu_start", AICast_ScriptAction_MusicStart}, // (char *new_music, int time) // time to fade in {"mu_play", AICast_ScriptAction_MusicPlay}, // (char *new_music) {"mu_stop", AICast_ScriptAction_MusicStop}, // (int time) // time to fadeout {"mu_fade", AICast_ScriptAction_MusicFade}, // (float target_volume, int time) // time to fade to target {"mu_queue", AICast_ScriptAction_MusicQueue}, // (char *new_music) // music that will start when previous fades to 0 //----(SA) end {"explicit_routing", AICast_ScriptAction_ExplicitRouting}, {"lockplayer", AICast_ScriptAction_LockPlayer}, {"anim_condition", AICast_ScriptAction_AnimCondition}, {"pushaway", AICast_ScriptAction_PushAway}, {"catchfire", AICast_ScriptAction_CatchFire}, {NULL, NULL} }; qboolean AICast_EventMatch_StringEqual( cast_script_event_t *event, char *eventParm ); qboolean AICast_EventMatch_IntInRange( cast_script_event_t *event, char *eventParm ); // the list of events that can start an action sequence // NOTE!!: only append to this list, DO NOT INSERT!! cast_script_event_define_t scriptEvents[] = { {"spawn", NULL}, // called as each character is spawned into the game {"playerstart", NULL}, // called when player hits 'start' button {"enemysight", AICast_EventMatch_StringEqual}, // enemy has been sighted for the first time (once only) {"sight", AICast_EventMatch_StringEqual}, // non-enemy has been sighted for the first time (once only) {"enemydead", AICast_EventMatch_StringEqual}, // our enemy is now dead {"trigger", AICast_EventMatch_StringEqual}, // something has triggered us (always followed by an identifier) {"pain", AICast_EventMatch_IntInRange}, // we've been hurt {"death", AICast_EventMatch_StringEqual}, // RIP {"activate", AICast_EventMatch_StringEqual}, // "param" has just activated us {"enemysightcorpse",AICast_EventMatch_StringEqual}, // sighted the given enemy as a corpse, for the first time {"friendlysightcorpse", NULL}, // sighted a friendly as a corpse for the first time {"avoiddanger", AICast_EventMatch_StringEqual}, // we are avoiding something dangerous {"blocked", AICast_EventMatch_StringEqual}, // blocked by someone else {"statechange", AICast_EventMatch_StringEqual}, // changing aistates {"bulletimpact", NULL}, {"inspectbodystart", AICast_EventMatch_StringEqual}, // starting to travel to body for inspection {"inspectbodyend", AICast_EventMatch_StringEqual}, // reached body for inspection {"inspectsoundstart", AICast_EventMatch_StringEqual}, // reached sound for inspection {"inspectsoundend", AICast_EventMatch_StringEqual}, // reached sound for inspection {"attacksound", AICast_EventMatch_StringEqual}, // play a custom attack sound, and/or deny playing the default sound {"fakedeath", NULL}, {"bulletimpactsound", NULL}, {"inspectfriendlycombatstart", NULL}, {"painenemy", AICast_EventMatch_StringEqual}, {"forced_mg42_unmount", NULL}, {NULL, NULL} }; /* =============== AICast_EventMatch_StringEqual =============== */ qboolean AICast_EventMatch_StringEqual( cast_script_event_t *event, char *eventParm ) { if ( !event->params || !event->params[0] || ( eventParm && !Q_strcasecmp( event->params, eventParm ) ) ) { return qtrue; } else { return qfalse; } } /* =============== AICast_EventMatch_IntInRange =============== */ qboolean AICast_EventMatch_IntInRange( cast_script_event_t *event, char *eventParm ) { char *pString, *token; int int1, int2, eInt; // get the cast name pString = eventParm; token = COM_ParseExt( &pString, qfalse ); int1 = atoi( token ); token = COM_ParseExt( &pString, qfalse ); int2 = atoi( token ); eInt = atoi( event->params ); if ( eventParm && eInt > int1 && eInt <= int2 ) { return qtrue; } else { return qfalse; } } /* =============== AICast_EventForString =============== */ int AICast_EventForString( char *string ) { int i; for ( i = 0; scriptEvents[i].eventStr; i++ ) { if ( !Q_strcasecmp( string, scriptEvents[i].eventStr ) ) { return i; } } return -1; } //----(SA) added /* =============== AICast_ActionForString =============== */ cast_script_stack_action_t *AICast_ActionForString( cast_state_t *cs, char *string ) { int i; for ( i = 0; scriptActions[i].actionString; i++ ) { if ( !Q_strcasecmp( string, scriptActions[i].actionString ) ) { if ( !Q_strcasecmp( string, "foundsecret" ) ) { level.numSecrets++; G_SendMissionStats(); } return &scriptActions[i]; } } return NULL; } /* ============= AICast_ScriptLoad Loads the script for the current level into the buffer ============= */ void AICast_ScriptLoad( void ) { char filename[MAX_QPATH]; vmCvar_t mapname; fileHandle_t f; int len; level.scriptAI = NULL; trap_Cvar_VariableStringBuffer( "ai_scriptName", filename, sizeof( filename ) ); if ( strlen( filename ) > 0 ) { trap_Cvar_Register( &mapname, "ai_scriptName", "", CVAR_ROM ); } else { trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); } Q_strncpyz( filename, "maps/", sizeof( filename ) ); Q_strcat( filename, sizeof( filename ), mapname.string ); Q_strcat( filename, sizeof( filename ), ".ai" ); len = trap_FS_FOpenFile( filename, &f, FS_READ ); // make sure we clear out the temporary scriptname trap_Cvar_Set( "ai_scriptName", "" ); if ( len < 0 ) { return; } level.scriptAI = G_Alloc( len ); trap_FS_Read( level.scriptAI, len, f ); trap_FS_FCloseFile( f ); return; } /* ============== AICast_ScriptParse Parses the script for the given character ============== */ void AICast_ScriptParse( cast_state_t *cs ) { #define MAX_SCRIPT_EVENTS 64 gentity_t *ent; char *pScript; char *token; qboolean wantName; qboolean inScript; int eventNum; cast_script_event_t events[MAX_SCRIPT_EVENTS]; int numEventItems; cast_script_event_t *curEvent; char params[MAX_QPATH]; cast_script_stack_action_t *action; int i; int bracketLevel; qboolean buildScript; //----(SA) added if ( !level.scriptAI ) { return; } ent = &g_entities[cs->entityNum]; if ( !ent->aiName ) { return; } buildScript = trap_Cvar_VariableIntegerValue( "com_buildScript" ); buildScript = qtrue; pScript = level.scriptAI; wantName = qtrue; inScript = qfalse; COM_BeginParseSession( "AICast_ScriptParse" ); bracketLevel = 0; numEventItems = 0; memset( events, 0, sizeof( events ) ); while ( 1 ) { token = COM_Parse( &pScript ); if ( !token[0] ) { if ( !wantName ) { G_Error( "AICast_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); } break; } // end of script if ( token[0] == '}' ) { if ( inScript ) { break; } if ( wantName ) { G_Error( "AICast_ScriptParse(), Error (line %d): '}' found, but not expected.\n", COM_GetCurrentParseLine() ); } wantName = qtrue; } else if ( token[0] == '{' ) { if ( wantName ) { G_Error( "AICast_ScriptParse(), Error (line %d): '{' found, NAME expected.\n", COM_GetCurrentParseLine() ); } } else if ( wantName ) { if ( !Q_strcasecmp( ent->aiName, token ) ) { inScript = qtrue; numEventItems = 0; } wantName = qfalse; } else if ( inScript ) { if ( !Q_strcasecmp( token, "attributes" ) ) { // read in all the attributes AICast_CheckLevelAttributes( cs, ent, &pScript ); continue; } eventNum = AICast_EventForString( token ); if ( eventNum < 0 ) { G_Error( "AICast_ScriptParse(), Error (line %d): unknown event: %s.\n", COM_GetCurrentParseLine(), token ); } if ( numEventItems >= MAX_SCRIPT_EVENTS ) { G_Error( "AICast_ScriptParse(), Error (line %d): MAX_SCRIPT_EVENTS reached (%d)\n", COM_GetCurrentParseLine(), MAX_SCRIPT_EVENTS ); } // if this is a "friendlysightcorpse" event, then disable corpse vis sharing if ( !Q_stricmp( token, "friendlysightcorpse" ) ) { cs->aiFlags &= ~AIFL_CORPSESIGHTING; } curEvent = &events[numEventItems]; curEvent->eventNum = eventNum; memset( params, 0, sizeof( params ) ); // parse any event params before the start of this event's actions while ( ( token = COM_Parse( &pScript ) ) && ( token[0] != '{' ) ) { if ( !token[0] ) { G_Error( "AICast_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); } if ( eventNum == 13 ) { // statechange event, check params if ( strlen( token ) > 1 ) { if ( BG_IndexForString( token, animStateStr, qtrue ) < 0 ) { G_Error( "AICast_ScriptParse(), Error (line %d): unknown state type '%s'.\n", COM_GetCurrentParseLine(), token ); } } } if ( strlen( params ) ) { // add a space between each param Q_strcat( params, sizeof( params ), " " ); } Q_strcat( params, sizeof( params ), token ); } if ( strlen( params ) ) { // copy the params into the event curEvent->params = G_Alloc( strlen( params ) + 1 ); Q_strncpyz( curEvent->params, params, strlen( params ) + 1 ); } // parse the actions for this event while ( ( token = COM_Parse( &pScript ) ) && ( token[0] != '}' ) ) { if ( !token[0] ) { G_Error( "AICast_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); } action = AICast_ActionForString( cs, token ); if ( !action ) { G_Error( "AICast_ScriptParse(), Error (line %d): unknown action: %s.\n", COM_GetCurrentParseLine(), token ); } curEvent->stack.items[curEvent->stack.numItems].action = action; memset( params, 0, sizeof( params ) ); token = COM_ParseExt( &pScript, qfalse ); for ( i = 0; token[0]; i++ ) { if ( strlen( params ) ) { // add a space between each param Q_strcat( params, sizeof( params ), " " ); } if ( i == 0 ) { // Special case: playsound's need to be cached on startup to prevent in-game pauses if ( !Q_stricmp( action->actionString, "playsound" ) ) { G_SoundIndex( token ); } //----(SA) added a bit more if ( buildScript && ( !Q_stricmp( action->actionString, "mu_start" ) || !Q_stricmp( action->actionString, "mu_play" ) || !Q_stricmp( action->actionString, "mu_queue" ) || !Q_stricmp( action->actionString, "startcam" ) || !Q_stricmp( action->actionString, "startcamblack" ) ) ) { if ( strlen( token ) ) { // we know there's a [0], but don't know if it's '0' trap_SendServerCommand( cs->entityNum, va( "addToBuild %s\n", token ) ); } } if ( !Q_stricmp( action->actionString, "giveweapon" ) ) { // register weapon for client pre-loading gitem_t *weap = BG_FindItem2( token ); // (SA) FIXME: rats, need to fix this for weapon names with spaces: 'mauser rifle' // if(weap) RegisterItem( weap ); // don't be nice, just do it. if it can't find it, you'll bomb out to the error menu } //----(SA) end } if ( strrchr( token,' ' ) ) { // need to wrap this param in quotes since it has more than one word Q_strcat( params, sizeof( params ), "\"" ); } Q_strcat( params, sizeof( params ), token ); if ( strrchr( token,' ' ) ) { // need to wrap this param in quotes since it has more than one word Q_strcat( params, sizeof( params ), "\"" ); } token = COM_ParseExt( &pScript, qfalse ); } if ( strlen( params ) ) { // copy the params into the event curEvent->stack.items[curEvent->stack.numItems].params = G_Alloc( strlen( params ) + 1 ); Q_strncpyz( curEvent->stack.items[curEvent->stack.numItems].params, params, strlen( params ) + 1 ); } curEvent->stack.numItems++; if ( curEvent->stack.numItems >= AICAST_MAX_SCRIPT_STACK_ITEMS ) { G_Error( "AICast_ScriptParse(): script exceeded MAX_SCRIPT_ITEMS (%d), line %d\n", AICAST_MAX_SCRIPT_STACK_ITEMS, COM_GetCurrentParseLine() ); } } numEventItems++; } else // skip this character completely { // TTimo: gcc: suggest () around assignment used as truth value while ( ( token = COM_Parse( &pScript ) ) ) { if ( !token[0] ) { G_Error( "AICast_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); } else if ( token[0] == '{' ) { bracketLevel++; } else if ( token[0] == '}' ) { if ( !--bracketLevel ) { break; } } } } } // alloc and copy the events into the cast_state_t for this cast if ( numEventItems > 0 ) { cs->castScriptEvents = G_Alloc( sizeof( cast_script_event_t ) * numEventItems ); memcpy( cs->castScriptEvents, events, sizeof( cast_script_event_t ) * numEventItems ); cs->numCastScriptEvents = numEventItems; cs->castScriptStatus.castScriptEventIndex = -1; } } /* ================ AICast_ScriptChange ================ */ void AICast_ScriptChange( cast_state_t *cs, int newScriptNum ) { cast_script_status_t scriptStatusBackup; cs->scriptCallIndex++; // backup the current scripting scriptStatusBackup = cs->castScriptStatus; // set the new script to this cast, and reset script status cs->castScriptStatus.castScriptStackHead = 0; cs->castScriptStatus.castScriptStackChangeTime = level.time; cs->castScriptStatus.castScriptEventIndex = newScriptNum; cs->castScriptStatus.scriptId = scriptStatusBackup.scriptId + 1; cs->castScriptStatus.scriptGotoId = -1; cs->castScriptStatus.scriptGotoEnt = -1; cs->castScriptStatus.scriptFlags |= SFL_FIRST_CALL; // try and run the script, if it doesn't finish, then abort the current script (discard backup) if ( AICast_ScriptRun( cs, qtrue ) ) { // completed successfully cs->castScriptStatus.castScriptStackHead = scriptStatusBackup.castScriptStackHead; cs->castScriptStatus.castScriptStackChangeTime = scriptStatusBackup.castScriptStackChangeTime; cs->castScriptStatus.castScriptEventIndex = scriptStatusBackup.castScriptEventIndex; cs->castScriptStatus.scriptId = scriptStatusBackup.scriptId; cs->castScriptStatus.scriptFlags = scriptStatusBackup.scriptFlags; } } /* ================ AICast_ScriptEvent An event has occured, for which a script may exist ================ */ void AICast_ScriptEvent( struct cast_state_s *cs, char *eventStr, char *params ) { int i, eventNum; eventNum = -1; // find out which event this is for ( i = 0; scriptEvents[i].eventStr; i++ ) { if ( !Q_strcasecmp( eventStr, scriptEvents[i].eventStr ) ) { // match found eventNum = i; break; } } if ( eventNum < 0 ) { if ( g_cheats.integer ) { // dev mode G_Printf( "devmode-> AICast_ScriptEvent(), unknown event: %s\n", eventStr ); } } // show debugging info if ( ( ( aicast_debug.integer == 1 ) || ( ( aicast_debug.integer == 2 ) && ( ( strlen( aicast_debugname.string ) < 1 ) || ( g_entities[cs->entityNum].aiName && !strcmp( aicast_debugname.string, g_entities[cs->entityNum].aiName ) ) ) ) ) ) { G_Printf( "(%s) AIScript event: %s %s ", g_entities[cs->entityNum].aiName, eventStr, params ); } cs->aiFlags &= ~AIFL_DENYACTION; // see if this cast has this event for ( i = 0; i < cs->numCastScriptEvents; i++ ) { if ( cs->castScriptEvents[i].eventNum == eventNum ) { if ( ( !cs->castScriptEvents[i].params ) || ( !scriptEvents[eventNum].eventMatch || scriptEvents[eventNum].eventMatch( &cs->castScriptEvents[i], params ) ) ) { // show debugging info if ( ( ( aicast_debug.integer == 1 ) || ( ( aicast_debug.integer == 2 ) && ( ( strlen( aicast_debugname.string ) < 1 ) || ( g_entities[cs->entityNum].aiName && !strcmp( aicast_debugname.string, g_entities[cs->entityNum].aiName ) ) ) ) ) ) { G_Printf( "found, calling script\n", g_entities[cs->entityNum].aiName, eventStr, params ); } AICast_ScriptChange( cs, i ); break; } } } // show debugging info if ( ( ( aicast_debug.integer == 1 ) || ( ( aicast_debug.integer == 2 ) && ( ( strlen( aicast_debugname.string ) < 1 ) || ( g_entities[cs->entityNum].aiName && !strcmp( aicast_debugname.string, g_entities[cs->entityNum].aiName ) ) ) ) ) ) { if ( i == cs->numCastScriptEvents ) { G_Printf( "not found\n" ); } } } /* ================ AICast_ForceScriptEvent Definately run this event now, overriding any paised state ================ */ void AICast_ForceScriptEvent( struct cast_state_s *cs, char *eventStr, char *params ) { int oldPauseTime; oldPauseTime = cs->scriptPauseTime; cs->scriptPauseTime = 0; AICast_ScriptEvent( cs, eventStr, params ); cs->scriptPauseTime = oldPauseTime; } /* ============= AICast_ScriptRun returns qtrue if the script completed ============= */ qboolean AICast_ScriptRun( cast_state_t *cs, qboolean force ) { cast_script_stack_t *stack; if ( !aicast_scripts.integer ) { return qtrue; } if ( cs->castScriptStatus.castScriptEventIndex < 0 ) { return qtrue; } if ( !cs->castScriptEvents ) { cs->castScriptStatus.castScriptEventIndex = -1; return qtrue; } // only allow the PLAYER'S spawn function through if we're NOT still waiting on everything to finish loading in if ( !cs->entityNum && saveGamePending && Q_stricmp( "spawn", scriptEvents[cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].eventNum].eventStr ) ) { //char loading[4]; //trap_Cvar_VariableStringBuffer( "savegame_loading", loading, sizeof(loading) ); //if (strlen( loading ) > 0 && atoi(loading) != 0) // we're loading a savegame return qfalse; } if ( !force && ( cs->scriptPauseTime >= level.time ) ) { return qtrue; } stack = &cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack; if ( !stack->numItems ) { cs->castScriptStatus.castScriptEventIndex = -1; return qtrue; } while ( cs->castScriptStatus.castScriptStackHead < stack->numItems ) { // // show debugging info if ( ( cs->castScriptStatus.castScriptStackChangeTime == level.time ) && ( ( aicast_debug.integer == 1 ) || ( ( aicast_debug.integer == 2 ) && ( ( strlen( aicast_debugname.string ) < 1 ) || ( g_entities[cs->entityNum].aiName && !strcmp( aicast_debugname.string, g_entities[cs->entityNum].aiName ) ) ) ) ) ) { G_Printf( "(%s) AIScript command: %s %s\n", g_entities[cs->entityNum].aiName, stack->items[cs->castScriptStatus.castScriptStackHead].action->actionString, ( stack->items[cs->castScriptStatus.castScriptStackHead].params ? stack->items[cs->castScriptStatus.castScriptStackHead].params : "" ) ); } // if ( !stack->items[cs->castScriptStatus.castScriptStackHead].action->actionFunc( cs, stack->items[cs->castScriptStatus.castScriptStackHead].params ) ) { // check that we are still running the same script that we were when we call the action if ( cs->castScriptStatus.castScriptEventIndex >= 0 && stack == &cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack ) { cs->castScriptStatus.scriptFlags &= ~SFL_FIRST_CALL; } return qfalse; } // move to the next action in the script cs->castScriptStatus.castScriptStackHead++; // record the time that this new item became active cs->castScriptStatus.castScriptStackChangeTime = level.time; // reset misc stuff cs->castScriptStatus.scriptGotoId = -1; cs->castScriptStatus.scriptGotoEnt = -1; cs->castScriptStatus.scriptFlags |= SFL_FIRST_CALL; } cs->castScriptStatus.castScriptEventIndex = -1; return qtrue; }