The following source code changes are required in the game project to support AI scripting. Due to the large amount of changes made to g_npc.c, g_npcmove.c and g_npcthink.c those changes will not be documented here. Instead the updated files are avalible on the Downloads page as part of the AI Scripting Source Files package. All changes to these files are commented by // ai script so they can be located and examined.
Also in this package are two files that must be added to the game project. These files are g_npcaction.c and g_npcscript.c, which are placed in the code/game folder. In Visual C++ you use the Project->Add to Project->Add Files dialog to achieve this. After this is complete you can make the changes to the rest of the game project.
In bg_misc.c about line 29 add :
gnpc_t bg_npclist[] =
{
{
"npc_ank", // class
NPC_ANK,
// ai script
GROUP_MONSTER,
// end ai script
100, // health
1.0,
20, // walkingSpeed
60, // runningSpeed
180, // fov
50, // jumpHeight
20, // walkingRotSpd
75, // runningRotSpd
0, // melee distance
25, // melee damage
20, // far damage
{0},
-24,-24,-24,
24,24,24,
0,0,30,
// ai script
{0,0,15},
// end ai script
"", // precache
"" // sounds
},
{
"npc_bat", // class
NPC_BAT,
// ai script
GROUP_MONSTER,
// end ai script
100, // health
1.0,
40,
60, // runningSpeed
180, // fov
50, // jumpHeight
20, // walkingRotSpd
75, // runningRotSpd
0, // melee distance
0, // melee damage
20, // far damage
{0},
{-24,-24,-24},
{24,24,40},
{0,0,30},
// ai script
{0,0,33},
// end ai script
"", // precache
"" // sounds
},
{
"npc_hulk", // class
NPC_HULK,
// ai script
GROUP_MONSTER,
// end ai script
100, // health
1.0,
30,
60, // runningSpeed
180, // fov
50, // jumpHeight
20, // walkingRotSpd
75, // runningRotSpd
0, // melee distance
50, // melee damage
35, // far damage
{0},
{-24,-24,-24},
{24,24,24},
{0,0,30},
// ai script
{0,0,0},
// end ai script
"", // precache
"" // sounds
},
{
"npc_metlar", // class
NPC_METLAR,
// ai script
GROUP_MONSTER,
// end ai script
100, // health
1.0,
20,
60, // runningSpeed
180, // fov
50, // jumpHeight
20, // walkingRotSpd
75, // runningRotSpd
0, // melee distance
25, // melee damage
20, // far damage
{0},
{-24,-24,-24},
{24,24,40},
{0,0,30},
// ai script
{0,0,0},
// end ai script
"", // precache
"" // sounds
},
{
"npc_pilot", // class
NPC_PILOT,
// ai script
GROUP_NEUTRAL,
// end ai script
100, // health
1.0,
20,
60, // runningSpeed
180, // fov
50, // jumpHeight
20, // walkingRotSpd
75, // runningRotSpd
0, // melee distance
0, // melee damage
20, // far damage
{0},
{-24,-24,-24},
{24,24,40},
{0,0,30},
// ai script
{0,0,15},
// end ai script
"", // precache
"" // sounds
},
{
"npc_sealord", // class
NPC_SEALORD,
// ai script
GROUP_MONSTER,
// end ai script
1000, // health
1.0,
0,
0, // runningSpeed
180, // fov
50, // jumpHeight
0, // walkingRotSpd
0, // runningRotSpd
0, // melee distance
25, // melee damage
20, // far damage
{0},
{-24,-24,-24},
{24,24,40},
{0,0,30},
// ai script
{0,-120,30},
// end ai script
"", // precache
"" // sounds
},
{
"npc_soldier1", // class
NPC_SOLDIER1,
// ai script
GROUP_NEUTRAL,
// end ai script
100, // health
1.0,
20,
60, // runningSpeed
180, // fov
50, // jumpHeight
20, // walkingRotSpd
75, // runningRotSpd
0, // melee distance
0, // melee damage
20, // far damage
{0},
{-24,-24,-24},
{24,24,40},
{0,0,30},
// ai script
{0,0,15},
// end ai script
"", // precache
"" // sounds
},
{
"npc_soldier2", // class
NPC_SOLDIER2,
// ai script
GROUP_NEUTRAL,
// end ai script
100, // health
1.0,
20,
60, // runningSpeed
180, // fov
50, // jumpHeight
20, // walkingRotSpd
75, // runningRotSpd
0, // melee distance
0, // melee damage
20, // far damage
{0},
{-24,-24,-24},
{24,24,40},
{0,0,30},
// ai script
{0,0,15},
// end ai script
"", // precache
"" // sounds
},
// npc addon
{
"npc_man", // class
NPC_MAN,
// ai script
GROUP_NEUTRAL,
// end ai script
100, // health
1.0,
20,
60, // runningSpeed
180, // fov
20, // jumpHeight
20, // walkingRotSpd
75, // runningRotSpd
110, // melee distance
20, // melee damage
0, // far damage
{0},
{-24,-24,-24},
{24,24,40},
{0,0,30},
// ai script
{0,0,0},
// end ai script
"", // precache
"" // sounds
},
// end npc addon
{
"npc_awolf", // class
NPC_AWOLF,
// ai script
GROUP_MONSTER,
// end ai script
100, // health
1.0,
20,
60, // runningSpeed
180, // fov
20, // jumpHeight
20, // walkingRotSpd
75, // runningRotSpd
0, // melee distance
20, // melee damage
0, // far damage
{0},
{-24,-24,-24},
{24,24,40},
{0,0,30},
// ai script
{0,0,0},
// end ai script
"", // precache
"" // sounds
},
{
"npc_ogre", // class
NPC_OGRE,
// ai script
GROUP_MONSTER,
// end ai script
100, // health
1.0,
20,
60, // runningSpeed
180, // fov
20, // jumpHeight
20, // walkingRotSpd
75, // runningRotSpd
0, // melee distance
0, // melee damage
30, // far damage
{0},
{-24,-24,-24},
{24,24,40},
{0,0,30},
// ai script
{0,-13,15},
// end ai script
"", // precache
"" // sounds
},
{NULL},
};
In bg_misc.c after function BG_FindItem about line 1377 add :
// ai script
#ifdef SINGLEPLAYER
gitem_t *BG_FindItem2( const char *name ) {
gitem_t *it;
char *name2;
name2 = (char*)name;
for ( it = bg_itemlist + 1 ; it->classname ; it++ ) {
if ( !Q_stricmp( it->pickup_name, name ) )
return it;
if (!Q_stricmp( it->classname, name2)) {
return it;
}
}
Com_Printf("BG_FindItem2(): unable to locate item '%s'\n", name);
return NULL;
}
#endif
// end ai script
In bg_public.h about line 83 add :
#define CS_ITEM 27 // string of 0's and 1's that tell which items are present
#ifdef SINGLEPLAYER // npc
#define CS_NPCS 28
// ai script
#define CS_NPCSKIN 29
// end ai script
#endif
In bg_public.h about line 465 add :
#ifdef SINGLEPLAYER // entity
EV_PLAYERSTOP,
EV_PLAYERLOCK, // ai script
#endif
EV_DEBUG_LINE,
EV_STOPLOOPINGSOUND,
In bg_public.h about line 570 add :
ANPC_LAND,
ANPC_PAIN,
// ai script
ANPC_SPECIAL0,
ANPC_SPECIAL1,
ANPC_SPECIAL2,
ANPC_SPECIAL3,
ANPC_SPECIAL4,
ANPC_SPECIAL5,
ANPC_SPECIAL6,
ANPC_SPECIAL7,
ANPC_SPECIAL8,
ANPC_SPECIAL9,
// end ai script
MAX_ANIMATIONS_NPC
} animNumberNPC_t;
In bg_public.h about line 681 add :
NPC_OGRE,
NPC_NUMNPCS
} npcType_t;
// ai script
typedef enum
{
GROUP_MONSTER,
GROUP_NEUTRAL
} NPCGroup_t;
// end ai script
#define HULK_QUAKE_LEN 1700 // msec
typedef struct gnpc_s {
char *classname;
npcType_t npcType;
// ai script
NPCGroup_t group;
// end ai script
int health;
float painFreq;
int walkingSpeed;
int runningSpeed;
int fov;
int jumpHeight;
int walkingRotSpd;
int runningRotSpd;
int melee_dist;
int melee_damage;
int far_damage;
int animTimes[MAX_ANIMATIONS_NPC];
vec3_t mins,maxs,eye;
// ai script
vec3_t weaponoffset;
// end ai script
char *precaches;
char *sounds;
} gnpc_t;
In bg_public.h about line 770 add :
gitem_t *BG_FindItem( const char *pickupName );
// ai script
#ifdef SINGLEPLAYER
gitem_t *BG_FindItem2( const char *pickupName );
#endif
// end ai script
gitem_t *BG_FindItemForWeapon( weapon_t weapon );
In g_active.c in function ClientThink_real about line 773 add :
if (ent->stop_event)
{
if (ent->stop_event>level.time)
{
ucmd->forwardmove=0;
ucmd->rightmove=0;
ucmd->upmove=0;
ucmd->buttons=0;
}
else
ent->stop_event=0;
}
// ai script
if (ent->lockplayer)
{
ucmd->forwardmove=0;
ucmd->rightmove=0;
ucmd->upmove=0;
ucmd->buttons=0;
}
// end ai script
#endif
In g_client.c in function ClientSpawn about line 1075 add :
index = ent - g_entities;
client = ent->client;
#ifdef SINGLEPLAYER // ai script
ent->ainame = "player"; // needed for script AI
ScriptParse( ent );
#endif
// find a spawn point
In g_client.c in function ClientSpawn about line 1301 add :
// clear entity state values
BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue );
#ifdef SINGLEPLAYER // ai script
ent->save_load = save_loading;
ScriptEvent( ent, "spawn", "" );
trap_Cvar_Set( "g_playerstart", "1");
ent->playerstarttime = level.time + 500;
ent->blacktime = level.time + 500;
trap_SendServerCommand( -1, "setBlack" );
#endif
}
In g_combat.c in function player_die about line 570 add :
contents = trap_PointContents( self->r.currentOrigin, -1 );
if ( !( contents & CONTENTS_NODROP )) {
// ai script
#ifndef SINGLEPLAYER
TossClientItems( self );
#endif
// end ai script
}
else {
In g_combat.c in function G_Damage about line 837 add :
if (!targ->takedamage) {
return;
}
// ai script
#ifdef SINGLEPLAYER
if (targ->s.eType==ET_NPC && attacker && attacker->s.eType==ET_NPC)
{
if (targ->npcattr.noaidamage)
return;
}
#endif
// end ai script
// the intermission has allready been qualified for, so don't
// allow any extra scoring
if ( level.intermissionQueued ) {
In g_combat.c in function G_Damage about line 917 add :
if ( dflags & DAMAGE_NO_KNOCKBACK ) {
knockback = 0;
}
// ai script
#ifdef SINGLEPLAYER
if ( knockback && targ->s.eType==ET_NPC )
{
vec3_t kvel;
float mass;
mass = 200;
VectorScale (dir, g_knockback.value * (float)knockback / mass, kvel);
VectorAdd (targ->ns.ps.velocity, kvel, targ->ns.ps.velocity);
// set the timer so that the other client can't cancel
// out the movement immediately
if ( !targ->ns.ps.pm_time ) {
int t;
t = knockback * 2;
if ( t < 50 ) {
t = 50;
}
if ( t > 200 ) {
t = 200;
}
targ->ns.ps.pm_time = t;
targ->ns.ps.pm_flags |= PMF_TIME_KNOCKBACK;
}
}
#endif
// end ai script
// figure momentum add, even if the damage won't be taken
if ( knockback && targ->client ) {
In g_local.h about line 77 change the typedef for npcCommand_t to :
typedef enum {
// ai script
NPC_COM_NONE,
NPC_COM_RELAXED,
NPC_COM_ALERT,
NPC_COM_ATTACK,
NPC_COM_GOTO,
NPC_AI_GOTO
// end ai script
} npcCommand_t;
In g_local.h about line 170 after the definition of npmove_t add :
// ai script
#define AI_MAX_STACK_ITEMS 64
#define MAX_SCRIPT_EVENTS 64
#define MAX_ACCUM_BUFFERS 8
#define REACHGOAL_DIST 32
#define REACHNPC_DIST 64
#define AI_ENEMY 0x001
#define AI_DENYACTION 0x002
#define AI_SIGHT_SCRIPT_CALLED 0x004
#define AI_NOREMOVE 0x008
#define AI_JUMPATTACK 0x010
#define AI_JUMPMOVE 0x020
#define AI_INSPECT 0x040
#define AI_INSPECTED 0x080
#define S_FIRST_CALL 0x0001
#define S_FRIENDLYSIGHTCORPSE_TRIGGERED 0x0002
#define S_WAITING_RESTORE 0x0004
typedef struct
{
char *action;
qboolean (*actionFunc)(struct gentity_s *ent, char *params);
} ai_stackaction_t;
typedef struct
{
ai_stackaction_t *action;
char *params;
} ai_stackitem_t;
typedef struct
{
ai_stackitem_t items[AI_MAX_STACK_ITEMS];
int numItems;
} ai_stack_t;
typedef struct
{
int eventNum;
char *params;
ai_stack_t stack;
} ai_event_t;
//
typedef struct
{
char *eventStr;
qboolean (*eventMatch)( ai_event_t *event, char *eventParm );
} ai_event_define_t;
typedef enum {
MS_DEFAULT,
MS_WALK,
MS_FLY,
MS_RUN
} movestate_t;
typedef struct
{
int EventIndex;
int StackHead;
int ChangeTime;
int scriptId;
int scriptFlags;
vec3_t WaitPos;
int WaitMovetime;
int NoAttacktime;
int AttackEnt;
int GotoId;
int GotoEnt;
movestate_t movestate;
movestate_t movestatebackup;
} script_status_t;
typedef struct
{
int health;
float painFreq;
int walkingSpeed;
int runningSpeed;
int fov;
int jumpHeight;
int walkingRotSpd;
int runningRotSpd;
int melee_dist;
int melee_damage;
int far_damage;
float aim_accuracy;
int farweapon;
qboolean noaidamage;
int idr;
int alertness;
int ideal_dist;
int jumpspeed;
float hearingscale;
} npcAttr_t;
typedef struct
{
int flags;
int lastcheck_timestamp;
int visible_timestamp;
vec3_t visible_pos;
vec3_t visible_vel;
int notvisible_timestamp;
int lastcheck_health;
} visibility_t;
// end ai script
In g_local.h about line 392 in definition of gentity_s add :
gnpc_t *npc;
npcData_t ns;
int slow_event;
// ai script
char *ainame;
npcAttr_t npcattr;
int aiFlags;
qboolean path_on;
qboolean lockplayer;
int dontMoveTime;
int playerstarttime;
int blacktime;
int deathtime;
int movietime;
int firstmovie;
int NoSightTime;
int attackSNDtime;
int numScriptEvents;
ai_event_t *ScriptEvents;
script_status_t ScriptStatus;
script_status_t ScriptStatusCurrent;
script_status_t ScriptStatusBackup;
int scriptCallIndex;
int AccumBuffer[MAX_ACCUM_BUFFERS];
int save_load;
visibility_t vislist[MAX_GENTITIES];
qboolean SlowApproach;
int reloadFadeTime;
int bulletImpactIgnoreTime;
int bulletImpactTime;
vec3_t bulletImpactStart;
vec3_t bulletImpactEnd;
int bulletImpactEntity;
int attackerNum;
int audibleEventTime;
int audibleEventEnt;
vec3_t audibleEventOrg;
char *skin;
int inspectNum;
// end ai script
#endif
#ifdef SINGLEPLAYER // entity
int stop_event;
#endif
};
In g_local.h about line 671 in definition of level_locals_t add :
gentity_t *bodyQue[BODY_QUEUE_SIZE];
#ifdef MISSIONPACK
int portalSequence;
#endif
#ifdef SINGLEPLAYER // ai script
char *scriptAI;
qboolean isparsed;
#endif
} level_locals_t;
In g_local.h about line 918 add :
qboolean NPC_InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles);
int NPC_IsVisible(gentity_t *viewer,gentity_t *ent);
//
// g_npcscript.c
//
// ai script
void ScriptLoad( void );
void ScriptParse( gentity_t *ent );
void ScriptEvent( gentity_t *ent, char *eventStr, char *params );
qboolean ScriptRun( gentity_t *ent, qboolean force );
void AI_Attributes( gentity_t *ent, char **ppStr );
void ScriptThink(void);
gentity_t *AI_FindEntityForName( char *name );
void AI_UpdateVisibility( gentity_t *srcent, gentity_t *destent);
void AI_ProcessBullet( gentity_t *attacker, vec3_t start, vec3_t end );
qboolean AI_StateChange( gentity_t *ent, npcCommand_t newaistate );
void AI_RecordWeaponFire( gentity_t *ent, int weapon );
void AI_InspectAudibleEventStart(gentity_t *ent);
void AI_InspectBulletEventStart(gentity_t *ent);
void AI_InspectFriendlyStart(gentity_t *ent);
// end ai script
#endif
In g_local.h about line 1126 add :
void trap_Printf( const char *fmt );
void trap_Error( const char *fmt );
// ai script
#ifdef SINGLEPLAYER
void trap_Endgame( void );
#endif
// end ai script
int trap_Milliseconds( void );
In g_main.c about line 94 add :
vmCvar_t g_enableBreath;
vmCvar_t g_proxMineTimeout;
#endif
#ifdef SINGLEPLAYER // ai script
vmCvar_t g_playerStart;
#endif
In g_main.c about line 182 add :
{ &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse},
#ifdef SINGLEPLAYER // ai script
{ &g_playerStart, "g_playerStart", "0", CVAR_ROM, 0, qfalse },
#endif
{ &g_rankings, "g_rankings", "0", 0, 0, qfalse}
In g_main.c in function G_InitGame about line 488 add :
ClearRegisteredItems();
#ifdef SINGLEPLAYER // npc
ClearRegisteredNPCs();
#endif
#ifdef SINGLEPLAYER // ai script
ScriptLoad();
#endif
// parse the key/value pairs and spawn gentities
G_SpawnEntitiesFromString();
In g_main.c in function G_InitGame about line 514 add :
if( g_gametype.integer == GT_SINGLE_PLAYER || trap_Cvar_VariableIntegerValue( "com_buildScript" ) ) {
// ai script
#ifndef SINGLEPLAYER
G_ModelIndex( SP_PODIUM_MODEL );
#endif
// end ai script
G_SoundIndex( "sound/player/gurp1.wav" );
In g_main.c in function G_RunFrame about line 1740 add :
#ifdef SINGLEPLAYER
gclient_t *cl;
int save_loading;
// ai script
gentity_t *player;
int movie;
char *skinstr;
char filename[MAX_QPATH*2],npcname[MAX_QPATH];
// end ai script
#endif
int start, end;
In g_main.c in function G_RunFrame about line 1759 add :
level.time = levelTime;
msec = level.time - level.previousTime;
// ai script
#ifdef SINGLEPLAYER
player = &g_entities[0];
if(player && trap_Cvar_VariableIntegerValue( "g_playerstart" )
&& player->client->pers.connected == CON_CONNECTED
&& player->playerstarttime <= level.time) {
ScriptEvent( player, "playerstart", "" );
// now let it think
ScriptThink();
trap_Cvar_Set( "g_playerstart", "0");
for (i=0;i<level.num_entities;i++)
{
ent = &g_entities[i];
if (ent->s.eType==ET_NPC && ent->skin)
{
Com_sprintf(npcname,sizeof(npcname),"%s",ent->npc->classname+4);
Com_sprintf(filename,sizeof(filename),"models/npc/%s/%s",npcname, ent->skin);
skinstr = va("n\\%i\\t\\%s", i, filename);
trap_SetConfigstring(CS_NPCSKIN, skinstr);
}
}
}
if(player && player->client->pers.connected == CON_CONNECTED
&& player->blacktime <= level.time && player->blacktime != 0) {
trap_SendServerCommand( -1, va("fadeBlack %d", 1000) );
player->blacktime = 0;
}
movie = trap_Cvar_VariableIntegerValue( "cl_inCameraMode" );
if(movie==1)
{
player->movietime = level.time + 1500;
}
if(movie==2)
{
if(!player->firstmovie)
{
player->firstmovie = 1;
trap_SendServerCommand( -1, va("fadeWhite %d", 1500) );
}
if(player->movietime <= level.time)
{
player->firstmovie = 0;
trap_Cvar_Set ("cl_inCameraMode","0");
trap_SendServerCommand( -1, "stopCam" );
ScriptEvent( player, "trigger", "cameraInterrupt" );
}
}
#endif
// end ai script
// get any cvar changes
G_UpdateCvars();
In g_main.c in function G_RunFrame about line 1830 add :
G_LoadGame(load_game);
trap_Cvar_Set("Save_Loading","0");
// ai script
save_loading = 0;
trap_SendServerCommand( -1, va("fadeBlack %d", 1000) );
// end ai script
break;
In g_main.c in function G_RunFrame about line 1840 add :
else
{
trap_Cvar_Set("Save_Loading","0");
// ai script
save_loading = 0;
// end ai script
}
}
// ai script
if(save_loading == 0)
{
ent = &g_entities[0];
ent->ScriptStatusCurrent = ent->ScriptStatus;
ScriptRun( ent, qfalse );
}
// end ai script
#endif
In g_main.c in function G_RunFrame about line 1927 add :
if ( i < MAX_CLIENTS ) {
G_RunClient( ent );
continue;
}
// ai script
#ifdef SINGLEPLAYER
ent->ScriptStatusCurrent = ent->ScriptStatus;
ScriptRun( ent, qfalse );
#endif
// end ai script
G_RunThink( ent );
}
In g_missile.c in function G_MissileImpact about line 276 add :
other = &g_entities[trace->entityNum];
// ai script
#ifdef SINGLEPLAYER
AI_ProcessBullet( &g_entities[ent->r.ownerNum], g_entities[ent->r.ownerNum].s.pos.trBase, trace->endpos );
#endif
// end ai script
// check for bounce
if ( !other->takedamage &&
In g_public.h about line 112 add :
G_ERROR, // ( const char *string );
// abort the game
// ai script
#ifdef SINGLEPLAYER
G_ENDGAME, // ( void );
// exit to main menu and start "endgame" menu
#endif
// end ai script
G_MILLISECONDS, // ( void );
In g_savestate.c about line 271 add :
{FOFS(item), F_ITEM},
#ifdef SINGLEPLAYER // npc
{FOFS(ns.enemy), F_ENTITY},
// ai script
{FOFS(ainame), F_STRING},
{FOFS(skin), F_STRING},
// end ai script
#endif
In g_savestate.c about line 303 add :
static ignoreField_t gentityIgnoreFields[] = {
#ifdef SINGLEPLAYER // npc
{FOFS(npc), sizeof(gnpc_t *)},
{FOFS(ScriptEvents), sizeof(ai_event_t *)}, // ai script
#endif
In g_spawn.c about line 117 add :
{"targetShaderName", FOFS(targetShaderName), F_LSTRING},
{"targetShaderNewName", FOFS(targetShaderNewName), F_LSTRING},
// ai script
#ifdef SINGLEPLAYER
{"ainame", FOFS(ainame), F_LSTRING},
{"skin", FOFS(skin), F_LSTRING},
#endif
// end ai script
In g_spawn.c about line 216 add :
void SP_npc( gentity_t *ent,gnpc_t *npc);
void SP_npcpath( gentity_t *ent);
// ai script
void SP_script_play( gentity_t *self );
void SP_ai_trigger( gentity_t *ent );
// end ai script
#endif
In g_spawn.c about line 309 add :
#ifdef SINGLEPLAYER // npc
{"npcpath", SP_npcpath},
// ai script
{"script_play", SP_script_play},
{"script_trigger", SP_ai_trigger},
// end ai script
#endif
In g_spawn.c in function G_CallSpawn about line 341 add :
for ( npc=bg_npclist ; npc->classname ; npc++ ) {
if ( !strcmp(npc->classname, ent->classname) ) {
SP_npc( ent, npc );
// ai script
ScriptParse( ent );
// end ai script
return qtrue;
}
In g_spawn.c in function G_CallSpawn about line 361 add :
for ( s=spawns ; s->name ; s++ ) {
if ( !strcmp(s->name, ent->classname) ) {
// found it
s->spawn(ent);
// ai script
#ifdef SINGLEPLAYER
ScriptParse( ent );
#endif
// end ai script
return qtrue;
}
In g_syscalls.c about line 48 add :
void trap_Error( const char *fmt ) {
syscall( G_ERROR, fmt );
}
// ai script
#ifdef SINGLEPLAYER
void trap_Endgame( void ) {
syscall( G_ENDGAME );
}
#endif
// end ai script
int trap_Milliseconds( void ) {
In g_weapon.c in function Bullet_Fire about line 183 add :
trap_Trace (&tr, muzzle, NULL, NULL, end, passent, MASK_SHOT);
if ( tr.surfaceFlags & SURF_NOIMPACT ) {
return;
}
// ai script
#ifdef SINGLEPLAYER
AI_ProcessBullet( ent, muzzle, tr.endpos );
#endif
// end ai script
traceEnt = &g_entities[ tr.entityNum ];
In g_weapon.c in function FireWeapon about line 906 add :
default:
// FIXME G_Error( "Bad ent->s.weapon" );
break;
}
// ai script
#ifdef SINGLEPLAYER
AI_RecordWeaponFire( ent, ent->s.weapon );
#endif
// end ai script
}
In g_weapon.c change the function NPC_FireWeapon about line 1170 to :
void NPC_FireWeapon(gentity_t *ent,vec3_t target_pos)
{
vec3_t dir,ang;
vec3_t fire_org;
// ai script
vec3_t offset, xoffset;
vec3_t v[3];
float aimSpreadScale;
int weapon = 0;
VectorCopy(ent->r.currentOrigin,fire_org);
AnglesToAxis( ent->ns.ps.viewangles, v );
offset[0] = ent->npc->weaponoffset[0];
offset[1] = ent->npc->weaponoffset[1];
offset[2] = ent->npc->weaponoffset[2];
xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0];
xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1];
xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2];
VectorAdd( ent->r.currentOrigin, xoffset, fire_org );
VectorSubtract (target_pos,fire_org, dir);
vectoangles(dir,ang);
if (ent->npc->npcType!=NPC_SEALORD)
{
ang[1]=ent->ns.ps.viewangles[1];
}
else if (ent->ns.fireTime > 0)
{
ang[0]+=rand()%15-7;
ang[1]+=rand()%15-7;
ang[2]+=rand()%15-7;
}
s_quadFactor=1;
AngleVectors (ang, forward, right, up);
VectorMA( fire_org, 1, forward, muzzle);
SnapVector( muzzle );
if (ent->npcattr.aim_accuracy <= 0)
ent->npcattr.aim_accuracy = 0.0001f;
aimSpreadScale = (1.0f - ent->npcattr.aim_accuracy) * 2.0f;
if(aimSpreadScale > 1)
aimSpreadScale = 1.0f;
if(!ent->npcattr.farweapon)
{
if (ent->npc->npcType==NPC_SOLDIER1 || ent->npc->npcType==NPC_SOLDIER2 || ent->npc->npcType==NPC_PILOT)
{
Bullet_Fire(ent,50*aimSpreadScale,20*(int)(1.0f+0.25f*(float)npc_skill));
weapon = WP_GUN;
}
else if (ent->npc->npcType==NPC_ANK)
{
fire_ank_shot(ent,muzzle,forward,ent->ns.enemy);
weapon = WP_FIREBALL;
}
else if (ent->npc->npcType==NPC_BAT)
{
fire_bat_shot(ent,muzzle,forward);
weapon = WP_BAT;
}
else if (ent->npc->npcType==NPC_OGRE)
{
fire_ogre_shot(ent,muzzle,forward,ent->ns.enemy);
weapon = WP_PLASMAGUN;
}
else if (ent->npc->npcType==NPC_SEALORD)
{
if (ent->ns.meleeTime > 0)
{
fire_sealord_big_shot(ent,muzzle,forward);
weapon = WP_SEA1;
}
else
{
fire_sealord_small_shot(ent,muzzle,forward);
weapon = WP_SEA2;
}
}
} else
{
switch( ent->npcattr.farweapon ) {
case WP_FIREBALL:
fire_ank_shot(ent,muzzle,forward,ent->ns.enemy);
break;
case WP_BAT:
fire_bat_shot(ent,muzzle,forward);
break;
case WP_PLASMAGUN:
fire_ogre_shot(ent,muzzle,forward,ent->ns.enemy);
break;
case WP_SEA1:
fire_sealord_big_shot(ent,muzzle,forward);
break;
case WP_SEA2:
fire_sealord_small_shot(ent,muzzle,forward);
break;
case WP_GUN:
Bullet_Fire(ent,50*aimSpreadScale,20*(int)(1.0f+0.25f*(float)npc_skill));
break;
default:
break;
}
weapon = ent->npcattr.farweapon;
}
AI_RecordWeaponFire( ent, weapon );
// end ai script
}
In q_math.c after the function PerpendicularVector at the end of the file add :
// ai script
/*
================
ProjectPointOntoVector
================
*/
void ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd,
vec3_t vProj )
{
vec3_t pVec, vec;
VectorSubtract( point, vStart, pVec );
VectorSubtract( vEnd, vStart, vec );
VectorNormalize( vec );
// project onto the directional vector for this segment
VectorMA( vStart, DotProduct( pVec, vec ), vec, vProj );
}
// end ai script
In q_shared.h about line 773 add :
void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up);
void PerpendicularVector( vec3_t dst, const vec3_t src );
// ai script
void ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd,
vec3_t vProj );
// end ai script