12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244 |
- ////////////////////////////////////////////////////////////////////////////////////////
- // RAVEN SOFTWARE - STAR WARS: JK II
- // (c) 2002 Activision
- //
- // Boba Fett
- // ---------
- // Ah yes, this file is pretty messy. I've tried to move everything in here, but in fact
- // a lot of his AI occurs in the seeker and jedi AI files. Some of these functions
- //
- //
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- #include "g_headers.h"
- #include "b_local.h"
- ////////////////////////////////////////////////////////////////////////////////////////
- // Forward References Of Functions
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_Precache( void );
- void Boba_DustFallNear(const vec3_t origin, int dustcount);
- void Boba_ChangeWeapon(int wp);
- qboolean Boba_StopKnockdown(gentity_t *self, gentity_t *pusher, const vec3_t pushDir, qboolean forceKnockdown = qfalse);
- // Flight Related Functions (also used by Rocket Trooper)
- //--------------------------------------------------------
- qboolean Boba_Flying( gentity_t *self );
- void Boba_FlyStart( gentity_t *self );
- void Boba_FlyStop( gentity_t *self );
- // Called From NPC_Pain()
- //-----------------------------
- void Boba_Pain( gentity_t *self, gentity_t *inflictor, int damage, int mod);
- // Local: Flame Thrower Weapon
- //-----------------------------
- void Boba_FireFlameThrower( gentity_t *self );
- void Boba_StopFlameThrower( gentity_t *self );
- void Boba_StartFlameThrower( gentity_t *self );
- void Boba_DoFlameThrower( gentity_t *self );
- // Local: Other Tactics
- //----------------------
- void Boba_DoAmbushWait( gentity_t *self);
- void Boba_DoSniper( gentity_t *self);
- // Local: Respawning
- //-------------------
- bool Boba_Respawn();
- // Called From Within AI_Jedi && AI_Seeker
- //-----------------------------------------
- void Boba_Fire();
- void Boba_FireDecide();
- // Local: Called From Tactics()
- //----------------------------
- void Boba_TacticsSelect();
- bool Boba_CanSeeEnemy( gentity_t *self );
- // Called From NPC_RunBehavior()
- //-------------------------------
- void Boba_Update(); // Always Called First, Before Any Other Thinking
- bool Boba_Tactics(); // If returns true, Jedi and Seeker AI not used
- bool Boba_Flee(); // If returns true, Jedi and Seeker AI not used
- ////////////////////////////////////////////////////////////////////////////////////////
- // External Functions
- ////////////////////////////////////////////////////////////////////////////////////////
- extern void G_SoundAtSpot( vec3_t org, int soundIndex, qboolean broadcast );
- extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum );
- extern void ChangeWeapon( gentity_t *ent, int newWeapon );
- extern void WP_ResistForcePush( gentity_t *self, gentity_t *pusher, qboolean noPenalty );
- extern void ForceJump( gentity_t *self, usercmd_t *ucmd );
- extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
- ////////////////////////////////////////////////////////////////////////////////////////
- // External Data
- ////////////////////////////////////////////////////////////////////////////////////////
- extern cvar_t* g_bobaDebug;
- ////////////////////////////////////////////////////////////////////////////////////////
- // Boba Debug Output
- ////////////////////////////////////////////////////////////////////////////////////////
- #ifndef FINAL_BUILD
- #if !defined(CTYPE_H_INC)
- #include <ctype.h>
- #define CTYPE_H_INC
- #endif
- #if !defined(STDARG_H_INC)
- #include <stdarg.h>
- #define STDARG_H_INC
- #endif
- #if !defined(STDIO_H_INC)
- #include <stdio.h>
- #define STDIO_H_INC
- #endif
- void Boba_Printf(const char * format, ...)
- {
- if (g_bobaDebug->integer==0)
- {
- return;
- }
- static char string[2][1024]; // in case this is called by nested functions
- static int index = 0;
- static char nFormat[300];
- char* buf;
- // Tack On The Standard Format Around The Given Format
- //-----------------------------------------------------
- sprintf(nFormat, "[BOBA %8d] %s\n", level.time, format);
- // Resolve Remaining Elipsis Parameters Into Newly Formated String
- //-----------------------------------------------------------------
- buf = string[index & 1];
- index++;
- va_list argptr;
- va_start (argptr, format);
- vsprintf (buf, nFormat, argptr);
- va_end (argptr);
- // Print It To Debug Output Console
- //----------------------------------
- gi.Printf(buf);
- }
- #else
- void Boba_Printf(const char * format, ...)
- {
- }
- #endif
- ////////////////////////////////////////////////////////////////////////////////////////
- // Defines
- ////////////////////////////////////////////////////////////////////////////////////////
- #define BOBA_FLAMEDURATION 3000
- #define BOBA_FLAMETHROWRANGE 128
- #define BOBA_FLAMETHROWSIZE 40
- #define BOBA_FLAMETHROWDAMAGEMIN 1//10
- #define BOBA_FLAMETHROWDAMAGEMAX 5//40
- #define BOBA_ROCKETRANGEMIN 300
- #define BOBA_ROCKETRANGEMAX 2000
- ////////////////////////////////////////////////////////////////////////////////////////
- // Global Data
- ////////////////////////////////////////////////////////////////////////////////////////
- bool BobaHadDeathScript = false;
- bool BobaActive = false;
- vec3_t BobaFootStepLoc;
- int BobaFootStepCount = 0;
- vec3_t AverageEnemyDirection;
- int AverageEnemyDirectionSamples;
- ////////////////////////////////////////////////////////////////////////////////////////
- // Enums
- ////////////////////////////////////////////////////////////////////////////////////////
- enum EBobaTacticsState
- {
- BTS_NONE,
- // Attack
- //--------
- BTS_RIFLE, // Uses Jedi / Seeker Movement
- BTS_MISSILE, // Uses Jedi / Seeker Movement
- BTS_SNIPER, // Uses Special Movement Internal To This File
- BTS_FLAMETHROW, // Locked In Place
- // Waiting
- //---------
- BTS_AMBUSHWAIT, // Goto CP & Wait
- BTS_MAX
- };
- ////////////////////////////////////////////////////////////////////////////////////////
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_Precache( void )
- {
- G_SoundIndex( "sound/chars/boba/bf_blast-off.wav" );
- G_SoundIndex( "sound/chars/boba/bf_jetpack_lp.wav" );
- G_SoundIndex( "sound/chars/boba/bf_land.wav" );
- G_SoundIndex( "sound/weapons/boba/bf_flame.mp3" );
- G_SoundIndex( "sound/player/footsteps/boot1" );
- G_SoundIndex( "sound/player/footsteps/boot2" );
- G_SoundIndex( "sound/player/footsteps/boot3" );
- G_SoundIndex( "sound/player/footsteps/boot4" );
- G_EffectIndex( "boba/jetSP" );
- G_EffectIndex( "boba/fthrw" );
- G_EffectIndex( "volumetric/black_smoke" );
- G_EffectIndex( "chunks/dustFall" );
- AverageEnemyDirectionSamples = 0;
- VectorClear(AverageEnemyDirection);
- BobaHadDeathScript = false;
- BobaActive = true;
- BobaFootStepCount = 0;
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_DustFallNear(const vec3_t origin, int dustcount)
- {
- if (!BobaActive)
- {
- return;
- }
- trace_t testTrace;
- vec3_t testDirection;
- vec3_t testStartPos;
- vec3_t testEndPos;
- VectorCopy(origin, testStartPos);
- for (int i=0; i<dustcount; i++)
- {
- testDirection[0] = (random() * 2.0f) - 1.0f;
- testDirection[1] = (random() * 2.0f) - 1.0f;
- testDirection[2] = 1.0f;
- VectorMA(origin, 1000.0f, testDirection, testEndPos);
- gi.trace (&testTrace, origin, NULL, NULL, testEndPos, (player && player->inuse)?(0):(ENTITYNUM_NONE), MASK_SHOT );
- if (!testTrace.startsolid &&
- !testTrace.allsolid &&
- testTrace.fraction>0.1f &&
- testTrace.fraction<0.9f)
- {
- G_PlayEffect( "chunks/dustFall", testTrace.endpos, testTrace.plane.normal );
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // This is just a super silly wrapper around NPC_Change Weapon
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_ChangeWeapon( int wp )
- {
- if ( NPC->s.weapon == wp )
- {
- return;
- }
- NPC_ChangeWeapon( wp );
- G_AddEvent( NPC, EV_GENERAL_SOUND, G_SoundIndex( "sound/weapons/change.wav" ));
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // Choose an "anti-knockdown" response
- ////////////////////////////////////////////////////////////////////////////////////////
- qboolean Boba_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir, qboolean forceKnockdown )
- {
- if ( self->client->NPC_class != CLASS_BOBAFETT )
- {
- return qfalse;
- }
- if ( self->client->moveType == MT_FLYSWIM )
- {//can't knock me down when I'm flying
- return qtrue;
- }
- vec3_t pDir, fwd, right, ang = {0, self->currentAngles[YAW], 0};
- float fDot, rDot;
- int strafeTime = Q_irand( 1000, 2000 );
- AngleVectors( ang, fwd, right, NULL );
- VectorNormalize2( pushDir, pDir );
- fDot = DotProduct( pDir, fwd );
- rDot = DotProduct( pDir, right );
- if ( Q_irand( 0, 2 ) )
- {//flip or roll with it
- usercmd_t tempCmd;
- if ( fDot >= 0.4f )
- {
- tempCmd.forwardmove = 127;
- TIMER_Set( self, "moveforward", strafeTime );
- }
- else if ( fDot <= -0.4f )
- {
- tempCmd.forwardmove = -127;
- TIMER_Set( self, "moveback", strafeTime );
- }
- else if ( rDot > 0 )
- {
- tempCmd.rightmove = 127;
- TIMER_Set( self, "strafeRight", strafeTime );
- TIMER_Set( self, "strafeLeft", -1 );
- }
- else
- {
- tempCmd.rightmove = -127;
- TIMER_Set( self, "strafeLeft", strafeTime );
- TIMER_Set( self, "strafeRight", -1 );
- }
- G_AddEvent( self, EV_JUMP, 0 );
- if ( !Q_irand( 0, 1 ) )
- {//flip
- self->client->ps.forceJumpCharge = 280;//FIXME: calc this intelligently?
- ForceJump( self, &tempCmd );
- }
- else
- {//roll
- TIMER_Set( self, "duck", strafeTime );
- }
- self->painDebounceTime = 0;//so we do something
- }
- else if ( !Q_irand( 0, 1 ) && forceKnockdown )
- {//resist
- WP_ResistForcePush( self, pusher, qtrue );
- }
- else
- {//fall down
- return qfalse;
- }
- return qtrue;
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // Is this entity flying
- ////////////////////////////////////////////////////////////////////////////////////////
- qboolean Boba_Flying( gentity_t *self )
- {
- assert(self && self->NPC && self->client && self->client->NPC_class==CLASS_BOBAFETT);
- return ((qboolean)(self->client->moveType==MT_FLYSWIM));
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- bool Boba_CanSeeEnemy( gentity_t *self )
- {
- assert(self && self->NPC && self->client && self->client->NPC_class==CLASS_BOBAFETT);
- return ((level.time - self->NPC->enemyLastSeenTime)<1000);
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_Pain( gentity_t *self, gentity_t *inflictor, int damage, int mod)
- {
- if (mod==MOD_SABER && !(NPCInfo->aiFlags&NPCAI_FLAMETHROW))
- {
- TIMER_Set( self, "Boba_TacticsSelect", 0); // Hurt By The Saber, Time To Try Something New
- }
- if (self->NPC->aiFlags&NPCAI_FLAMETHROW)
- {
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- self->client->ps.torsoAnimTimer = level.time - TIMER_Get(self, "falmeTime");
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_FlyStart( gentity_t *self )
- {//switch to seeker AI for a while
- if ( TIMER_Done( self, "jetRecharge" )
- && !Boba_Flying( self ) )
- {
- self->client->ps.gravity = 0;
- self->svFlags |= SVF_CUSTOM_GRAVITY;
- self->client->moveType = MT_FLYSWIM;
- //start jet effect
- self->client->jetPackTime = level.time + Q_irand( 3000, 10000 );
- if ( self->genericBolt1 != -1 )
- {
- G_PlayEffect( G_EffectIndex( "boba/jetSP" ), self->playerModel, self->genericBolt1, self->s.number, self->currentOrigin, qtrue, qtrue );
- }
- if ( self->genericBolt2 != -1 )
- {
- G_PlayEffect( G_EffectIndex( "boba/jetSP" ), self->playerModel, self->genericBolt2, self->s.number, self->currentOrigin, qtrue, qtrue );
- }
- //take-off sound
- G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/boba/bf_blast-off.wav" );
- //jet loop sound
- self->s.loopSound = G_SoundIndex( "sound/chars/boba/bf_jetpack_lp.wav" );
- if ( self->NPC )
- {
- self->count = Q3_INFINITE; // SEEKER shot ammo count
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_FlyStop( gentity_t *self )
- {
- self->client->ps.gravity = g_gravity->value;
- self->svFlags &= ~SVF_CUSTOM_GRAVITY;
- self->client->moveType = MT_RUNJUMP;
- //Stop effect
- self->client->jetPackTime = 0;
- if ( self->genericBolt1 != -1 )
- {
- G_StopEffect( "boba/jetSP", self->playerModel, self->genericBolt1, self->s.number );
- }
- if ( self->genericBolt2 != -1 )
- {
- G_StopEffect( "boba/jetSP", self->playerModel, self->genericBolt2, self->s.number );
- }
- //stop jet loop sound
- G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/boba/bf_land.wav" );
- self->s.loopSound = 0;
- if ( self->NPC )
- {
- self->count = 0; // SEEKER shot ammo count
- TIMER_Set( self, "jetRecharge", Q_irand( 1000, 5000 ) );
- TIMER_Set( self, "jumpChaseDebounce", Q_irand( 500, 2000 ) );
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // This func actually does the damage inflicting traces
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_FireFlameThrower( gentity_t *self )
- {
- trace_t tr;
- vec3_t start, end, dir;
- CVec3 traceMins(self->mins);
- CVec3 traceMaxs(self->maxs);
- gentity_t* traceEnt = NULL;
- int damage = Q_irand( BOBA_FLAMETHROWDAMAGEMIN, BOBA_FLAMETHROWDAMAGEMAX );
- AngleVectors(self->currentAngles, dir, 0, 0);
- dir[2] = 0.0f;
- VectorCopy(self->currentOrigin, start);
- traceMins *= 0.5f;
- traceMaxs *= 0.5f;
- start[2] += 40.0f;
- VectorMA( start, 150.0f, dir, end );
- if (g_bobaDebug->integer)
- {
- CG_DrawEdge(start, end, EDGE_IMPACT_POSSIBLE);
- }
- gi.trace( &tr, start, self->mins, self->maxs, end, self->s.number, MASK_SHOT);
- traceEnt = &g_entities[tr.entityNum];
- if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage )
- {
- G_Damage( traceEnt, self, self, dir, tr.endpos, damage, DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC|DAMAGE_IGNORE_TEAM, MOD_LAVA, HL_NONE );
- if (traceEnt->health>0)
- {
- // G_Knockdown( traceEnt, self, dir, Q_irand(200, 330), qfalse);
- G_Throw(traceEnt, dir, 30);
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_StopFlameThrower( gentity_t *self )
- {
- if ((NPCInfo->aiFlags&NPCAI_FLAMETHROW))
- {
- self->NPC->aiFlags &= ~NPCAI_FLAMETHROW;
- self->client->ps.torsoAnimTimer = 0;
- TIMER_Set( self, "flameTime", 0);
- TIMER_Set( self, "nextAttackDelay", 0);
- TIMER_Set( self, "Boba_TacticsSelect", 0);
- // G_SoundOnEnt( self, CHAN_WEAPON, "sound/effects/flameoff.mp3" );
- G_StopEffect( G_EffectIndex("boba/fthrw"), self->playerModel, self->genericBolt3, self->s.number);
- Boba_Printf("FlameThrower OFF");
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_StartFlameThrower( gentity_t *self )
- {
- if (!(NPCInfo->aiFlags&NPCAI_FLAMETHROW))
- {
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- self->NPC->aiFlags |= NPCAI_FLAMETHROW;
- self->client->ps.torsoAnimTimer = BOBA_FLAMEDURATION;
- TIMER_Set( self, "flameTime", BOBA_FLAMEDURATION);
- TIMER_Set( self, "nextAttackDelay", BOBA_FLAMEDURATION);
- TIMER_Set( self, "nextFlameDelay", BOBA_FLAMEDURATION*2);
- TIMER_Set( self, "Boba_TacticsSelect", BOBA_FLAMEDURATION);
- G_SoundOnEnt( self, CHAN_WEAPON, "sound/weapons/boba/bf_flame.mp3" );
- G_PlayEffect( G_EffectIndex("boba/fthrw"), self->playerModel, self->genericBolt3, self->s.number, self->s.origin, 1 );
- Boba_Printf("FlameThrower ON");
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_DoFlameThrower( gentity_t *self )
- {
- if (!(NPCInfo->aiFlags&NPCAI_FLAMETHROW) && TIMER_Done(self, "nextAttackDelay"))
- {
- Boba_StartFlameThrower( self );
- }
- if ( (NPCInfo->aiFlags&NPCAI_FLAMETHROW))
- {
- Boba_FireFlameThrower( self );
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_DoAmbushWait( gentity_t *self)
- {
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_DoSniper( gentity_t *self)
- {
- if (TIMER_Done(NPC, "PickNewSniperPoint"))
- {
- TIMER_Set(NPC, "PickNewSniperPoint", Q_irand(15000, 25000));
- int SniperPoint = NPC_FindCombatPoint(NPC->currentOrigin, 0, NPC->currentOrigin, CP_SNIPE|CP_CLEAR|CP_HAS_ROUTE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1);
- if (SniperPoint!=-1)
- {
- NPC_SetCombatPoint(SniperPoint);
- NPC_SetMoveGoal( NPC, level.combatPoints[SniperPoint].origin, 20, qtrue, SniperPoint );
- }
- }
- if (Distance(NPC->currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin)<50.0f)
- {
- Boba_FireDecide();
- }
- bool IsOnAPath = !!NPC_MoveToGoal(qtrue);
- // Resolve Blocked Problems
- //--------------------------
- if (NPCInfo->aiFlags&NPCAI_BLOCKED &&
- NPC->client->moveType!=MT_FLYSWIM &&
- ((level.time - NPCInfo->blockedDebounceTime)>3000)
- )
- {
- Boba_Printf("BLOCKED: Attempting Jump");
- if (IsOnAPath)
- {
- if (!NPC_TryJump(NPCInfo->blockedTargetPosition))
- {
- Boba_Printf(" Failed");
- }
- }
- }
- NPC_FaceEnemy(qtrue);
- NPC_UpdateAngles( qtrue, qtrue );
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // Call This function to make Boba actually shoot his current weapon
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_Fire()
- {
- WeaponThink(qtrue);
- // If Actually Fired, Decide To Apply Alt Fire And Calc Next Attack Delay
- //------------------------------------------------------------------------
- if (ucmd.buttons&BUTTON_ATTACK)
- {
- switch (NPC->s.weapon)
- {
- case WP_ROCKET_LAUNCHER:
- TIMER_Set( NPC, "nextAttackDelay", Q_irand(1000, 2000));
- // Occasionally Shoot A Homing Missile
- //-------------------------------------
- if (!Q_irand(0,3))
- {
- ucmd.buttons &= ~BUTTON_ATTACK;
- ucmd.buttons |= BUTTON_ALT_ATTACK;
- NPC->client->fireDelay = Q_irand( 1000, 3000 );
- }
- break;
- case WP_DISRUPTOR:
- TIMER_Set(NPC, "nextAttackDelay", Q_irand(1000, 4000));
- // Occasionally Alt-Fire
- //-----------------------
- if (!Q_irand(0,3))
- {
- ucmd.buttons &= ~BUTTON_ATTACK;
- ucmd.buttons |= BUTTON_ALT_ATTACK;
- NPC->client->fireDelay = Q_irand( 1000, 3000 );
- }
- break;
- case WP_BLASTER:
- if (TIMER_Done(NPC, "nextBlasterAltFireDecide"))
- {
- if (Q_irand(0, (NPC->count*2)+3)>2)
- {
- TIMER_Set(NPC, "nextBlasterAltFireDecide", Q_irand(3000, 8000));
- if (!(NPCInfo->scriptFlags&SCF_ALT_FIRE))
- {
- Boba_Printf("ALT FIRE On");
- NPCInfo->scriptFlags |= SCF_ALT_FIRE;
- NPC_ChangeWeapon(WP_BLASTER); // Update Delay Timers
- }
- }
- else
- {
- TIMER_Set(NPC, "nextBlasterAltFireDecide", Q_irand(2000, 5000));
- if ( (NPCInfo->scriptFlags&SCF_ALT_FIRE))
- {
- Boba_Printf("ALT FIRE Off");
- NPCInfo->scriptFlags &=~SCF_ALT_FIRE;
- NPC_ChangeWeapon(WP_BLASTER); // Update Delay Timers
- }
- }
- }
- // Occasionally Alt Fire
- //-----------------------
- if (NPCInfo->scriptFlags&SCF_ALT_FIRE)
- {
- ucmd.buttons &= ~BUTTON_ATTACK;
- ucmd.buttons |= BUTTON_ALT_ATTACK;
- }
- break;
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // Call this function to see if Fett should fire his current weapon
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_FireDecide( void )
- {
- // Any Reason Not To Shoot?
- //--------------------------
- if (!NPC || // Only NPCs
- !NPC->client || // Only Clients
- NPC->client->NPC_class!=CLASS_BOBAFETT || // Only Boba
- !NPC->enemy || // Only If There Is An Enemy
- NPC->s.weapon==WP_NONE || // Only If Using A Valid Weapon
- !TIMER_Done(NPC, "nextAttackDelay") || // Only If Ready To Shoot Again
- !Boba_CanSeeEnemy(NPC) // Only If Enemy Recently Seen
- )
- {
- return;
- }
- // Now Check Weapon Specific Parameters To See If We Should Shoot Or Not
- //-----------------------------------------------------------------------
- switch (NPC->s.weapon)
- {
- case WP_ROCKET_LAUNCHER:
- if (Distance(NPC->currentOrigin, NPC->enemy->currentOrigin)>400.0f)
- {
- Boba_Fire();
- }
- break;
- case WP_DISRUPTOR:
- // TODO: Add Conditions Here
- Boba_Fire();
- break;
- case WP_BLASTER:
- // TODO: Add Conditions Here
- Boba_Fire();
- break;
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // Tactics avaliable to Boba Fett:
- // --------------------------------
- // BTS_RIFLE, // Uses Jedi / Seeker Movement
- // BTS_MISSILE, // Uses Jedi / Seeker Movement
- // BTS_SNIPER, // Uses Special Movement Internal To This File
- // BTS_FLAMETHROW, // Locked In Place
- // BTS_AMBUSHWAIT, // Goto CP & Wait
- //
- //
- // Weapons available to Boba Fett:
- // --------------------------------
- // WP_NONE (Flame Thrower)
- // WP_ROCKET_LAUNCHER
- // WP_BLASTER
- // WP_DISRUPTOR
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_TacticsSelect()
- {
- // Don't Change Tactics For A Little While
- //------------------------------------------
- TIMER_Set(NPC, "Boba_TacticsSelect", Q_irand(8000, 15000));
- int nextState = NPCInfo->localState;
- // Get Some Data That Will Help With The Selection Of The Next Tactic
- //--------------------------------------------------------------------
- bool enemyAlive = (NPC->enemy->health>0);
- float enemyDistance = Distance(NPC->currentOrigin, NPC->enemy->currentOrigin);
- bool enemyInFlameRange = (enemyDistance<BOBA_FLAMETHROWRANGE);
- bool enemyInRocketRange = (enemyDistance>BOBA_ROCKETRANGEMIN && enemyDistance<BOBA_ROCKETRANGEMAX);
- bool enemyRecentlySeen = Boba_CanSeeEnemy(NPC);
- // Enemy Is Really Close
- //-----------------------
- if (!enemyAlive)
- {
- nextState = BTS_RIFLE;
- }
- else if (enemyInFlameRange)
- {
- // If It's Been Long Enough Since Our Last Flame Blast, Try To Torch The Enemy
- //-----------------------------------------------------------------------------
- if (TIMER_Done(NPC, "nextFlameDelay"))
- {
- nextState = BTS_FLAMETHROW;
- }
- // Otherwise, He's Probably Too Close, So Try To Get Clear Of Him
- //----------------------------------------------------------------
- else
- {
- nextState = BTS_RIFLE;
- }
- }
- // Recently Saw The Enemy, Time For Some Good Ole Fighten!
- //---------------------------------------------------------
- else if (enemyRecentlySeen)
- {
- // At First, Boba will prefer to use his blaster against the player, but
- // the more times he is driven away (NPC->count), he will be less likely to
- // choose the blaster, and more likely to go for the missile launcher
- nextState = (!enemyInRocketRange || Q_irand(0, NPC->count)<1)?(BTS_RIFLE):(BTS_MISSILE);
- }
- // Hmmm... Havn't Seen The Player In A While, We Might Want To Try Something Sneaky
- //-----------------------------------------------------------------------------------
- else
- {
- bool SnipePointsNear = false; // TODO
- bool AmbushPointNear = false; // TODO
- if (Q_irand(0, NPC->count)>0)
- {
- int SniperPoint = NPC_FindCombatPoint(NPC->currentOrigin, 0, NPC->currentOrigin, CP_SNIPE|CP_CLEAR|CP_HAS_ROUTE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1);
- if (SniperPoint!=-1)
- {
- NPC_SetCombatPoint(SniperPoint);
- NPC_SetMoveGoal( NPC, level.combatPoints[SniperPoint].origin, 20, qtrue, SniperPoint );
- TIMER_Set(NPC, "PickNewSniperPoint", Q_irand(15000, 25000));
- SnipePointsNear = true;
- }
- }
- if (SnipePointsNear && TIMER_Done(NPC, "Boba_NoSniperTime"))
- {
- TIMER_Set(NPC, "Boba_NoSniperTime", 120000); // Don't snipe again for a while
- TIMER_Set(NPC, "Boba_TacticsSelect", Q_irand(35000, 45000));// More patience here
- nextState = BTS_SNIPER;
- }
- else if (AmbushPointNear)
- {
- TIMER_Set(NPC, "Boba_TacticsSelect", Q_irand(15000, 25000));// More patience here
- nextState = BTS_AMBUSHWAIT;
- }
- else
- {
- nextState = (!enemyInRocketRange || Q_irand(0, NPC->count)<1)?(BTS_RIFLE):(BTS_MISSILE);
- }
- }
- // The Next State Has Been Selected, Now Change Weapon If Necessary
- //------------------------------------------------------------------
- if (nextState!=NPCInfo->localState)
- {
- NPCInfo->localState = nextState;
- switch (NPCInfo->localState)
- {
- case BTS_FLAMETHROW:
- Boba_Printf("NEW TACTIC: Flame Thrower");
- Boba_ChangeWeapon(WP_NONE);
- Boba_DoFlameThrower(NPC);
- break;
- case BTS_RIFLE:
- Boba_Printf("NEW TACTIC: Rifle");
- Boba_ChangeWeapon(WP_BLASTER);
- break;
- case BTS_MISSILE:
- Boba_Printf("NEW TACTIC: Rocket Launcher");
- Boba_ChangeWeapon(WP_ROCKET_LAUNCHER);
- break;
- case BTS_SNIPER:
- Boba_Printf("NEW TACTIC: Sniper");
- Boba_ChangeWeapon(WP_DISRUPTOR);
- break;
- case BTS_AMBUSHWAIT:
- Boba_Printf("NEW TACTIC: Ambush");
- Boba_ChangeWeapon(WP_NONE);
- break;
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // Tactics
- //
- // This function is called right after Update()
- // If returns true, Jedi and Seeker AI not used for movement
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- bool Boba_Tactics()
- {
- if (!NPC->enemy)
- {
- return false;
- }
- // Think About Changing Tactics
- //------------------------------
- if (TIMER_Done(NPC, "Boba_TacticsSelect"))
- {
- Boba_TacticsSelect();
- }
- // These Tactics Require Seeker & Jedi Movement
- //----------------------------------------------
- if (!NPCInfo->localState ||
- NPCInfo->localState==BTS_RIFLE ||
- NPCInfo->localState==BTS_MISSILE)
- {
- return false;
- }
- // Flame Thrower - Locked In Place
- //---------------------------------
- if (NPCInfo->localState==BTS_FLAMETHROW)
- {
- Boba_DoFlameThrower( NPC );
- }
- // Sniper - Move Around, And Take Shots
- //--------------------------------------
- else if (NPCInfo->localState==BTS_SNIPER)
- {
- Boba_DoSniper( NPC );
- }
- // Ambush Wait
- //------------
- else if (NPCInfo->localState==BTS_AMBUSHWAIT)
- {
- Boba_DoAmbushWait( NPC );
- }
- NPC_FacePosition( NPC->enemy->currentOrigin, qtrue);
- NPC_UpdateAngles(qtrue, qtrue);
- return true; // Do Not Use Normal Jedi Or Seeker Movement
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- bool Boba_Respawn()
- {
- int cp = -1;
- // Try To Predict Where The Enemy Is Going
- //-----------------------------------------
- if (AverageEnemyDirectionSamples && NPC->behaviorSet[BSET_DEATH]==0)
- {
- vec3_t endPos;
- VectorMA(NPC->enemy->currentOrigin, 1000.0f / (float)AverageEnemyDirectionSamples, AverageEnemyDirection, endPos);
- cp = NPC_FindCombatPoint(endPos, 0, endPos, CP_FLEE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1);
- Boba_Printf("Attempting Predictive Spawn Point");
- }
- // If That Failed, Try To Go Directly To The Enemy
- //-------------------------------------------------
- if (cp==-1)
- {
- cp = NPC_FindCombatPoint(NPC->enemy->currentOrigin, 0, NPC->enemy->currentOrigin, CP_FLEE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1);
- Boba_Printf("Attempting Closest Current Spawn Point");
- }
- // If We've Found One, Go There
- //------------------------------
- if (cp!=-1)
- {
- NPC_SetCombatPoint( cp );
- NPCInfo->surrenderTime = 0;
- NPC->health = NPC->max_health;
- NPC->svFlags &=~SVF_NOCLIENT;
- NPC->count ++; // This is the number of times spawned
- G_SetOrigin(NPC, level.combatPoints[cp].origin);
- AverageEnemyDirectionSamples = 0;
- VectorClear(AverageEnemyDirection);
- Boba_Printf("Found Spawn Point (%d)", cp);
- return true;
- }
- assert(0); // Yea, that's bad...
- Boba_Printf("FAILED TO FIND SPAWN POINT");
- return false;
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- void Boba_Update()
- {
- // Never Forget The Player... Never.
- //-----------------------------------
- if (player && player->inuse && !NPC->enemy)
- {
- G_SetEnemy(NPC, player);
- NPC->svFlags |= SVF_LOCKEDENEMY; // Don't forget about the enemy once you've found him
- }
- // Hey, This Is Boba, He Tests The Trace All The Time
- //----------------------------------------------------
- if (NPC->enemy)
- {
- if (!(NPC->svFlags&SVF_NOCLIENT))
- {
- trace_t testTrace;
- vec3_t eyes;
- CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes );
- gi.trace (&testTrace, eyes, NULL, NULL, NPC->enemy->currentOrigin, NPC->s.number, MASK_SHOT);
- bool wasSeen = Boba_CanSeeEnemy(NPC);
- if (!testTrace.startsolid &&
- !testTrace.allsolid &&
- testTrace.entityNum == NPC->enemy->s.number)
- {
- NPCInfo->enemyLastSeenTime = level.time;
- NPCInfo->enemyLastHeardTime = level.time;
- VectorCopy(NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation);
- VectorCopy(NPC->enemy->currentOrigin, NPCInfo->enemyLastHeardLocation);
- }
- else if (gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin))
- {
- NPCInfo->enemyLastHeardTime = level.time;
- VectorCopy(NPC->enemy->currentOrigin, NPCInfo->enemyLastHeardLocation);
- }
- if (g_bobaDebug->integer)
- {
- bool nowSeen = Boba_CanSeeEnemy(NPC);
- if (!wasSeen && nowSeen)
- {
- Boba_Printf("Enemy Seen");
- }
- if (wasSeen && !nowSeen)
- {
- Boba_Printf("Enemy Lost");
- }
- CG_DrawEdge(NPC->currentOrigin, NPC->enemy->currentOrigin, (nowSeen)?(EDGE_IMPACT_SAFE):(EDGE_IMPACT_POSSIBLE));
- }
- }
- if (!NPCInfo->surrenderTime)
- {
- if ((level.time - NPCInfo->enemyLastSeenTime)>20000 && TIMER_Done(NPC, "TooLongGoneRespawn"))
- {
- TIMER_Set(NPC, "TooLongGoneRespawn", 30000); // Give him some time to get to you before trying again
- Boba_Printf("Gone Too Long, Attempting Respawn Even Though Not Hiding");
- Boba_Respawn();
- }
- }
- }
- // Make Sure He Always Appears In The Last Area With Full Health When His Death Script Is Turned On
- //--------------------------------------------------------------------------------------------------
- if (!BobaHadDeathScript && NPC->behaviorSet[BSET_DEATH]!=0)
- {
- if (!gi.inPVS(NPC->enemy->currentOrigin, NPC->currentOrigin))
- {
- Boba_Printf("Attempting Final Battle Spawn...");
- if (Boba_Respawn())
- {
- BobaHadDeathScript = true;
- }
- else
- {
- Boba_Printf("Failed");
- }
- }
- }
- // Don't Forget To Turn Off That Flame Thrower, Mr. Fett - You're Waisting Precious Natural Gases
- //------------------------------------------------------------------------------------------------
- if ((NPCInfo->aiFlags&NPCAI_FLAMETHROW) && (TIMER_Done(NPC, "flameTime")))
- {
- Boba_StopFlameThrower(NPC);
- }
- // Occasionally A Jump Turns Into A Rocket Fly
- //---------------------------------------------
- if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE
- && NPC->client->ps.forceJumpZStart
- && !Q_irand( 0, 10 ) )
- {//take off
- Boba_FlyStart( NPC );
- }
- // If Hurting, Try To Run Away
- //-----------------------------
- if (!NPCInfo->surrenderTime && (NPC->health<NPC->max_health/10))
- {
- Boba_Printf("Time To Surrender, Searching For Flee Point");
- // Find The Closest Flee Point That I Can Get To
- //-----------------------------------------------
- int cp = NPC_FindCombatPoint(NPC->currentOrigin, 0, NPC->currentOrigin, CP_FLEE|CP_HAS_ROUTE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1);
- if (cp!=-1)
- {
- NPC_SetCombatPoint( cp );
- NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp );
- if (NPC->count<6)
- {
- NPCInfo->surrenderTime = level.time + Q_irand(5000, 10000) + 1000*(6-NPC->count);
- }
- else
- {
- NPCInfo->surrenderTime = level.time + Q_irand(5000, 10000);
- }
- }
- else
- {
- Boba_Printf(" Failure");
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- //
- ////////////////////////////////////////////////////////////////////////////////////////
- bool Boba_Flee()
- {
- bool EnemyRecentlySeen = ((level.time - NPCInfo->enemyLastSeenTime)<10000);
- bool ReachedEscapePoint = (Distance(level.combatPoints[NPCInfo->combatPoint].origin, NPC->currentOrigin)<50.0f);
- bool HasBeenGoneEnough = (level.time>NPCInfo->surrenderTime || (level.time - NPCInfo->enemyLastSeenTime)>400000);
- // Is It Time To Come Back For Some More?
- //----------------------------------------
- if (!EnemyRecentlySeen || ReachedEscapePoint)
- {
- NPC->svFlags |= SVF_NOCLIENT;
- if (HasBeenGoneEnough)
- {
- if ((level.time - NPCInfo->enemyLastSeenTime)>400000)
- {
- Boba_Printf(" Gone Too Long, Attempting Respawn");
- }
- if (Boba_Respawn())
- {
- return true;
- }
- }
- else if (ReachedEscapePoint && (NPCInfo->surrenderTime - level.time)>3000)
- {
- if (TIMER_Done(NPC, "SpookPlayerTimer"))
- {
- vec3_t testDirection;
- TIMER_Set(NPC, "SpookPlayerTimer", Q_irand(2000, 10000));
- switch(Q_irand(0, 1))
- {
- case 0:
- Boba_Printf("SPOOK: Dust");
- Boba_DustFallNear(NPC->enemy->currentOrigin, Q_irand(1,2));
- break;
- case 1:
- Boba_Printf("SPOOK: Footsteps");
- testDirection[0] = (random() * 0.5f) - 1.0f;
- testDirection[0] += (testDirection[0]>0.0f)?(0.5f):(-0.5f);
- testDirection[1] = (random() * 0.5f) - 1.0f;
- testDirection[1] += (testDirection[1]>0.0f)?(0.5f):(-0.5f);
- testDirection[2] = 1.0f;
- VectorMA(NPC->enemy->currentOrigin, 400.0f, testDirection, BobaFootStepLoc);
- BobaFootStepCount = Q_irand(3,8);
- break;
- }
- }
- if (BobaFootStepCount && TIMER_Done(NPC, "BobaFootStepFakeTimer"))
- {
- TIMER_Set(NPC, "BobaFootStepFakeTimer", Q_irand(300, 800));
- BobaFootStepCount --;
- G_SoundAtSpot(BobaFootStepLoc, G_SoundIndex(va("sound/player/footsteps/boot%d", Q_irand(1,4))), qtrue);
- }
- if (TIMER_Done(NPC, "ResampleEnemyDirection") && NPC->enemy->resultspeed>10.0f)
- {
- TIMER_Set(NPC, "ResampleEnemyDirection", Q_irand(500, 1000));
- AverageEnemyDirectionSamples ++;
- vec3_t moveDir;
- VectorCopy(NPC->enemy->client->ps.velocity, moveDir);
- VectorNormalize(moveDir);
- VectorAdd(AverageEnemyDirection, moveDir, AverageEnemyDirection);
- }
- if (g_bobaDebug->integer && AverageEnemyDirectionSamples)
- {
- vec3_t endPos;
- VectorMA(NPC->enemy->currentOrigin, 500.0f / (float)AverageEnemyDirectionSamples, AverageEnemyDirection, endPos);
- CG_DrawEdge(NPC->enemy->currentOrigin, endPos, EDGE_IMPACT_POSSIBLE);
- }
- }
- }
- else
- {
- NPCInfo->surrenderTime += 100;
- }
- // Finish The Flame Thrower First...
- //-----------------------------------
- if (NPCInfo->aiFlags&NPCAI_FLAMETHROW)
- {
- Boba_DoFlameThrower( NPC );
- NPC_FacePosition( NPC->enemy->currentOrigin, qtrue);
- NPC_UpdateAngles(qtrue, qtrue);
- return true;
- }
- bool IsOnAPath = !!NPC_MoveToGoal(qtrue);
- if (!ReachedEscapePoint &&
- NPCInfo->aiFlags&NPCAI_BLOCKED &&
- NPC->client->moveType!=MT_FLYSWIM &&
- ((level.time - NPCInfo->blockedDebounceTime)>1000)
- )
- {
- if (!Boba_CanSeeEnemy(NPC) && Distance(NPC->currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin)<200)
- {
- Boba_Printf("BLOCKED: Just Teleporting There");
- G_SetOrigin(NPC, level.combatPoints[NPCInfo->combatPoint].origin);
- }
- else
- {
- Boba_Printf("BLOCKED: Attempting Jump");
- if (IsOnAPath)
- {
- if (NPC_TryJump(NPCInfo->blockedTargetPosition))
- {
- }
- else
- {
- Boba_Printf(" Failed");
- }
- }
- else if (EnemyRecentlySeen)
- {
- if (NPC_TryJump(NPCInfo->enemyLastSeenLocation))
- {
- }
- else
- {
- Boba_Printf(" Failed");
- }
- }
- }
- }
- NPC_UpdateAngles( qtrue, qtrue );
- return true;
- }
|