1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725 |
- //
- // NPC.cpp - generic functions
- //
- // leave this line at the top for all NPC_xxxx.cpp files...
- #include "g_headers.h"
- #include "b_local.h"
- #include "anims.h"
- #include "g_functions.h"
- #include "say.h"
- #include "Q3_Interface.h"
- #include "g_vehicles.h"
- extern vec3_t playerMins;
- extern vec3_t playerMaxs;
- //extern void PM_SetAnimFinal(int *torsoAnim,int *legsAnim,int type,int anim,int priority,int *torsoAnimTimer,int *legsAnimTimer,gentity_t *gent);
- extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time );
- extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time );
- extern void NPC_BSNoClip ( void );
- extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
- extern void NPC_ApplyRoff (void);
- extern void NPC_TempLookTarget ( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
- extern void NPC_CheckPlayerAim ( void );
- extern void NPC_CheckAllClear ( void );
- extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
- extern qboolean NPC_CheckLookTarget( gentity_t *self );
- extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime );
- extern void Mark1_dying( gentity_t *self );
- extern void NPC_BSCinematic( void );
- extern int GetTime ( int lastTime );
- extern void G_CheckCharmed( gentity_t *self );
- extern qboolean Boba_Flying( gentity_t *self );
- extern qboolean RT_Flying( gentity_t *self );
- extern qboolean Jedi_CultistDestroyer( gentity_t *self );
- extern void Boba_Update();
- extern bool Boba_Flee();
- extern bool Boba_Tactics();
- extern void BubbleShield_Update();
- extern qboolean PM_LockedAnim( int anim );
- extern cvar_t *g_dismemberment;
- extern cvar_t *g_saberRealisticCombat;
- extern cvar_t *g_corpseRemovalTime;
- //Local Variables
- // ai debug cvars
- cvar_t *debugNPCAI; // used to print out debug info about the bot AI
- cvar_t *debugNPCFreeze; // set to disable bot ai and temporarily freeze them in place
- cvar_t *debugNPCName;
- cvar_t *d_saberCombat;
- cvar_t *d_JediAI;
- cvar_t *d_noGroupAI;
- cvar_t *d_asynchronousGroupAI;
- cvar_t *d_slowmodeath;
- extern qboolean stop_icarus;
- gentity_t *NPC;
- gNPC_t *NPCInfo;
- gclient_t *client;
- usercmd_t ucmd;
- visibility_t enemyVisibility;
- void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend);
- static bState_t G_CurrentBState( gNPC_t *gNPC );
- extern int eventClearTime;
- void CorpsePhysics( gentity_t *self )
- {
- // run the bot through the server like it was a real client
- memset( &ucmd, 0, sizeof( ucmd ) );
- ClientThink( self->s.number, &ucmd );
- VectorCopy( self->s.origin, self->s.origin2 );
-
- //FIXME: match my pitch and roll for the slope of my groundPlane
- if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !(self->flags&FL_DISINTEGRATED) )
- {//on the ground
- //FIXME: check 4 corners
- pitch_roll_for_slope( self );
- }
- if ( eventClearTime == level.time + ALERT_CLEAR_TIME )
- {//events were just cleared out so add me again
- if ( !(self->client->ps.eFlags&EF_NODRAW) )
- {
- AddSightEvent( self->enemy, self->currentOrigin, 384, AEL_DISCOVERED );
- }
- }
- if ( level.time - self->s.time > 3000 )
- {//been dead for 3 seconds
- if ( g_dismemberment->integer < 11381138 && !g_saberRealisticCombat->integer )
- {//can't be dismembered once dead
- if ( self->client->NPC_class != CLASS_PROTOCOL )
- {
- self->client->dismembered = true;
- }
- }
- }
- if ( level.time - self->s.time > 500 )
- {//don't turn "nonsolid" until about 1 second after actual death
- if ((self->client->NPC_class != CLASS_MARK1) && (self->client->NPC_class != CLASS_INTERROGATOR)) // The Mark1 & Interrogator stays solid.
- {
- self->contents = CONTENTS_CORPSE;
- }
- if ( self->message )
- {
- self->contents |= CONTENTS_TRIGGER;
- }
- }
- }
- /*
- ----------------------------------------
- NPC_RemoveBody
- Determines when it's ok to ditch the corpse
- ----------------------------------------
- */
- #define REMOVE_DISTANCE 128
- #define REMOVE_DISTANCE_SQR (REMOVE_DISTANCE * REMOVE_DISTANCE)
- extern qboolean InFOVFromPlayerView ( gentity_t *ent, int hFOV, int vFOV );
- qboolean G_OkayToRemoveCorpse( gentity_t *self )
- {
- //if we're still on a vehicle, we won't remove ourselves until we get ejected
- if ( self->client && self->client->NPC_class != CLASS_VEHICLE && self->s.m_iVehicleNum != 0 )
- {
- Vehicle_t *pVeh = g_entities[self->s.m_iVehicleNum].m_pVehicle;
- if ( pVeh )
- {
- if ( !pVeh->m_pVehicleInfo->Eject( pVeh, self, qtrue ) )
- {//dammit, still can't get off the vehicle...
- return qfalse;
- }
- }
- else
- {
- assert(0);
- #ifndef FINAL_BUILD
- Com_Printf(S_COLOR_RED"ERROR: Dead pilot's vehicle removed while corpse was riding it (pilot: %s)???\n",self->targetname);
- #endif
- }
- }
- if ( self->message )
- {//I still have a key
- return qfalse;
- }
-
- if ( IIcarusInterface::GetIcarus()->IsRunning( self->m_iIcarusID ) )
- {//still running a script
- return qfalse;
- }
- if ( self->activator
- && self->activator->client
- && ((self->activator->client->ps.eFlags&EF_HELD_BY_RANCOR)
- || (self->activator->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE)
- || (self->activator->client->ps.eFlags&EF_HELD_BY_WAMPA)) )
- {//still holding a victim?
- return qfalse;
- }
- if ( self->client
- && ((self->client->ps.eFlags&EF_HELD_BY_RANCOR)
- || (self->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE)
- || (self->client->ps.eFlags&EF_HELD_BY_WAMPA) ) )
- {//being held by a creature
- return qfalse;
- }
- if ( self->client->ps.heldByClient < ENTITYNUM_WORLD )
- {//being dragged
- return qfalse;
- }
- //okay, well okay to remove us...?
- return qtrue;
- }
- void NPC_RemoveBody( gentity_t *self )
- {
- self->nextthink = level.time + FRAMETIME/2;
- //run physics at 20fps
- CorpsePhysics( self );
- if ( self->NPC->nextBStateThink <= level.time )
- {//run logic at 10 fps
- if( self->m_iIcarusID != IIcarusInterface::ICARUS_INVALID && !stop_icarus )
- {
- IIcarusInterface::GetIcarus()->Update( self->m_iIcarusID );
- }
- self->NPC->nextBStateThink = level.time + FRAMETIME;
- if ( !G_OkayToRemoveCorpse( self ) )
- {
- return;
- }
- // I don't consider this a hack, it's creative coding . . .
- // I agree, very creative... need something like this for ATST and GALAKMECH too!
- if (self->client->NPC_class == CLASS_MARK1)
- {
- Mark1_dying( self );
- }
- // Since these blow up, remove the bounding box.
- if ( self->client->NPC_class == CLASS_REMOTE
- || self->client->NPC_class == CLASS_SENTRY
- || self->client->NPC_class == CLASS_PROBE
- || self->client->NPC_class == CLASS_INTERROGATOR
- || self->client->NPC_class == CLASS_PROBE
- || self->client->NPC_class == CLASS_MARK2 )
- {
- G_FreeEntity( self );
- return;
- }
- //FIXME: don't ever inflate back up?
- self->maxs[2] = self->client->renderInfo.eyePoint[2] - self->currentOrigin[2] + 4;
- if ( self->maxs[2] < -8 )
- {
- self->maxs[2] = -8;
- }
- if ( (self->NPC->aiFlags&NPCAI_HEAL_ROSH) )
- {//kothos twins' bodies are never removed
- return;
- }
- if ( self->client->NPC_class == CLASS_GALAKMECH )
- {//never disappears
- return;
- }
- if ( self->NPC && self->NPC->timeOfDeath <= level.time )
- {
- self->NPC->timeOfDeath = level.time + 1000;
- // Only do all of this nonsense for Scav boys ( and girls )
- /// if ( self->client->playerTeam == TEAM_SCAVENGERS || self->client->playerTeam == TEAM_KLINGON
- // || self->client->playerTeam == TEAM_HIROGEN || self->client->playerTeam == TEAM_MALON )
- // should I check NPC_class here instead of TEAM ? - dmv
- if( self->client->playerTeam == TEAM_ENEMY || self->client->NPC_class == CLASS_PROTOCOL )
- {
- self->nextthink = level.time + FRAMETIME; // try back in a second
- if ( DistanceSquared( g_entities[0].currentOrigin, self->currentOrigin ) <= REMOVE_DISTANCE_SQR )
- {
- return;
- }
- if ( (InFOVFromPlayerView( self, 110, 90 )) ) // generous FOV check
- {
- if ( (NPC_ClearLOS( &g_entities[0], self->currentOrigin )) )
- {
- return;
- }
- }
- }
- //FIXME: there are some conditions - such as heavy combat - in which we want
- // to remove the bodies... but in other cases it's just weird, like
- // when they're right behind you in a closed room and when they've been
- // placed as dead NPCs by a designer...
- // For now we just assume that a corpse with no enemy was
- // placed in the map as a corpse
- if ( self->enemy )
- {
- if ( self->client && self->client->ps.saberEntityNum > 0 && self->client->ps.saberEntityNum < ENTITYNUM_WORLD )
- {
- gentity_t *saberent = &g_entities[self->client->ps.saberEntityNum];
- if ( saberent )
- {
- G_FreeEntity( saberent );
- }
- }
- G_FreeEntity( self );
- }
- }
- }
- }
- /*
- ----------------------------------------
- NPC_RemoveBody
- Determines when it's ok to ditch the corpse
- ----------------------------------------
- */
- int BodyRemovalPadTime( gentity_t *ent )
- {
- int time;
- if ( !ent || !ent->client )
- return 0;
- /*
- switch ( ent->client->playerTeam )
- {
- case TEAM_KLINGON: // no effect, we just remove them when the player isn't looking
- case TEAM_SCAVENGERS:
- case TEAM_HIROGEN:
- case TEAM_MALON:
- case TEAM_IMPERIAL:
- case TEAM_STARFLEET:
- time = 10000; // 15 secs.
- break;
- case TEAM_BORG:
- time = 2000;
- break;
- case TEAM_STASIS:
- return qtrue;
- break;
- case TEAM_FORGE:
- time = 1000;
- break;
- case TEAM_BOTS:
- // if (!Q_stricmp( ent->NPC_type, "mouse" ))
- // {
- time = 0;
- // }
- // else
- // {
- // time = 10000;
- // }
- break;
- case TEAM_8472:
- time = 2000;
- break;
- default:
- // never go away
- time = Q3_INFINITE;
- break;
- }
- */
- // team no longer indicates species/race, so in this case we'd use NPC_class, but
- switch( ent->client->NPC_class )
- {
- case CLASS_MOUSE:
- case CLASS_GONK:
- case CLASS_R2D2:
- case CLASS_R5D2:
- //case CLASS_PROTOCOL:
- case CLASS_MARK1:
- case CLASS_MARK2:
- case CLASS_PROBE:
- case CLASS_SEEKER:
- case CLASS_REMOTE:
- case CLASS_SENTRY:
- case CLASS_INTERROGATOR:
- time = 0;
- break;
- default:
- // never go away
- if ( g_corpseRemovalTime->integer <= 0 )
- {
- time = Q3_INFINITE;
- }
- else
- {
- time = g_corpseRemovalTime->integer*1000;
- }
- break;
- }
-
- return time;
- }
- /*
- ----------------------------------------
- NPC_RemoveBodyEffect
- Effect to be applied when ditching the corpse
- ----------------------------------------
- */
- static void NPC_RemoveBodyEffect(void)
- {
- // vec3_t org;
- // gentity_t *tent;
- if ( !NPC || !NPC->client || (NPC->s.eFlags & EF_NODRAW) )
- return;
- /*
- switch(NPC->client->playerTeam)
- {
- case TEAM_STARFLEET:
- //FIXME: Starfleet beam out
- break;
- case TEAM_BOTS:
- // VectorCopy( NPC->currentOrigin, org );
- // org[2] -= 16;
- // tent = G_TempEntity( org, EV_BOT_EXPLODE );
- // tent->owner = NPC;
- break;
- default:
- break;
- }
- */
- // team no longer indicates species/race, so in this case we'd use NPC_class, but
-
- // stub code
- switch(NPC->client->NPC_class)
- {
- case CLASS_PROBE:
- case CLASS_SEEKER:
- case CLASS_REMOTE:
- case CLASS_SENTRY:
- case CLASS_GONK:
- case CLASS_R2D2:
- case CLASS_R5D2:
- //case CLASS_PROTOCOL:
- case CLASS_MARK1:
- case CLASS_MARK2:
- case CLASS_INTERROGATOR:
- case CLASS_ATST: // yeah, this is a little weird, but for now I'm listing all droids
- // VectorCopy( NPC->currentOrigin, org );
- // org[2] -= 16;
- // tent = G_TempEntity( org, EV_BOT_EXPLODE );
- // tent->owner = NPC;
- break;
- default:
- break;
- }
- }
- /*
- ====================================================================
- void pitch_roll_for_slope (edict_t *forwhom, vec3_t *slope, vec3_t storeAngles )
- MG
- This will adjust the pitch and roll of a monster to match
- a given slope - if a non-'0 0 0' slope is passed, it will
- use that value, otherwise it will use the ground underneath
- the monster. If it doesn't find a surface, it does nothinh\g
- and returns.
- ====================================================================
- */
- void pitch_roll_for_slope( gentity_t *forwhom, vec3_t pass_slope, vec3_t storeAngles )
- {
- vec3_t slope;
- vec3_t nvf, ovf, ovr, startspot, endspot, new_angles = { 0, 0, 0 };
- float pitch, mod, dot;
- //if we don't have a slope, get one
- if( !pass_slope || VectorCompare( vec3_origin, pass_slope ) )
- {
- trace_t trace;
- VectorCopy( forwhom->currentOrigin, startspot );
- startspot[2] += forwhom->mins[2] + 4;
- VectorCopy( startspot, endspot );
- endspot[2] -= 300;
- gi.trace( &trace, forwhom->currentOrigin, vec3_origin, vec3_origin, endspot, forwhom->s.number, MASK_SOLID );
- // if(trace_fraction>0.05&&forwhom.movetype==MOVETYPE_STEP)
- // forwhom.flags(-)FL_ONGROUND;
- if ( trace.fraction >= 1.0 )
- return;
- if( !( &trace.plane ) )
- return;
- if ( VectorCompare( vec3_origin, trace.plane.normal ) )
- return;
- VectorCopy( trace.plane.normal, slope );
- }
- else
- {
- VectorCopy( pass_slope, slope );
- }
- if ( forwhom->client && forwhom->client->NPC_class == CLASS_VEHICLE )
- {//special code for vehicles
- Vehicle_t *pVeh = forwhom->m_pVehicle;
- vec3_t tempAngles;
- tempAngles[PITCH] = tempAngles[ROLL] = 0;
- tempAngles[YAW] = pVeh->m_vOrientation[YAW];
- AngleVectors( tempAngles, ovf, ovr, NULL );
- }
- else
- {
- AngleVectors( forwhom->currentAngles, ovf, ovr, NULL );
- }
- vectoangles( slope, new_angles );
- pitch = new_angles[PITCH] + 90;
- new_angles[ROLL] = new_angles[PITCH] = 0;
- AngleVectors( new_angles, nvf, NULL, NULL );
- mod = DotProduct( nvf, ovr );
- if ( mod<0 )
- mod = -1;
- else
- mod = 1;
- dot = DotProduct( nvf, ovf );
- if ( storeAngles )
- {
- storeAngles[PITCH] = dot * pitch;
- storeAngles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod);
- }
- else if ( forwhom->client )
- {
- forwhom->client->ps.viewangles[PITCH] = dot * pitch;
- forwhom->client->ps.viewangles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod);
- float oldmins2 = forwhom->mins[2];
- forwhom->mins[2] = -24 + 12 * fabs(forwhom->client->ps.viewangles[PITCH])/180.0f;
- //FIXME: if it gets bigger, move up
- if ( oldmins2 > forwhom->mins[2] )
- {//our mins is now lower, need to move up
- //FIXME: trace?
- forwhom->client->ps.origin[2] += (oldmins2 - forwhom->mins[2]);
- forwhom->currentOrigin[2] = forwhom->client->ps.origin[2];
- gi.linkentity( forwhom );
- }
- }
- else
- {
- forwhom->currentAngles[PITCH] = dot * pitch;
- forwhom->currentAngles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod);
- }
- }
- /*
- void NPC_PostDeathThink( void )
- {
- float mostdist;
- trace_t trace1, trace2, trace3, trace4, movetrace;
- vec3_t org, endpos, startpos, forward, right;
- int whichtrace = 0;
- float cornerdist[4];
- qboolean frontbackbothclear = false;
- qboolean rightleftbothclear = false;
- if( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE || !VectorCompare( vec3_origin, NPC->client->ps.velocity ) )
- {
- if ( NPC->client->ps.groundEntityNum != ENTITYNUM_NONE && NPC->client->ps.friction == 1.0 )//check avelocity?
- {
- pitch_roll_for_slope( NPC );
- }
- return;
- }
- cornerdist[FRONT] = cornerdist[BACK] = cornerdist[RIGHT] = cornerdist[LEFT] = 0.0f;
- mostdist = MIN_DROP_DIST;
- AngleVectors( NPC->currentAngles, forward, right, NULL );
- VectorCopy( NPC->currentOrigin, org );
- org[2] += NPC->mins[2];
- VectorMA( org, NPC->dead_size, forward, startpos );
- VectorCopy( startpos, endpos );
- endpos[2] -= 128;
- gi.trace( &trace1, startpos, vec3_origin, vec3_origin, endpos, NPC->s.number, MASK_SOLID, );
- if( !trace1.allsolid && !trace1.startsolid )
- {
- cornerdist[FRONT] = trace1.fraction;
- if ( trace1.fraction > mostdist )
- {
- mostdist = trace1.fraction;
- whichtrace = 1;
- }
- }
- VectorMA( org, -NPC->dead_size, forward, startpos );
- VectorCopy( startpos, endpos );
- endpos[2] -= 128;
- gi.trace( &trace2, startpos, vec3_origin, vec3_origin, endpos, NPC->s.number, MASK_SOLID );
- if( !trace2.allsolid && !trace2.startsolid )
- {
- cornerdist[BACK] = trace2.fraction;
- if ( trace2.fraction > mostdist )
- {
- mostdist = trace2.fraction;
- whichtrace = 2;
- }
- }
- VectorMA( org, NPC->dead_size/2, right, startpos );
- VectorCopy( startpos, endpos );
- endpos[2] -= 128;
- gi.trace( &trace3, startpos, vec3_origin, vec3_origin, endpos, NPC->s.number, MASK_SOLID );
- if ( !trace3.allsolid && !trace3.startsolid )
- {
- cornerdist[RIGHT] = trace3.fraction;
- if ( trace3.fraction>mostdist )
- {
- mostdist = trace3.fraction;
- whichtrace = 3;
- }
- }
- VectorMA( org, -NPC->dead_size/2, right, startpos );
- VectorCopy( startpos, endpos );
- endpos[2] -= 128;
- gi.trace( &trace4, startpos, vec3_origin, vec3_origin, endpos, NPC->s.number, MASK_SOLID );
- if ( !trace4.allsolid && !trace4.startsolid )
- {
- cornerdist[LEFT] = trace4.fraction;
- if ( trace4.fraction > mostdist )
- {
- mostdist = trace4.fraction;
- whichtrace = 4;
- }
- }
-
- //OK! Now if two opposite sides are hanging, use a third if any, else, do nothing
- if ( cornerdist[FRONT] > MIN_DROP_DIST && cornerdist[BACK] > MIN_DROP_DIST )
- frontbackbothclear = true;
- if ( cornerdist[RIGHT] > MIN_DROP_DIST && cornerdist[LEFT] > MIN_DROP_DIST )
- rightleftbothclear = true;
- if ( frontbackbothclear && rightleftbothclear )
- return;
- if ( frontbackbothclear )
- {
- if ( cornerdist[RIGHT] > MIN_DROP_DIST )
- whichtrace = 3;
- else if ( cornerdist[LEFT] > MIN_DROP_DIST )
- whichtrace = 4;
- else
- return;
- }
- if ( rightleftbothclear )
- {
- if ( cornerdist[FRONT] > MIN_DROP_DIST )
- whichtrace = 1;
- else if ( cornerdist[BACK] > MIN_DROP_DIST )
- whichtrace = 2;
- else
- return;
- }
-
- switch ( whichtrace )
- {//check for stuck
- case 1:
- VectorMA( NPC->currentOrigin, NPC->maxs[0], forward, endpos );
- gi.trace( &movetrace, NPC->currentOrigin, NPC->mins, NPC->maxs, endpos, NPC->s.number, MASK_MONSTERSOLID );
- if ( movetrace.allsolid || movetrace.startsolid || movetrace.fraction < 1.0 )
- if ( canmove( movetrace.ent ) )
- whichtrace = -1;
- else
- whichtrace = 0;
- break;
- case 2:
- VectorMA( NPC->currentOrigin, -NPC->maxs[0], forward, endpos );
- gi.trace( &movetrace, NPC->currentOrigin, NPC->mins, NPC->maxs, endpos, NPC->s.number, MASK_MONSTERSOLID );
- if ( movetrace.allsolid || movetrace.startsolid || movetrace.fraction < 1.0 )
- if ( canmove( movetrace.ent ) )
- whichtrace = -1;
- else
- whichtrace = 0;
- break;
- case 3:
- VectorMA( NPC->currentOrigin, NPC->maxs[0], right, endpos );
- gi.trace( &movetrace, NPC->currentOrigin, NPC->mins, NPC->maxs, endpos, NPC->s.number, MASK_MONSTERSOLID );
- if ( movetrace.allsolid || movetrace.startsolid || movetrace.fraction < 1.0 )
- if ( canmove( movetrace.ent ) )
- whichtrace = -1;
- else
- whichtrace = 0;
- break;
- case 4:
- VectorMA( NPC->currentOrigin, -NPC->maxs[0], right, endpos );
- gi.trace( &movetrace, NPC->currentOrigin, NPC->mins, NPC->maxs, endpos, NPC->s.number, MASK_MONSTERSOLID );
- if (movetrace.allsolid || movetrace.startsolid || movetrace.fraction < 1.0 )
- if ( canmove( movetrace.ent ) )
- whichtrace = -1;
- else
- whichtrace = 0;
- break;
- }
-
- switch ( whichtrace )
- {
- case 1:
- VectorMA( NPC->client->ps.velocity, 200, forward, NPC->client->ps.velocity );
- if ( trace1.fraction >= 0.9 )
- {
- //can't anymore, origin not in center of deathframe!
- // NPC->avelocity[PITCH] = -300;
- NPC->client->ps.friction = 1.0;
- }
- else
- {
- pitch_roll_for_slope( NPC, &trace1.plane.normal );
- NPC->client->ps.friction = trace1.plane.normal[2] * 0.1;
- }
- return;
- break;
-
- case 2:
- VectorMA( NPC->client->ps.velocity, -200, forward, NPC->client->ps.velocity );
- if(trace2.fraction >= 0.9)
- {
- //can't anymore, origin not in center of deathframe!
- // NPC->avelocity[PITCH] = 300;
- NPC->client->ps.friction = 1.0;
- }
- else
- {
- pitch_roll_for_slope( NPC, &trace2.plane.normal );
- NPC->client->ps.friction = trace2.plane.normal[2] * 0.1;
- }
- return;
- break;
-
- case 3:
- VectorMA( NPC->client->ps.velocity, 200, right, NPC->client->ps.velocity );
- if ( trace3.fraction >= 0.9 )
- {
- //can't anymore, origin not in center of deathframe!
- // NPC->avelocity[ROLL] = -300;
- NPC->client->ps.friction = 1.0;
- }
- else
- {
- pitch_roll_for_slope( NPC, &trace3.plane.normal );
- NPC->client->ps.friction = trace3.plane.normal[2] * 0.1;
- }
- return;
- break;
-
- case 4:
- VectorMA( NPC->client->ps.velocity, -200, right, NPC->client->ps.velocity );
- if ( trace4.fraction >= 0.9 )
- {
- //can't anymore, origin not in center of deathframe!
- // NPC->avelocity[ROLL] = 300;
- NPC->client->ps.friction = 1.0;
- }
- else
- {
- pitch_roll_for_slope( NPC, &trace4.plane.normal );
- NPC->client->ps.friction = trace4.plane.normal[2] * 0.1;
- }
- return;
- break;
- }
- //on solid ground
- if ( whichtrace == -1 )
- {
- return;
- }
- NPC->client->ps.friction = 1.0;
- //VectorClear( NPC->avelocity );
- pitch_roll_for_slope( NPC );
- //gi.linkentity (NPC);
- }
- */
- /*
- ----------------------------------------
- DeadThink
- ----------------------------------------
- */
- static void DeadThink ( void )
- {
- trace_t trace;
- //HACKHACKHACKHACKHACK
- //We should really have a seperate G2 bounding box (seperate from the physics bbox) for G2 collisions only
- //FIXME: don't ever inflate back up?
- //GAH! With Ragdoll, they get stuck in the ceiling
- float oldMaxs2 = NPC->maxs[2];
- NPC->maxs[2] = NPC->client->renderInfo.eyePoint[2] - NPC->currentOrigin[2] + 4;
- if ( NPC->maxs[2] < -8 )
- {
- NPC->maxs[2] = -8;
- }
- if ( NPC->maxs[2] > oldMaxs2 )
- {//inflating maxs, make sure we're not inflating into solid
- gi.trace (&trace, NPC->currentOrigin, NPC->mins, NPC->maxs, NPC->currentOrigin, NPC->s.number, NPC->clipmask );
- if ( trace.allsolid )
- {//must be inflating
- NPC->maxs[2] = oldMaxs2;
- }
- }
- /*
- {
- if ( VectorCompare( NPC->client->ps.velocity, vec3_origin ) )
- {//not flying through the air
- if ( NPC->mins[0] > -32 )
- {
- NPC->mins[0] -= 1;
- gi.trace (&trace, NPC->currentOrigin, NPC->mins, NPC->maxs, NPC->currentOrigin, NPC->s.number, NPC->clipmask );
- if ( trace.allsolid )
- {
- NPC->mins[0] += 1;
- }
- }
- if ( NPC->maxs[0] < 32 )
- {
- NPC->maxs[0] += 1;
- gi.trace (&trace, NPC->currentOrigin, NPC->mins, NPC->maxs, NPC->currentOrigin, NPC->s.number, NPC->clipmask );
- if ( trace.allsolid )
- {
- NPC->maxs[0] -= 1;
- }
- }
- if ( NPC->mins[1] > -32 )
- {
- NPC->mins[1] -= 1;
- gi.trace (&trace, NPC->currentOrigin, NPC->mins, NPC->maxs, NPC->currentOrigin, NPC->s.number, NPC->clipmask );
- if ( trace.allsolid )
- {
- NPC->mins[1] += 1;
- }
- }
- if ( NPC->maxs[1] < 32 )
- {
- NPC->maxs[1] += 1;
- gi.trace (&trace, NPC->currentOrigin, NPC->mins, NPC->maxs, NPC->currentOrigin, NPC->s.number, NPC->clipmask );
- if ( trace.allsolid )
- {
- NPC->maxs[1] -= 1;
- }
- }
- }
- }
- //HACKHACKHACKHACKHACK
- */
- //FIXME: tilt and fall off of ledges?
- //NPC_PostDeathThink();
- /*
- if ( !NPCInfo->timeOfDeath && NPC->client != NULL && NPCInfo != NULL )
- {
- //haven't finished death anim yet and were NOT given a specific amount of time to wait before removal
- int legsAnim = NPC->client->ps.legsAnim;
- animation_t *animations = knownAnimFileSets[NPC->client->clientInfo.animFileIndex].animations;
- NPC->bounceCount = -1; // This is a cheap hack for optimizing the pointcontents check below
- //ghoul doesn't tell us this anymore
- //if ( NPC->client->renderInfo.legsFrame == animations[legsAnim].firstFrame + (animations[legsAnim].numFrames - 1) )
- {
- //reached the end of the death anim
- NPCInfo->timeOfDeath = level.time + BodyRemovalPadTime( NPC );
- }
- }
- else
- */
- {
- //death anim done (or were given a specific amount of time to wait before removal), wait the requisite amount of time them remove
- if ( level.time >= NPCInfo->timeOfDeath + BodyRemovalPadTime( NPC ) )
- {
- if ( NPC->client->ps.eFlags & EF_NODRAW )
- {
- if ( !IIcarusInterface::GetIcarus()->IsRunning( NPC->m_iIcarusID ) )
- {
- NPC->e_ThinkFunc = thinkF_G_FreeEntity;
- NPC->nextthink = level.time + FRAMETIME;
- }
- }
- else
- {
- // Start the body effect first, then delay 400ms before ditching the corpse
- NPC_RemoveBodyEffect();
- //FIXME: keep it running through physics somehow?
- NPC->e_ThinkFunc = thinkF_NPC_RemoveBody;
- NPC->nextthink = level.time + FRAMETIME/2;
- // if ( NPC->client->playerTeam == TEAM_FORGE )
- // NPCInfo->timeOfDeath = level.time + FRAMETIME * 8;
- // else if ( NPC->client->playerTeam == TEAM_BOTS )
- class_t npc_class = NPC->client->NPC_class;
- // check for droids
- if ( npc_class == CLASS_SEEKER || npc_class == CLASS_REMOTE || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE ||
- npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 ||
- npc_class == CLASS_MARK2 || npc_class == CLASS_SENTRY )//npc_class == CLASS_PROTOCOL ||
- {
- NPC->client->ps.eFlags |= EF_NODRAW;
- NPCInfo->timeOfDeath = level.time + FRAMETIME * 8;
- }
- else
- {
- NPCInfo->timeOfDeath = level.time + FRAMETIME * 4;
- }
- }
- return;
- }
- }
- // If the player is on the ground and the resting position contents haven't been set yet...(BounceCount tracks the contents)
- if ( NPC->bounceCount < 0 && NPC->s.groundEntityNum >= 0 )
- {
- // if client is in a nodrop area, make him/her nodraw
- int contents = NPC->bounceCount = gi.pointcontents( NPC->currentOrigin, -1 );
- if ( ( contents & CONTENTS_NODROP ) )
- {
- NPC->client->ps.eFlags |= EF_NODRAW;
- }
- }
- CorpsePhysics( NPC );
- }
- /*
- ===============
- SetNPCGlobals
- local function to set globals used throughout the AI code
- ===============
- */
- void SetNPCGlobals( gentity_t *ent )
- {
- NPC = ent;
- NPCInfo = ent->NPC;
- client = ent->client;
- memset( &ucmd, 0, sizeof( usercmd_t ) );
- }
- gentity_t *_saved_NPC;
- gNPC_t *_saved_NPCInfo;
- gclient_t *_saved_client;
- usercmd_t _saved_ucmd;
- void SaveNPCGlobals()
- {
- _saved_NPC = NPC;
- _saved_NPCInfo = NPCInfo;
- _saved_client = client;
- memcpy( &_saved_ucmd, &ucmd, sizeof( usercmd_t ) );
- }
- void RestoreNPCGlobals()
- {
- NPC = _saved_NPC;
- NPCInfo = _saved_NPCInfo;
- client = _saved_client;
- memcpy( &ucmd, &_saved_ucmd, sizeof( usercmd_t ) );
- }
- //We MUST do this, other funcs were using NPC illegally when "self" wasn't the global NPC
- void ClearNPCGlobals( void )
- {
- NPC = NULL;
- NPCInfo = NULL;
- client = NULL;
- }
- //===============
- extern qboolean showBBoxes;
- vec3_t NPCDEBUG_RED = {1.0, 0.0, 0.0};
- vec3_t NPCDEBUG_GREEN = {0.0, 1.0, 0.0};
- vec3_t NPCDEBUG_BLUE = {0.0, 0.0, 1.0};
- vec3_t NPCDEBUG_LIGHT_BLUE = {0.3f, 0.7f, 1.0};
- extern void CG_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha );
- extern void CG_Line( vec3_t start, vec3_t end, vec3_t color, float alpha );
- extern void CG_Cylinder( vec3_t start, vec3_t end, float radius, vec3_t color );
- void NPC_ShowDebugInfo (void)
- {
- if ( showBBoxes )
- {
- gentity_t *found = NULL;
- vec3_t mins, maxs;
- //do player, too
- VectorAdd( player->currentOrigin, player->mins, mins );
- VectorAdd( player->currentOrigin, player->maxs, maxs );
- CG_Cube( mins, maxs, NPCDEBUG_RED, 0.25 );
- //do NPCs
- while( (found = G_Find( found, FOFS(classname), "NPC" ) ) != NULL )
- {
- if ( gi.inPVS( found->currentOrigin, g_entities[0].currentOrigin ) )
- {
- VectorAdd( found->currentOrigin, found->mins, mins );
- VectorAdd( found->currentOrigin, found->maxs, maxs );
- CG_Cube( mins, maxs, NPCDEBUG_RED, 0.25 );
- }
- }
- }
- }
- void NPC_ApplyScriptFlags (void)
- {
- if ( NPCInfo->scriptFlags & SCF_CROUCHED )
- {
- if ( NPCInfo->charmedTime > level.time && (ucmd.forwardmove || ucmd.rightmove) )
- {//ugh, if charmed and moving, ignore the crouched command
- }
- else
- {
- ucmd.upmove = -127;
- }
- }
- if(NPCInfo->scriptFlags & SCF_RUNNING)
- {
- ucmd.buttons &= ~BUTTON_WALKING;
- }
- else if(NPCInfo->scriptFlags & SCF_WALKING)
- {
- if ( NPCInfo->charmedTime > level.time && (ucmd.forwardmove || ucmd.rightmove) )
- {//ugh, if charmed and moving, ignore the walking command
- }
- else
- {
- ucmd.buttons |= BUTTON_WALKING;
- }
- }
- /*
- if(NPCInfo->scriptFlags & SCF_CAREFUL)
- {
- ucmd.buttons |= BUTTON_CAREFUL;
- }
- */
- if(NPCInfo->scriptFlags & SCF_LEAN_RIGHT)
- {
- ucmd.buttons |= BUTTON_USE;
- ucmd.rightmove = 127;
- ucmd.forwardmove = 0;
- ucmd.upmove = 0;
- }
- else if(NPCInfo->scriptFlags & SCF_LEAN_LEFT)
- {
- ucmd.buttons |= BUTTON_USE;
- ucmd.rightmove = -127;
- ucmd.forwardmove = 0;
- ucmd.upmove = 0;
- }
- if ( (NPCInfo->scriptFlags & SCF_ALT_FIRE) && (ucmd.buttons & BUTTON_ATTACK) )
- {//Use altfire instead
- ucmd.buttons |= BUTTON_ALT_ATTACK;
- }
- // only removes NPC when it's safe too (Player is out of PVS)
- if( NPCInfo->scriptFlags & SCF_SAFE_REMOVE )
- {
- // take from BSRemove
- if( !gi.inPVS( NPC->currentOrigin, g_entities[0].currentOrigin ) )//FIXME: use cg.vieworg?
- {
- G_UseTargets2( NPC, NPC, NPC->target3 );
- NPC->s.eFlags |= EF_NODRAW;
- NPC->svFlags &= ~SVF_NPC;
- NPC->s.eType = ET_INVISIBLE;
- NPC->contents = 0;
- NPC->health = 0;
- NPC->targetname = NULL;
- //Disappear in half a second
- NPC->e_ThinkFunc = thinkF_G_FreeEntity;
- NPC->nextthink = level.time + FRAMETIME;
- }//FIXME: else allow for out of FOV???
- }
- }
- extern qboolean JET_Flying( gentity_t *self );
- extern void JET_FlyStart( gentity_t *self );
- extern void JET_FlyStop( gentity_t *self );
- void NPC_HandleAIFlags (void)
- {
- // Update Guys With Jet Packs
- //----------------------------
- if (NPCInfo->scriptFlags & SCF_FLY_WITH_JET)
- {
- bool ShouldFly = !!(NPCInfo->aiFlags & NPCAI_FLY);
- bool IsFlying = !!(JET_Flying(NPC));
- bool IsInTheAir = (NPC->client->ps.groundEntityNum==ENTITYNUM_NONE);
- if (IsFlying)
- {
- // Don't Stop Flying Until Near The Ground
- //-----------------------------------------
- if (IsInTheAir)
- {
- vec3_t ground;
- trace_t trace;
- VectorCopy(NPC->currentOrigin, ground);
- ground[2] -= 60.0f;
- gi.trace(&trace, NPC->currentOrigin, 0, 0, ground, NPC->s.number, NPC->clipmask);
- IsInTheAir = (!trace.allsolid && !trace.startsolid && trace.fraction>0.9f);
- }
- // If Flying, Remember The Last Time
- //-----------------------------------
- if (IsInTheAir)
- {
- NPC->lastInAirTime = level.time;
- ShouldFly = true;
- }
- // Auto Turn Off Jet Pack After 1 Second On The Ground
- //-----------------------------------------------------
- else if (!ShouldFly && (level.time - NPC->lastInAirTime)>3000)
- {
- NPCInfo->aiFlags &= ~NPCAI_FLY;
- }
- }
- // If We Should Be Flying And Are Not, Start Er Up
- //-------------------------------------------------
- if (ShouldFly && !IsFlying)
- {
- JET_FlyStart(NPC); // EVENTUALLY, Remove All Other Calls
- }
- // Otherwise, If Needed, Shut It Off
- //-----------------------------------
- else if (!ShouldFly && IsFlying)
- {
- JET_FlyStop(NPC); // EVENTUALLY, Remove All Other Calls
- }
- }
- //FIXME: make these flags checks a function call like NPC_CheckAIFlagsAndTimers
- if ( NPCInfo->aiFlags & NPCAI_LOST )
- {//Print that you need help!
- //FIXME: shouldn't remove this just yet if cg_draw needs it
- NPCInfo->aiFlags &= ~NPCAI_LOST;
-
- /*
- if ( showWaypoints )
- {
- Quake3Game()->DebugPrint(WL_WARNING, "%s can't navigate to target %s (my wp: %d, goal wp: %d)\n", NPC->targetname, NPCInfo->goalEntity->targetname, NPC->waypoint, NPCInfo->goalEntity->waypoint );
- }
- */
- if ( NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy )
- {//We can't nav to our enemy
- //Drop enemy and see if we should search for him
- NPC_LostEnemyDecideChase();
- }
- }
- //been told to play a victory sound after a delay
- if ( NPCInfo->greetingDebounceTime && NPCInfo->greetingDebounceTime < level.time )
- {
- G_AddVoiceEvent( NPC, Q_irand(EV_VICTORY1, EV_VICTORY3), Q_irand( 2000, 4000 ) );
- NPCInfo->greetingDebounceTime = 0;
- }
- if ( NPCInfo->ffireCount > 0 )
- {
- if ( NPCInfo->ffireFadeDebounce < level.time )
- {
- NPCInfo->ffireCount--;
- //Com_Printf( "drop: %d < %d\n", NPCInfo->ffireCount, 3+((2-g_spskill->integer)*2) );
- NPCInfo->ffireFadeDebounce = level.time + 3000;
- }
- }
- }
- void NPC_AvoidWallsAndCliffs (void)
- {
- /*
- vec3_t forward, right, testPos, angles, mins;
- trace_t trace;
- float fwdDist, rtDist;
- //FIXME: set things like this forward dir once at the beginning
- //of a frame instead of over and over again
- if ( NPCInfo->aiFlags & NPCAI_NO_COLL_AVOID )
- {
- return;
- }
- if ( ucmd.upmove > 0 || NPC->client->ps.groundEntityNum == ENTITYNUM_NONE )
- {//Going to jump or in the air
- return;
- }
- if ( !ucmd.forwardmove && !ucmd.rightmove )
- {
- return;
- }
- if ( fabs( AngleDelta( NPC->currentAngles[YAW], NPCInfo->desiredYaw ) ) < 5.0 )//!ucmd.angles[YAW] )
- {//Not turning much, don't do this
- //NOTE: Should this not happen only if you're not turning AT ALL?
- // You could be turning slowly but moving fast, so that would
- // still let you walk right off a cliff...
- //NOTE: Or maybe it is a good idea to ALWAYS do this, regardless
- // of whether ot not we're turning? But why would we be walking
- // straight into a wall or off a cliff unless we really wanted to?
- return;
- }
- VectorCopy( NPC->mins, mins );
- mins[2] += STEPSIZE;
- angles[YAW] = NPC->client->ps.viewangles[YAW];//Add ucmd.angles[YAW]?
- AngleVectors( angles, forward, right, NULL );
- fwdDist = ((float)ucmd.forwardmove)/16.0f;
- rtDist = ((float)ucmd.rightmove)/16.0f;
- VectorMA( NPC->currentOrigin, fwdDist, forward, testPos );
- VectorMA( testPos, rtDist, right, testPos );
- gi.trace( &trace, NPC->currentOrigin, mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask );
- if ( trace.allsolid || trace.startsolid || trace.fraction < 1.0 )
- {//Going to bump into something, don't move, just turn
- ucmd.forwardmove = 0;
- ucmd.rightmove = 0;
- return;
- }
- VectorCopy(trace.endpos, testPos);
- testPos[2] -= 128;
- gi.trace( &trace, trace.endpos, mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask );
- if ( trace.allsolid || trace.startsolid || trace.fraction < 1.0 )
- {//Not going off a cliff
- return;
- }
- //going to fall at least 128, don't move, just turn... is this bad, though? What if we want them to drop off?
- ucmd.forwardmove = 0;
- ucmd.rightmove = 0;
- return;
- */
- }
- void NPC_CheckAttackScript(void)
- {
- if(!(ucmd.buttons & BUTTON_ATTACK))
- {
- return;
- }
- G_ActivateBehavior(NPC, BSET_ATTACK);
- }
- float NPC_MaxDistSquaredForWeapon (void);
- void NPC_CheckAttackHold(void)
- {
- vec3_t vec;
- // If they don't have an enemy they shouldn't hold their attack anim.
- if ( !NPC->enemy )
- {
- NPCInfo->attackHoldTime = 0;
- return;
- }
- //FIXME: need to tie this into AI somehow?
- VectorSubtract(NPC->enemy->currentOrigin, NPC->currentOrigin, vec);
- if( VectorLengthSquared(vec) > NPC_MaxDistSquaredForWeapon() )
- {
- NPCInfo->attackHoldTime = 0;
- }
- else if( NPCInfo->attackHoldTime && NPCInfo->attackHoldTime > level.time )
- {
- ucmd.buttons |= BUTTON_ATTACK;
- }
- else if ( ( NPCInfo->attackHold ) && ( ucmd.buttons & BUTTON_ATTACK ) )
- {
- NPCInfo->attackHoldTime = level.time + NPCInfo->attackHold;
- }
- else
- {
- NPCInfo->attackHoldTime = 0;
- }
- }
- /*
- void NPC_KeepCurrentFacing(void)
- Fills in a default ucmd to keep current angles facing
- */
- void NPC_KeepCurrentFacing(void)
- {
- if(!ucmd.angles[YAW])
- {
- ucmd.angles[YAW] = ANGLE2SHORT( client->ps.viewangles[YAW] ) - client->ps.delta_angles[YAW];
- }
- if(!ucmd.angles[PITCH])
- {
- ucmd.angles[PITCH] = ANGLE2SHORT( client->ps.viewangles[PITCH] ) - client->ps.delta_angles[PITCH];
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_Charmed
- -------------------------
- */
- void NPC_BehaviorSet_Charmed( int bState )
- {
- switch( bState )
- {
- case BS_FOLLOW_LEADER://# 40: Follow your leader and shoot any enemies you come across
- NPC_BSFollowLeader();
- break;
- case BS_REMOVE:
- NPC_BSRemove();
- break;
- case BS_SEARCH: //# 43: Using current waypoint as a base, search the immediate branches of waypoints for enemies
- NPC_BSSearch();
- break;
- case BS_WANDER: //# 46: Wander down random waypoint paths
- NPC_BSWander();
- break;
- case BS_FLEE:
- NPC_BSFlee();
- break;
- default:
- case BS_DEFAULT://whatever
- NPC_BSDefault();
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_Default
- -------------------------
- */
- void NPC_BehaviorSet_Default( int bState )
- {
- switch( bState )
- {
- case BS_ADVANCE_FIGHT://head toward captureGoal, shoot anything that gets in the way
- NPC_BSAdvanceFight ();
- break;
- case BS_SLEEP://Follow a path, looking for enemies
- NPC_BSSleep ();
- break;
- case BS_FOLLOW_LEADER://# 40: Follow your leader and shoot any enemies you come across
- NPC_BSFollowLeader();
- break;
- case BS_JUMP: //41: Face navgoal and jump to it.
- NPC_BSJump();
- break;
- case BS_REMOVE:
- NPC_BSRemove();
- break;
- case BS_SEARCH: //# 43: Using current waypoint as a base, search the immediate branches of waypoints for enemies
- NPC_BSSearch();
- break;
- case BS_NOCLIP:
- NPC_BSNoClip();
- break;
- case BS_WANDER: //# 46: Wander down random waypoint paths
- NPC_BSWander();
- break;
- case BS_FLEE:
- NPC_BSFlee();
- break;
- case BS_WAIT:
- NPC_BSWait();
- break;
- case BS_CINEMATIC:
- NPC_BSCinematic();
- break;
- default:
- case BS_DEFAULT://whatever
- NPC_BSDefault();
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_Interrogator
- -------------------------
- */
- void NPC_BehaviorSet_Interrogator( int bState )
- {
- switch( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_DEFAULT:
- NPC_BSInterrogator_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- void NPC_BSImperialProbe_Attack( void );
- void NPC_BSImperialProbe_Patrol( void );
- void NPC_BSImperialProbe_Wait(void);
- /*
- -------------------------
- NPC_BehaviorSet_ImperialProbe
- -------------------------
- */
- void NPC_BehaviorSet_ImperialProbe( int bState )
- {
- switch( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_DEFAULT:
- NPC_BSImperialProbe_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- void NPC_BSSeeker_Default( void );
- /*
- -------------------------
- NPC_BehaviorSet_Seeker
- -------------------------
- */
- void NPC_BehaviorSet_Seeker( int bState )
- {
- switch( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_DEFAULT:
- default:
- NPC_BSSeeker_Default();
- break;
- }
- }
- void NPC_BSRemote_Default( void );
- /*
- -------------------------
- NPC_BehaviorSet_Remote
- -------------------------
- */
- void NPC_BehaviorSet_Remote( int bState )
- {
- switch( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_DEFAULT:
- NPC_BSRemote_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- void NPC_BSSentry_Default( void );
- /*
- -------------------------
- NPC_BehaviorSet_Sentry
- -------------------------
- */
- void NPC_BehaviorSet_Sentry( int bState )
- {
- switch( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_DEFAULT:
- NPC_BSSentry_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_Grenadier
- -------------------------
- */
- void NPC_BehaviorSet_Grenadier( int bState )
- {
- switch( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_DEFAULT:
- NPC_BSGrenadier_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_Tusken
- -------------------------
- */
- void NPC_BehaviorSet_Tusken( int bState )
- {
- switch( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_DEFAULT:
- NPC_BSTusken_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_Sniper
- -------------------------
- */
- void NPC_BehaviorSet_Sniper( int bState )
- {
- switch( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_DEFAULT:
- NPC_BSSniper_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_Stormtrooper
- -------------------------
- */
- void NPC_BehaviorSet_Stormtrooper( int bState )
- {
- switch( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_DEFAULT:
- NPC_BSST_Default();
- break;
- case BS_INVESTIGATE:
- NPC_BSST_Investigate();
- break;
- case BS_SLEEP:
- NPC_BSST_Sleep();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_Jedi
- -------------------------
- */
- void NPC_BehaviorSet_Jedi( int bState )
- {
- switch( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_INVESTIGATE://WTF???!!
- case BS_DEFAULT:
- NPC_BSJedi_Default();
- break;
- case BS_FOLLOW_LEADER:
- NPC_BSJedi_FollowLeader();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- qboolean G_JediInNormalAI( gentity_t *ent )
- {//NOTE: should match above func's switch!
- //check our bState
- bState_t bState = G_CurrentBState( ent->NPC );
- switch ( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_INVESTIGATE:
- case BS_DEFAULT:
- case BS_FOLLOW_LEADER:
- return qtrue;
- break;
- }
- return qfalse;
- }
- /*
- -------------------------
- NPC_BehaviorSet_Droid
- -------------------------
- */
- void NPC_BehaviorSet_Droid( int bState )
- {
- switch( bState )
- {
- case BS_DEFAULT:
- case BS_STAND_GUARD:
- case BS_PATROL:
- NPC_BSDroid_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_Mark1
- -------------------------
- */
- void NPC_BehaviorSet_Mark1( int bState )
- {
- switch( bState )
- {
- case BS_DEFAULT:
- case BS_STAND_GUARD:
- case BS_PATROL:
- NPC_BSMark1_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_Mark2
- -------------------------
- */
- void NPC_BehaviorSet_Mark2( int bState )
- {
- switch( bState )
- {
- case BS_DEFAULT:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- NPC_BSMark2_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_ATST
- -------------------------
- */
- void NPC_BehaviorSet_ATST( int bState )
- {
- switch( bState )
- {
- case BS_DEFAULT:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- NPC_BSATST_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_MineMonster
- -------------------------
- */
- void NPC_BehaviorSet_MineMonster( int bState )
- {
- switch( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_DEFAULT:
- NPC_BSMineMonster_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_Howler
- -------------------------
- */
- void NPC_BehaviorSet_Howler( int bState )
- {
- switch( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_DEFAULT:
- NPC_BSHowler_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_Rancor
- -------------------------
- */
- void NPC_BehaviorSet_Rancor( int bState )
- {
- switch( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_DEFAULT:
- NPC_BSRancor_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_Wampa
- -------------------------
- */
- void NPC_BehaviorSet_Wampa( int bState )
- {
- switch( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_DEFAULT:
- NPC_BSWampa_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_SandCreature
- -------------------------
- */
- void NPC_BehaviorSet_SandCreature( int bState )
- {
- switch( bState )
- {
- case BS_STAND_GUARD:
- case BS_PATROL:
- case BS_STAND_AND_SHOOT:
- case BS_HUNT_AND_KILL:
- case BS_DEFAULT:
- NPC_BSSandCreature_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- /*
- -------------------------
- NPC_BehaviorSet_Droid
- -------------------------
- */
- // Added 01/21/03 by AReis.
- void NPC_BehaviorSet_Animal( int bState )
- {
- switch( bState )
- {
- case BS_DEFAULT:
- case BS_STAND_GUARD:
- case BS_PATROL:
- NPC_BSAnimal_Default();
- //NPC_BSDroid_Default();
- break;
- default:
- NPC_BehaviorSet_Default( bState );
- break;
- }
- }
- /*
- -------------------------
- NPC_RunBehavior
- -------------------------
- */
- extern void NPC_BSEmplaced( void );
- extern qboolean NPC_CheckSurrender( void );
- extern void NPC_BSRT_Default( void );
- extern void NPC_BSCivilian_Default( int bState );
- extern void NPC_BSSD_Default( void );
- extern void NPC_BehaviorSet_Trooper( int bState );
- extern bool NPC_IsTrooper( gentity_t *ent );
- extern bool Pilot_MasterUpdate();
- void NPC_RunBehavior( int team, int bState )
- {
- qboolean dontSetAim = qfalse;
- //
- if ( bState == BS_CINEMATIC )
- {
- NPC_BSCinematic();
- }
- else if ( (NPCInfo->scriptFlags&SCF_PILOT) && Pilot_MasterUpdate())
- {
- return;
- }
- else if ( NPC_JumpBackingUp() )
- {
- return;
- }
- else if ( !TIMER_Done(NPC, "DEMP2_StunTime"))
- {
- NPC_UpdateAngles(qtrue, qtrue);
- return;
- }
- else if ( NPC->client->ps.weapon == WP_EMPLACED_GUN )
- {
- NPC_BSEmplaced();
- G_CheckCharmed( NPC );
- return;
- }
- else if ( NPC->client->NPC_class == CLASS_HOWLER )
- {
- NPC_BehaviorSet_Howler( bState );
- return;
- }
- else if ( Jedi_CultistDestroyer( NPC ) )
- {
- NPC_BSJedi_Default();
- dontSetAim = qtrue;
- }
- else if ( NPC->client->NPC_class == CLASS_SABER_DROID )
- {//saber droid
- NPC_BSSD_Default();
- }
- else if ( NPC->client->ps.weapon == WP_SABER )
- {//jedi
- NPC_BehaviorSet_Jedi( bState );
- dontSetAim = qtrue;
- }
- else if ( NPC->client->NPC_class == CLASS_REBORN && NPC->client->ps.weapon == WP_MELEE )
- {//force-only reborn
- NPC_BehaviorSet_Jedi( bState );
- dontSetAim = qtrue;
- }
- else if ( NPC->client->NPC_class == CLASS_BOBAFETT )
- {
- Boba_Update();
- if (NPCInfo->surrenderTime)
- {
- Boba_Flee();
- }
- else
- {
- if (!Boba_Tactics())
- {
- if ( Boba_Flying( NPC ) )
- {
- NPC_BehaviorSet_Seeker(bState);
- }
- else
- {
- NPC_BehaviorSet_Jedi( bState );
- }
- }
- }
- dontSetAim = qtrue;
- }
- else if ( NPC->client->NPC_class == CLASS_ROCKETTROOPER )
- {//bounty hunter
- if ( RT_Flying( NPC ) || NPC->enemy != NULL )
- {
- NPC_BSRT_Default();
- }
- else
- {
- NPC_BehaviorSet_Stormtrooper( bState );
- }
- G_CheckCharmed( NPC );
- dontSetAim = qtrue;
- }
- else if ( NPC->client->NPC_class == CLASS_RANCOR )
- {
- NPC_BehaviorSet_Rancor( bState );
- }
- else if ( NPC->client->NPC_class == CLASS_SAND_CREATURE )
- {
- NPC_BehaviorSet_SandCreature( bState );
- }
- else if ( NPC->client->NPC_class == CLASS_WAMPA )
- {
- NPC_BehaviorSet_Wampa( bState );
- G_CheckCharmed( NPC );
- }
- else if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH )
- {//being forced to march
- NPC_BSDefault();
- }
- else if ( NPC->client->ps.weapon == WP_TUSKEN_RIFLE )
- {
- if ( (NPCInfo->scriptFlags & SCF_ALT_FIRE) )
- {
- NPC_BehaviorSet_Sniper( bState );
- G_CheckCharmed( NPC );
- return;
- }
- else
- {
- NPC_BehaviorSet_Tusken( bState );
- G_CheckCharmed( NPC );
- return;
- }
- }
- else if ( NPC->client->ps.weapon == WP_TUSKEN_STAFF )
- {
- NPC_BehaviorSet_Tusken( bState );
- G_CheckCharmed( NPC );
- return;
- }
- else if ( NPC->client->ps.weapon == WP_NOGHRI_STICK )
- {
- NPC_BehaviorSet_Stormtrooper( bState );
- G_CheckCharmed( NPC );
- }
- else
- {
- switch( team )
- {
-
- // case TEAM_SCAVENGERS:
- // case TEAM_IMPERIAL:
- // case TEAM_KLINGON:
- // case TEAM_HIROGEN:
- // case TEAM_MALON:
- // not sure if TEAM_ENEMY is appropriate here, I think I should be using NPC_class to check for behavior - dmv
- case TEAM_ENEMY:
- // special cases for enemy droids
- switch( NPC->client->NPC_class)
- {
- case CLASS_ATST:
- NPC_BehaviorSet_ATST( bState );
- return;
- case CLASS_PROBE:
- NPC_BehaviorSet_ImperialProbe(bState);
- return;
- case CLASS_REMOTE:
- NPC_BehaviorSet_Remote( bState );
- return;
- case CLASS_SENTRY:
- NPC_BehaviorSet_Sentry(bState);
- return;
- case CLASS_INTERROGATOR:
- NPC_BehaviorSet_Interrogator( bState );
- return;
- case CLASS_MINEMONSTER:
- NPC_BehaviorSet_MineMonster( bState );
- return;
- case CLASS_HOWLER:
- NPC_BehaviorSet_Howler( bState );
- return;
- case CLASS_RANCOR:
- NPC_BehaviorSet_Rancor( bState );
- return;
- case CLASS_SAND_CREATURE:
- NPC_BehaviorSet_SandCreature( bState );
- return;
- case CLASS_MARK1:
- NPC_BehaviorSet_Mark1( bState );
- return;
- case CLASS_MARK2:
- NPC_BehaviorSet_Mark2( bState );
- return;
- }
- if (NPC->client->NPC_class==CLASS_ASSASSIN_DROID)
- {
- BubbleShield_Update();
- }
- if (NPC_IsTrooper(NPC))
- {
- NPC_BehaviorSet_Trooper( bState);
- return;
- }
- if ( NPC->enemy && NPC->client->ps.weapon == WP_NONE && bState != BS_HUNT_AND_KILL && !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) )
- {//if in battle and have no weapon, run away, fixme: when in BS_HUNT_AND_KILL, they just stand there
- if ( bState != BS_FLEE )
- {
- NPC_StartFlee( NPC->enemy, NPC->enemy->currentOrigin, AEL_DANGER_GREAT, 5000, 10000 );
- }
- else
- {
- NPC_BSFlee();
- }
- return;
- }
- if ( NPC->client->ps.weapon == WP_SABER )
- {//special melee exception
- NPC_BehaviorSet_Default( bState );
- return;
- }
- if ( NPC->client->ps.weapon == WP_DISRUPTOR && (NPCInfo->scriptFlags & SCF_ALT_FIRE) )
- {//a sniper
- NPC_BehaviorSet_Sniper( bState );
- return;
- }
- if ( NPC->client->ps.weapon == WP_THERMAL
- || NPC->client->ps.weapon == WP_MELEE )//FIXME: separate AI for melee fighters
- {//a grenadier
- NPC_BehaviorSet_Grenadier( bState );
- return;
- }
- if ( NPC_CheckSurrender() )
- {
- return;
- }
- NPC_BehaviorSet_Stormtrooper( bState );
- break;
- case TEAM_NEUTRAL:
- // special cases for enemy droids
- if ( NPC->client->NPC_class == CLASS_PROTOCOL )
- {
- NPC_BehaviorSet_Default(bState);
- }
- else if ( NPC->client->NPC_class == CLASS_UGNAUGHT
- || NPC->client->NPC_class == CLASS_JAWA )
- {//others, too?
- NPC_BSCivilian_Default( bState );
- return;
- }
- // Add special vehicle behavior here.
- else if ( NPC->client->NPC_class == CLASS_VEHICLE )
- {
- Vehicle_t *pVehicle = NPC->m_pVehicle;
- if ( !pVehicle->m_pPilot && pVehicle->m_iBoarding==0 )
- {
- if (pVehicle->m_pVehicleInfo->type == VH_ANIMAL)
- {
- NPC_BehaviorSet_Animal( bState );
- }
- // TODO: The only other case were we want a vehicle to do something specifically is
- // perhaps in multiplayer where we want the shuttle to be able to lift off when not
- // occupied and in a landing zone.
- }
- }
- else
- {
- // Just one of the average droids
- NPC_BehaviorSet_Droid( bState );
- }
- break;
- default:
- if ( NPC->client->NPC_class == CLASS_SEEKER )
- {
- NPC_BehaviorSet_Seeker(bState);
- }
- else
- {
- if ( NPCInfo->charmedTime > level.time )
- {
- NPC_BehaviorSet_Charmed( bState );
- }
- else
- {
- NPC_BehaviorSet_Default( bState );
- }
- G_CheckCharmed( NPC );
- dontSetAim = qtrue;
- }
- break;
- }
- }
- }
- static bState_t G_CurrentBState( gNPC_t *gNPC )
- {
- if ( gNPC->tempBehavior != BS_DEFAULT )
- {//Overrides normal behavior until cleared
- return (gNPC->tempBehavior);
- }
- if( gNPC->behaviorState == BS_DEFAULT )
- {
- gNPC->behaviorState = gNPC->defaultBehavior;
- }
- return (gNPC->behaviorState);
- }
- /*
- ===============
- NPC_ExecuteBState
- MCG
- NPC Behavior state thinking
- ===============
- */
- void NPC_ExecuteBState ( gentity_t *self)//, int msec )
- {
- bState_t bState;
- NPC_HandleAIFlags();
- //FIXME: these next three bits could be a function call, some sort of setup/cleanup func
- //Lookmode must be reset every think cycle
- if(NPC->delayScriptTime && NPC->delayScriptTime <= level.time)
- {
- G_ActivateBehavior( NPC, BSET_DELAYED);
- NPC->delayScriptTime = 0;
- }
- //Clear this and let bState set it itself, so it automatically handles changing bStates... but we need a set bState wrapper func
- NPCInfo->combatMove = qfalse;
- //Execute our bState
- bState = G_CurrentBState( NPCInfo );
- //Pick the proper bstate for us and run it
- NPC_RunBehavior( self->client->playerTeam, bState );
-
- // if(bState != BS_POINT_COMBAT && NPCInfo->combatPoint != -1)
- // {
- //level.combatPoints[NPCInfo->combatPoint].occupied = qfalse;
- //NPCInfo->combatPoint = -1;
- // }
- //Here we need to see what the scripted stuff told us to do
- //Only process snapshot if independant and in combat mode- this would pick enemies and go after needed items
- // ProcessSnapshot();
- //Ignore my needs if I'm under script control- this would set needs for items
- // CheckSelf();
- //Back to normal? All decisions made?
-
- //FIXME: don't walk off ledges unless we can get to our goal faster that way, or that's our goal's surface
- //NPCPredict();
- if ( NPC->enemy )
- {
- if ( !NPC->enemy->inuse )
- {//just in case bState doesn't catch this
- G_ClearEnemy( NPC );
- }
- }
- if ( NPC->client->ps.saberLockTime && NPC->client->ps.saberLockEnemy != ENTITYNUM_NONE )
- {
- NPC_SetLookTarget( NPC, NPC->client->ps.saberLockEnemy, level.time+1000 );
- }
- else if ( !NPC_CheckLookTarget( NPC ) )
- {
- if ( NPC->enemy )
- {
- NPC_SetLookTarget( NPC, NPC->enemy->s.number, 0 );
- }
- }
- if ( NPC->enemy )
- {
- if(NPC->enemy->flags & FL_DONT_SHOOT)
- {
- ucmd.buttons &= ~BUTTON_ATTACK;
- ucmd.buttons &= ~BUTTON_ALT_ATTACK;
- }
- else if ( NPC->client->playerTeam != TEAM_ENEMY //not an enemy
- && (NPC->client->playerTeam != TEAM_FREE || (NPC->client->NPC_class == CLASS_TUSKEN && Q_irand( 0, 4 )))//not a rampaging creature or I'm a tusken and I feel generous (temporarily)
- && NPC->enemy->NPC
- && (NPC->enemy->NPC->surrenderTime > level.time || (NPC->enemy->NPC->scriptFlags&SCF_FORCED_MARCH)) )
- {//don't shoot someone who's surrendering if you're a good guy
- ucmd.buttons &= ~BUTTON_ATTACK;
- ucmd.buttons &= ~BUTTON_ALT_ATTACK;
- }
- if(client->ps.weaponstate == WEAPON_IDLE)
- {
- client->ps.weaponstate = WEAPON_READY;
- }
- }
- else
- {
- if(client->ps.weaponstate == WEAPON_READY)
- {
- client->ps.weaponstate = WEAPON_IDLE;
- }
- }
- if(!(ucmd.buttons & BUTTON_ATTACK) && NPC->attackDebounceTime > level.time)
- {//We just shot but aren't still shooting, so hold the gun up for a while
- if(client->ps.weapon == WP_SABER )
- {//One-handed
- NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY1,SETANIM_FLAG_NORMAL);
- }
- else if(client->ps.weapon == WP_BRYAR_PISTOL)
- {//Sniper pose
- NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL);
- }
- /*//FIXME: What's the proper solution here?
- else
- {//heavy weapon
- NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL);
- }
- */
- }
- NPC_CheckAttackHold();
- NPC_ApplyScriptFlags();
-
- //cliff and wall avoidance
- NPC_AvoidWallsAndCliffs();
- // run the bot through the server like it was a real client
- //=== Save the ucmd for the second no-think Pmove ============================
- ucmd.serverTime = level.time - 50;
- memcpy( &NPCInfo->last_ucmd, &ucmd, sizeof( usercmd_t ) );
- if ( !NPCInfo->attackHoldTime )
- {
- NPCInfo->last_ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS);//so we don't fire twice in one think
- }
- //============================================================================
- NPC_CheckAttackScript();
- NPC_KeepCurrentFacing();
- if ( !NPC->next_roff_time || NPC->next_roff_time < level.time )
- {//If we were following a roff, we don't do normal pmoves.
- ClientThink( NPC->s.number, &ucmd );
- }
- else
- {
- NPC_ApplyRoff();
- }
- // end of thinking cleanup
- NPCInfo->touchedByPlayer = NULL;
- NPC_CheckPlayerAim();
- NPC_CheckAllClear();
-
- /*if( ucmd.forwardmove || ucmd.rightmove )
- {
- int i, la = -1, ta = -1;
- for(i = 0; i < MAX_ANIMATIONS; i++)
- {
- if( NPC->client->ps.legsAnim == i )
- {
- la = i;
- }
- if( NPC->client->ps.torsoAnim == i )
- {
- ta = i;
- }
-
- if(la != -1 && ta != -1)
- {
- break;
- }
- }
- if(la != -1 && ta != -1)
- {//FIXME: should never play same frame twice or restart an anim before finishing it
- gi.Printf("LegsAnim: %s(%d) TorsoAnim: %s(%d)\n", animTable[la].name, NPC->renderInfo.legsFrame, animTable[ta].name, NPC->client->renderInfo.torsoFrame);
- }
- }*/
- }
- void NPC_CheckInSolid(void)
- {
- trace_t trace;
- vec3_t point;
- VectorCopy(NPC->currentOrigin, point);
- point[2] -= 0.25;
- gi.trace(&trace, NPC->currentOrigin, NPC->mins, NPC->maxs, point, NPC->s.number, NPC->clipmask);
- if(!trace.startsolid && !trace.allsolid)
- {
- VectorCopy(NPC->currentOrigin, NPCInfo->lastClearOrigin);
- }
- else
- {
- if(VectorLengthSquared(NPCInfo->lastClearOrigin))
- {
- // gi.Printf("%s stuck in solid at %s: fixing...\n", NPC->script_targetname, vtos(NPC->currentOrigin));
- G_SetOrigin(NPC, NPCInfo->lastClearOrigin);
- gi.linkentity(NPC);
- }
- }
- }
- /*
- ===============
- NPC_Think
- Main NPC AI - called once per frame
- ===============
- */
- #if AI_TIMERS
- extern int AITime;
- #endif// AI_TIMERS
- void NPC_Think ( gentity_t *self)//, int msec )
- {
- vec3_t oldMoveDir;
- self->nextthink = level.time + FRAMETIME/2;
- SetNPCGlobals( self );
- memset( &ucmd, 0, sizeof( ucmd ) );
- VectorCopy( self->client->ps.moveDir, oldMoveDir );
- VectorClear( self->client->ps.moveDir );
- // see if NPC ai is frozen
- if ( debugNPCFreeze->integer || (NPC->svFlags&SVF_ICARUS_FREEZE) )
- {
- NPC_UpdateAngles( qtrue, qtrue );
- ClientThink(self->s.number, &ucmd);
- VectorCopy(self->s.origin, self->s.origin2 );
- return;
- }
- if(!self || !self->NPC || !self->client)
- {
- return;
- }
- // dead NPCs have a special think, don't run scripts (for now)
- //FIXME: this breaks deathscripts
- if ( self->health <= 0 )
- {
- DeadThink();
- if ( NPCInfo->nextBStateThink <= level.time )
- {
- if( self->m_iIcarusID != IIcarusInterface::ICARUS_INVALID && !stop_icarus )
- {
- IIcarusInterface::GetIcarus()->Update( self->m_iIcarusID );
- }
- }
- return;
- }
- // TODO! Tauntaun's (and other creature vehicles?) think, we'll need to make an exception here to allow that.
- if ( self->client
- && self->client->NPC_class == CLASS_VEHICLE
- && self->NPC_type
- && ( !self->m_pVehicle->m_pVehicleInfo->Inhabited( self->m_pVehicle ) ) )
- {//empty swoop logic
- if ( self->owner )
- {//still have attached owner, check and see if can forget him (so he can use me later)
- vec3_t dir2owner;
- VectorSubtract( self->owner->currentOrigin, self->currentOrigin, dir2owner );
- gentity_t *oldOwner = self->owner;
- self->owner = NULL;//clear here for that SpotWouldTelefrag check...?
- if ( VectorLengthSquared( dir2owner ) > 128*128
- || !(self->clipmask&oldOwner->clipmask)
- || (DotProduct( self->client->ps.velocity, oldOwner->client->ps.velocity ) < -200.0f &&!G_BoundsOverlap( self->absmin, self->absmin, oldOwner->absmin, oldOwner->absmax )) )
- {//all clear, become solid to our owner now
- gi.linkentity( self );
- }
- else
- {//blocked, retain owner
- self->owner = oldOwner;
- }
- }
- }
- if ( player->client->ps.viewEntity == self->s.number )
- {//being controlled by player
- if ( self->client )
- {//make the noises
- if ( TIMER_Done( self, "patrolNoise" ) && !Q_irand( 0, 20 ) )
- {
- switch( self->client->NPC_class )
- {
- case CLASS_R2D2: // droid
- G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/r2d2/misc/r2d2talk0%d.wav",Q_irand(1, 3)) );
- break;
- case CLASS_R5D2: // droid
- G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/r5d2/misc/r5talk%d.wav",Q_irand(1, 4)) );
- break;
- case CLASS_PROBE: // droid
- G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d.wav",Q_irand(1, 3)) );
- break;
- case CLASS_MOUSE: // droid
- G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/mouse/misc/mousego%d.wav",Q_irand(1, 3)) );
- break;
- case CLASS_GONK: // droid
- G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/gonk/misc/gonktalk%d.wav",Q_irand(1, 2)) );
- break;
- }
- TIMER_Set( self, "patrolNoise", Q_irand( 2000, 4000 ) );
- }
- }
- //FIXME: might want to at least make sounds or something?
- //NPC_UpdateAngles(qtrue, qtrue);
- //Which ucmd should we send? Does it matter, since it gets overridden anyway?
- NPCInfo->last_ucmd.serverTime = level.time - 50;
- ClientThink( NPC->s.number, &ucmd );
- VectorCopy(self->s.origin, self->s.origin2 );
- return;
- }
- if ( NPCInfo->nextBStateThink <= level.time )
- {
- #if AI_TIMERS
- int startTime = GetTime(0);
- #endif// AI_TIMERS
- if ( NPC->s.eType != ET_PLAYER )
- {//Something drastic happened in our script
- return;
- }
- if ( NPC->s.weapon == WP_SABER && g_spskill->integer >= 2 && NPCInfo->rank > RANK_LT_JG )
- {//Jedi think faster on hard difficulty, except low-rank (reborn)
- NPCInfo->nextBStateThink = level.time + FRAMETIME/2;
- }
- else
- {//Maybe even 200 ms?
- NPCInfo->nextBStateThink = level.time + FRAMETIME;
- }
- //nextthink is set before this so something in here can override it
- NPC_ExecuteBState( self );
- #if AI_TIMERS
- int addTime = GetTime( startTime );
- if ( addTime > 50 )
- {
- gi.Printf( S_COLOR_RED"ERROR: NPC number %d, %s %s at %s, weaponnum: %d, using %d of AI time!!!\n", NPC->s.number, NPC->NPC_type, NPC->targetname, vtos(NPC->currentOrigin), NPC->s.weapon, addTime );
- }
- AITime += addTime;
- #endif// AI_TIMERS
- }
- else
- {
- if ( NPC->client
- && NPC->client->NPC_class == CLASS_ROCKETTROOPER
- && (NPC->client->ps.eFlags&EF_FORCE_GRIPPED)
- && NPC->client->moveType == MT_FLYSWIM
- && NPC->client->ps.groundEntityNum == ENTITYNUM_NONE )
- {//reduce velocity
- VectorScale( NPC->client->ps.velocity, 0.75f, NPC->client->ps.velocity );
- }
- VectorCopy( oldMoveDir, self->client->ps.moveDir );
- //or use client->pers.lastCommand?
- NPCInfo->last_ucmd.serverTime = level.time - 50;
- if ( !NPC->next_roff_time || NPC->next_roff_time < level.time )
- {//If we were following a roff, we don't do normal pmoves.
- //FIXME: firing angles (no aim offset) or regular angles?
- NPC_UpdateAngles(qtrue, qtrue);
- memcpy( &ucmd, &NPCInfo->last_ucmd, sizeof( usercmd_t ) );
- ClientThink(NPC->s.number, &ucmd);
- }
- else
- {
- NPC_ApplyRoff();
- }
- VectorCopy(self->s.origin, self->s.origin2 );
- }
- //must update icarus *every* frame because of certain animation completions in the pmove stuff that can leave a 50ms gap between ICARUS animation commands
- if( self->m_iIcarusID != IIcarusInterface::ICARUS_INVALID && !stop_icarus )
- {
- IIcarusInterface::GetIcarus()->Update( self->m_iIcarusID );
- }
- }
- void NPC_InitAI ( void )
- {
- debugNPCAI = gi.cvar ( "d_npcai", "0", CVAR_CHEAT );
- debugNPCFreeze = gi.cvar ( "d_npcfreeze", "0", CVAR_CHEAT);
- d_JediAI = gi.cvar ( "d_JediAI", "0", CVAR_CHEAT );
- d_noGroupAI = gi.cvar ( "d_noGroupAI", "0", CVAR_CHEAT );
- d_asynchronousGroupAI = gi.cvar ( "d_asynchronousGroupAI", "1", CVAR_CHEAT );
- //0 = never (BORING)
- //1 = kyle only
- //2 = kyle and last enemy jedi
- //3 = kyle and any enemy jedi
- //4 = kyle and last enemy in a group, special kicks
- //5 = kyle and any enemy
- //6 = also when kyle takes pain or enemy jedi dodges player saber swing or does an acrobatic evasion
- // NOTE : I also create this in UI_Init()
- d_slowmodeath = gi.cvar ( "d_slowmodeath", "3", CVAR_ARCHIVE );//save this setting
- d_saberCombat = gi.cvar ( "d_saberCombat", "0", CVAR_CHEAT );
- }
- /*
- ==================================
- void NPC_InitAnimTable( void )
- Need to initialize this table.
- If someone tried to play an anim
- before table is filled in with
- values, causes tasks that wait for
- anim completion to never finish.
- (frameLerp of 0 * numFrames of 0 = 0)
- ==================================
- */
- void NPC_InitAnimTable( void )
- {
- for ( int i = 0; i < MAX_ANIM_FILES; i++ )
- {
- for ( int j = 0; j < MAX_ANIMATIONS; j++ )
- {
- level.knownAnimFileSets[i].animations[j].firstFrame = 0;
- level.knownAnimFileSets[i].animations[j].frameLerp = 100;
- // level.knownAnimFileSets[i].animations[j].initialLerp = 100;
- level.knownAnimFileSets[i].animations[j].numFrames = 0;
- }
- }
- }
- extern int G_ParseAnimFileSet( const char *skeletonName, const char *modelName=0);
- void NPC_InitGame( void )
- {
- // globals.NPCs = (gNPC_t *) gi.TagMalloc(game.maxclients * sizeof(game.bots[0]), TAG_GAME);
- debugNPCName = gi.cvar ( "d_npc", "", 0 );
- NPC_LoadParms();
- NPC_InitAI();
- NPC_InitAnimTable();
- G_ParseAnimFileSet("_humanoid"); //GET THIS CACHED NOW BEFORE CGAME STARTS
- /*
- ResetTeamCounters();
- for ( int team = TEAM_FREE; team < TEAM_NUM_TEAMS; team++ )
- {
- teamLastEnemyTime[team] = -10000;
- }
- */
- }
- void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend)
- { // FIXME : once torsoAnim and legsAnim are in the same structure for NCP and Players
- // rename PM_SETAnimFinal to PM_SetAnim and have both NCP and Players call PM_SetAnim
- if ( !ent )
- {
- return;
- }
- if ( ent->health > 0 )
- {//don't lock anims if the guy is dead
- if ( ent->client->ps.torsoAnimTimer
- && PM_LockedAnim( ent->client->ps.torsoAnim )
- && !PM_LockedAnim( anim ) )
- {//nothing can override these special anims
- setAnimParts &= ~SETANIM_TORSO;
- }
- if ( ent->client->ps.legsAnimTimer
- && PM_LockedAnim( ent->client->ps.legsAnim )
- && !PM_LockedAnim( anim ) )
- {//nothing can override these special anims
- setAnimParts &= ~SETANIM_LEGS;
- }
- }
- if ( !setAnimParts )
- {
- return;
- }
- if(ent->client)
- {//Players, NPCs
- if (setAnimFlags&SETANIM_FLAG_OVERRIDE)
- {
- if (setAnimParts & SETANIM_TORSO)
- {
- if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->client->ps.torsoAnim != anim )
- {
- PM_SetTorsoAnimTimer( ent, &ent->client->ps.torsoAnimTimer, 0 );
- }
- }
- if (setAnimParts & SETANIM_LEGS)
- {
- if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->client->ps.legsAnim != anim )
- {
- PM_SetLegsAnimTimer( ent, &ent->client->ps.legsAnimTimer, 0 );
- }
- }
- }
- PM_SetAnimFinal(&ent->client->ps.torsoAnim,&ent->client->ps.legsAnim,setAnimParts,anim,setAnimFlags,
- &ent->client->ps.torsoAnimTimer,&ent->client->ps.legsAnimTimer,ent, iBlend );
- }
- else
- {//bodies, etc.
- if (setAnimFlags&SETANIM_FLAG_OVERRIDE)
- {
- if (setAnimParts & SETANIM_TORSO)
- {
- if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->s.torsoAnim != anim )
- {
- PM_SetTorsoAnimTimer( ent, &ent->s.torsoAnimTimer, 0 );
- }
- }
- if (setAnimParts & SETANIM_LEGS)
- {
- if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->s.legsAnim != anim )
- {
- PM_SetLegsAnimTimer( ent, &ent->s.legsAnimTimer, 0 );
- }
- }
- }
- PM_SetAnimFinal(&ent->s.torsoAnim,&ent->s.legsAnim,setAnimParts,anim,setAnimFlags,
- &ent->s.torsoAnimTimer,&ent->s.legsAnimTimer,ent);
- }
- }
|