void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) { int respawn = 0; if (!other->client) return; if (other->health < 1) return; // dead people can't pickup if ( other->client->ps.pm_time > 0 ) { //cant pick up when out of control return; } // NPCs can pick it up if ((ent->spawnflags & ITMSF_ALLOWNPC) && (!other->s.number)) { return; } // Players cannot pick it up if ( (ent->spawnflags & ITMSF_NOPLAYER) && (other->s.number) ) { return; } if ( ent->noDamageTeam != TEAM_FREE && other->client->playerTeam != ent->noDamageTeam ) { //only one team can pick it up return; } if ( !G_CanPickUpWeapons( other ) ) { //FIXME: some flag would be better //droids can't pick up items/weapons! return; } //FIXME: need to make them run toward a dropped weapon when fleeing without one? //FIXME: need to make them come out of flee mode when pick up their old weapon? if ( CheckItemCanBePickedUpByNPC( ent, other ) ) { if ( other->NPC && other->NPC->goalEntity && other->NPC->goalEntity == ent ) { //they were running to pick me up, they did, so clear goal other->NPC->goalEntity = NULL; other->NPC->squadState = SQUAD_STAND_AND_SHOOT; NPCInfo->tempBehavior = BS_DEFAULT; TIMER_Set(other, "flee", -1); } else { return; } } else if ( !(ent->spawnflags & ITMSF_ALLOWNPC) ) { // NPCs cannot pick it up if ( other->s.number != 0 ) { // Not the player? return; } } // the same pickup rules are used for client side and server side if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps ) ) { return; } if ( other->client ) { if ( (other->client->ps.eFlags&EF_FORCE_GRIPPED) || (other->client->ps.eFlags&EF_FORCE_DRAINED) ) { //can't pick up anything while being gripped return; } if ( PM_InKnockDown( &other->client->ps ) && !PM_InGetUp( &other->client->ps ) ) { //can't pick up while in a knockdown return; } } if (!ent->item) { //not an item! gi.Printf( "Touch_Item: %s is not an item!\n", ent->classname); return; } if ( ent->item->giType == IT_WEAPON && ent->item->giTag == WP_SABER ) { //a saber if ( ent->delay > level.time ) { //just picked it up, don't pick up again right away return; } } if ( other->s.number < MAX_CLIENTS && (ent->spawnflags&ITMSF_USEPICKUP) ) { //only if player is holing use button if ( !(other->client->usercmd.buttons&BUTTON_USE) ) { //not holding use? return; } } qboolean bHadWeapon = qfalse; // call the item-specific pickup function switch( ent->item->giType ) { case IT_WEAPON: if ( other->NPC && other->s.weapon == WP_NONE ) { //Make them duck and sit here for a few seconds int pickUpTime = Q_irand( 1000, 3000 ); TIMER_Set( other, "duck", pickUpTime ); TIMER_Set( other, "roamTime", pickUpTime ); TIMER_Set( other, "stick", pickUpTime ); TIMER_Set( other, "verifyCP", pickUpTime ); TIMER_Set( other, "attackDelay", 600 ); respawn = 0; } if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) ) { bHadWeapon = qtrue; } respawn = Pickup_Weapon(ent, other); break; case IT_AMMO: respawn = Pickup_Ammo(ent, other); break; case IT_ARMOR: respawn = Pickup_Armor(ent, other); break; case IT_HEALTH: respawn = Pickup_Health(ent, other); break; case IT_HOLDABLE: respawn = Pickup_Holdable(ent, other); break; case IT_BATTERY: respawn = Pickup_Battery( ent, other ); break; case IT_HOLOCRON: respawn = Pickup_Holocron( ent, other ); break; default: return; } if ( !respawn ) { return; } // play the normal pickup sound if ( !other->s.number && g_timescale->value < 1.0f ) { //SIGH... with timescale on, you lose events left and right extern void CG_ItemPickup( int itemNum, qboolean bHadItem ); // but we're SP so we'll cheat cgi_S_StartSound( NULL, other->s.number, CHAN_AUTO, cgi_S_RegisterSound( ent->item->pickup_sound ) ); // show icon and name on status bar CG_ItemPickup( ent->s.modelindex, bHadWeapon ); } else { if ( bHadWeapon ) { G_AddEvent( other, EV_ITEM_PICKUP, -ent->s.modelindex ); } else { G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex ); } } // fire item targets G_UseTargets (ent, other); if ( ent->item->giType == IT_WEAPON && ent->item->giTag == WP_SABER ) { //a saber that was picked up if ( ent->count < 0 ) { //infinite supply ent->delay = level.time + 500; return; } ent->count--; if ( ent->count > 0 ) { //still have more to pick up ent->delay = level.time + 500; return; } } // wait of -1 will not respawn // if ( ent->wait == -1 ) { //why not just remove me? G_FreeEntity( ent ); /* //NOTE: used to do this: (for respawning?) ent->svFlags |= SVF_NOCLIENT; ent->s.eFlags |= EF_NODRAW; ent->contents = 0; ent->unlinkAfterEvent = qtrue; */ return; } }
void NPC_ChoosePainAnimation( gentity_t *self, gentity_t *other, const vec3_t point, int damage, int mod, int hitLoc, int voiceEvent = -1 ) { //If we've already taken pain, then don't take it again if ( level.time < self->painDebounceTime && mod != MOD_ELECTROCUTE && mod != MOD_MELEE ) {//FIXME: if hit while recoving from losing a saber lock, we should still play a pain anim? return; } int pain_anim = -1; float pain_chance; if ( self->s.weapon == WP_THERMAL && self->client->fireDelay > 0 ) {//don't interrupt thermal throwing anim return; } else if (self->client->ps.powerups[PW_GALAK_SHIELD]) { return; } else if ( self->client->NPC_class == CLASS_GALAKMECH ) { if ( hitLoc == HL_GENERIC1 ) {//hit the antenna! pain_chance = 1.0f; self->s.powerups |= ( 1 << PW_SHOCKED ); self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 ); } else if ( self->client->ps.powerups[PW_GALAK_SHIELD] ) {//shield up return; } else if ( self->health > 200 && damage < 100 ) {//have a *lot* of health pain_chance = 0.05f; } else {//the lower my health and greater the damage, the more likely I am to play a pain anim pain_chance = (200.0f-self->health)/100.0f + damage/50.0f; } } else if ( self->client && self->client->playerTeam == TEAM_PLAYER && other && !other->s.number ) {//ally shot by player always complains pain_chance = 1.1f; } else { if ( other && (other->s.weapon == WP_SABER || mod == MOD_ELECTROCUTE || mod == MOD_CRUSH/*FIXME:MOD_FORCE_GRIP*/) ) { if ( self->client->ps.weapon == WP_SABER && other->s.number < MAX_CLIENTS ) {//hmm, shouldn't *always* react to damage from player if I have a saber pain_chance = 1.05f - ((self->NPC->rank)/(float)RANK_CAPTAIN); } else { pain_chance = 1.0f;//always take pain from saber } } else if ( mod == MOD_GAS ) { pain_chance = 1.0f; } else if ( mod == MOD_MELEE ) {//higher in rank (skill) we are, less likely we are to be fazed by a punch pain_chance = 1.0f - ((RANK_CAPTAIN-self->NPC->rank)/(float)RANK_CAPTAIN); } else if ( self->client->NPC_class == CLASS_PROTOCOL ) { pain_chance = 1.0f; } else { pain_chance = NPC_GetPainChance( self, damage ); } if ( self->client->NPC_class == CLASS_DESANN ) { pain_chance *= 0.5f; } } //See if we're going to flinch if ( random() < pain_chance ) { //Pick and play our animation if ( (self->client->ps.eFlags&EF_FORCE_GRIPPED) ) { G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 ); } else if ( mod == MOD_GAS ) { //SIGH... because our choke sounds are inappropriately long, I have to debounce them in code! if ( TIMER_Done( self, "gasChokeSound" ) ) { TIMER_Set( self, "gasChokeSound", Q_irand( 1000, 2000 ) ); G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 ); } } else if ( (self->client->ps.eFlags&EF_FORCE_DRAINED) ) { NPC_SetPainEvent( self ); } else {//not being force-gripped or force-drained if ( G_CheckForStrongAttackMomentum( self ) || PM_SpinningAnim( self->client->ps.legsAnim ) || PM_SaberInSpecialAttack( self->client->ps.torsoAnim ) || PM_InKnockDown( &self->client->ps ) || PM_RollingAnim( self->client->ps.legsAnim ) || (PM_FlippingAnim( self->client->ps.legsAnim )&&!PM_InCartwheel( self->client->ps.legsAnim )) ) {//strong attacks, rolls, knockdowns, flips and spins cannot be interrupted by pain return; } else {//play an anim if ( self->client->NPC_class == CLASS_GALAKMECH ) {//only has 1 for now //FIXME: never plays this, it seems... pain_anim = BOTH_PAIN1; } else if ( mod == MOD_MELEE ) { pain_anim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 ); } else if ( self->s.weapon == WP_SABER ) {//temp HACK: these are the only 2 pain anims that look good when holding a saber pain_anim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 ); } else if ( mod != MOD_ELECTROCUTE ) { pain_anim = G_PickPainAnim( self, point, damage, hitLoc ); } if ( pain_anim == -1 ) { pain_anim = PM_PickAnim( self, BOTH_PAIN1, BOTH_PAIN18 ); } self->client->ps.saberAnimLevel = SS_FAST;//next attack must be a quick attack self->client->ps.saberMove = LS_READY;//don't finish whatever saber move you may have been in int parts = SETANIM_BOTH; if ( PM_CrouchAnim( self->client->ps.legsAnim ) || PM_InCartwheel( self->client->ps.legsAnim ) ) { parts = SETANIM_LEGS; } self->NPC->aiFlags &= ~NPCAI_KNEEL; NPC_SetAnim( self, parts, pain_anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } if ( voiceEvent != -1 ) { G_AddVoiceEvent( self, voiceEvent, Q_irand( 2000, 4000 ) ); } else { NPC_SetPainEvent( self ); } } //Setup the timing for it if ( mod == MOD_ELECTROCUTE ) { self->painDebounceTime = level.time + 4000; } self->painDebounceTime = level.time + PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t) pain_anim ); self->client->fireDelay = 0; } }
//void NPC_Wampa_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) void NPC_Wampa_Pain( gentity_t *self, gentity_t *attacker, int damage ) { qboolean hitByWampa = qfalse; if ( attacker&&attacker->client&&attacker->client->NPC_class==CLASS_WAMPA ) { hitByWampa = qtrue; } if ( attacker && attacker->inuse && attacker != self->enemy && !(attacker->flags&FL_NOTARGET) ) { if ( (!attacker->s.number&&!Q_irand(0,3)) || !self->enemy || self->enemy->health == 0 || (self->enemy->client&&self->enemy->client->NPC_class == CLASS_WAMPA) || (!Q_irand(0, 4 ) && DistanceSquared( attacker->r.currentOrigin, self->r.currentOrigin ) < DistanceSquared( self->enemy->r.currentOrigin, self->r.currentOrigin )) ) {//if my enemy is dead (or attacked by player) and I'm not still holding/eating someone, turn on the attacker //FIXME: if can't nav to my enemy, take this guy if I can nav to him G_SetEnemy( self, attacker ); TIMER_Set( self, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); if ( hitByWampa ) {//stay mad at this Wampa for 2-5 secs before looking for attacker enemies TIMER_Set( self, "wampaInfight", Q_irand( 2000, 5000 ) ); } } } if ( (hitByWampa|| Q_irand( 0, 100 ) < damage )//hit by wampa, hit while holding live victim, or took a lot of damage && self->client->ps.legsAnim != BOTH_GESTURE1 && self->client->ps.legsAnim != BOTH_GESTURE2 && TIMER_Done( self, "takingPain" ) ) { if ( !Wampa_CheckRoar( self ) ) { if ( self->client->ps.legsAnim != BOTH_ATTACK1 && self->client->ps.legsAnim != BOTH_ATTACK2 && self->client->ps.legsAnim != BOTH_ATTACK3 ) {//cant interrupt one of the big attack anims if ( self->health > 100 || hitByWampa ) { TIMER_Remove( self, "attacking" ); VectorCopy( self->NPC->lastPathAngles, self->s.angles ); if ( !Q_irand( 0, 1 ) ) { NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } else { NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } TIMER_Set( self, "takingPain", self->client->ps.legsTimer+Q_irand(0, 500) ); //allow us to re-evaluate our running speed/anim TIMER_Set( self, "runfar", -1 ); TIMER_Set( self, "runclose", -1 ); TIMER_Set( self, "walk", -1 ); if ( self->NPC ) { self->NPC->localState = LSTATE_WAITING; } } } } } }
void NPC_BSGrenadier_Attack( void ) { //Don't do anything if we're hurt if ( NPC->painDebounceTime > level.time ) { NPC_UpdateAngles( qtrue, qtrue ); return; } //NPC_CheckEnemy( qtrue, qfalse ); //If we don't have an enemy, just idle if ( NPC_CheckEnemyExt() == qfalse )//!NPC->enemy )// { NPC->enemy = NULL; NPC_BSGrenadier_Patrol();//FIXME: or patrol? return; } if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) {//going to run NPC_UpdateAngles( qtrue, qtrue ); return; } if ( !NPC->enemy ) {//WTF? somehow we lost our enemy? NPC_BSGrenadier_Patrol();//FIXME: or patrol? return; } enemyLOS = enemyCS = qfalse; AImove = qtrue; faceEnemy = qfalse; shoot = qfalse; enemyDist = DistanceSquared( NPC->enemy->currentOrigin, NPC->currentOrigin ); //See if we should switch to melee attack if ( enemyDist < 16384 && (!NPC->enemy->client||NPC->enemy->client->ps.weapon != WP_SABER||!NPC->enemy->client->ps.saberActive) )//128 {//enemy is close and not using saber if ( NPC->client->ps.weapon == WP_THERMAL ) {//grenadier trace_t trace; gi.trace ( &trace, NPC->currentOrigin, NPC->enemy->mins, NPC->enemy->maxs, NPC->enemy->currentOrigin, NPC->s.number, NPC->enemy->clipmask, G2_NOCOLLIDE, 0 ); if ( !trace.allsolid && !trace.startsolid && (trace.fraction == 1.0 || trace.entityNum == NPC->enemy->s.number ) ) {//I can get right to him //reset fire-timing variables NPC_ChangeWeapon( WP_MELEE ); if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//NPCInfo->behaviorState == BS_STAND_AND_SHOOT ) {//FIXME: should we be overriding scriptFlags? NPCInfo->scriptFlags |= SCF_CHASE_ENEMIES;//NPCInfo->behaviorState = BS_HUNT_AND_KILL; } } } } else if ( enemyDist > 65536 || (NPC->enemy->client && NPC->enemy->client->ps.weapon == WP_SABER && NPC->enemy->client->ps.saberActive) )//256 {//enemy is far or using saber if ( NPC->client->ps.weapon == WP_MELEE && (NPC->client->ps.stats[STAT_WEAPONS]&(1<<WP_THERMAL)) ) {//fisticuffs, make switch to thermal if have it //reset fire-timing variables NPC_ChangeWeapon( WP_THERMAL ); } } //can we see our target? if ( NPC_ClearLOS( NPC->enemy ) ) { NPCInfo->enemyLastSeenTime = level.time; enemyLOS = qtrue; if ( NPC->client->ps.weapon == WP_MELEE ) { if ( enemyDist <= 4096 && InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 90, 45 ) )//within 64 & infront { VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); enemyCS = qtrue; } } else if ( InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 45, 90 ) ) {//in front of me //can we shoot our target? //FIXME: how accurate/necessary is this check? int hit = NPC_ShotEntity( NPC->enemy ); gentity_t *hitEnt = &g_entities[hit]; if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) ) { VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); float enemyHorzDist = DistanceHorizontalSquared( NPC->enemy->currentOrigin, NPC->currentOrigin ); if ( enemyHorzDist < 1048576 ) {//within 1024 enemyCS = qtrue; NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy } else { NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy } } } } else { NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy } /* else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) { NPCInfo->enemyLastSeenTime = level.time; faceEnemy = qtrue; } */ if ( enemyLOS ) {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? faceEnemy = qtrue; } if ( enemyCS ) { shoot = qtrue; if ( NPC->client->ps.weapon == WP_THERMAL ) {//don't chase and throw AImove = qfalse; } else if ( NPC->client->ps.weapon == WP_MELEE && enemyDist < (NPC->maxs[0]+NPC->enemy->maxs[0]+16)*(NPC->maxs[0]+NPC->enemy->maxs[0]+16) ) {//close enough AImove = qfalse; } }//this should make him chase enemy when out of range...? //Check for movement to take care of Grenadier_CheckMoveState(); //See if we should override shooting decision with any special considerations Grenadier_CheckFireState(); if ( AImove ) {//move toward goal if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist > 10000 ) )//100 squared { AImove = Grenadier_Move(); } else { AImove = qfalse; } } if ( !AImove ) { if ( !TIMER_Done( NPC, "duck" ) ) { ucmd.upmove = -127; } //FIXME: what about leaning? } else {//stop ducking! TIMER_Set( NPC, "duck", -1 ); } if ( !faceEnemy ) {//we want to face in the dir we're running if ( AImove ) {//don't run away and shoot NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; NPCInfo->desiredPitch = 0; shoot = qfalse; } NPC_UpdateAngles( qtrue, qtrue ); } else// if ( faceEnemy ) {//face the enemy NPC_FaceEnemy(); } if ( NPCInfo->scriptFlags&SCF_DONT_FIRE ) { shoot = qfalse; } //FIXME: don't shoot right away! if ( shoot ) {//try to shoot if it's time if ( TIMER_Done( NPC, "attackDelay" ) ) { if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here { WeaponThink( qtrue ); TIMER_Set( NPC, "attackDelay", NPCInfo->shotTime-level.time ); } } } }
/* ------------------------- Remote_MaintainHeight ------------------------- */ void Remote_MaintainHeight( void ) { float dif; // Update our angles regardless NPC_UpdateAngles( qtrue, qtrue ); if ( NPC->client->ps.velocity[2] ) { NPC->client->ps.velocity[2] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) { NPC->client->ps.velocity[2] = 0; } } // If we have an enemy, we should try to hover at or a little below enemy eye level if ( NPC->enemy ) { if (TIMER_Done( NPC, "heightChange")) { TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 )); // Find the height difference dif = (NPC->enemy->currentOrigin[2] + Q_irand( 0, NPC->enemy->maxs[2]+8 )) - NPC->currentOrigin[2]; // cap to prevent dramatic height shifts if ( fabs( dif ) > 2 ) { if ( fabs( dif ) > 24 ) { dif = ( dif < 0 ? -24 : 24 ); } dif *= 10; NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; NPC->fx_time = level.time; G_Sound( NPC, G_SoundIndex("sound/chars/remote/misc/hiss.wav")); } } } else { gentity_t *goal = NULL; if ( NPCInfo->goalEntity ) // Is there a goal? { goal = NPCInfo->goalEntity; } else { goal = NPCInfo->lastGoalEntity; } if ( goal ) { dif = goal->currentOrigin[2] - NPC->currentOrigin[2]; if ( fabs( dif ) > 24 ) { dif = ( dif < 0 ? -24 : 24 ); NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; } } } // Apply friction if ( NPC->client->ps.velocity[0] ) { NPC->client->ps.velocity[0] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) { NPC->client->ps.velocity[0] = 0; } } if ( NPC->client->ps.velocity[1] ) { NPC->client->ps.velocity[1] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) { NPC->client->ps.velocity[1] = 0; } } }
/* ------------------------- Mark1_dying ------------------------- */ void Mark1_dying( gentity_t *self ) { int num,newBolt; if (self->client->ps.torsoTimer>0) { if (TIMER_Done(self,"dyingExplosion")) { num = Q_irand( 1, 3); // Find place to generate explosion if (num == 1) { num = Q_irand( 8, 10); newBolt = trap_G2API_AddBolt( self->ghoul2, 0, va("*flash%d",num) ); NPC_Mark1_Part_Explode(self,newBolt); } else { num = Q_irand( 1, 6); newBolt = trap_G2API_AddBolt( self->ghoul2, 0, va("*torso_tube%d",num) ); NPC_Mark1_Part_Explode(self,newBolt); NPC_SetSurfaceOnOff( self, va("torso_tube%d",num), TURN_OFF ); } TIMER_Set( self, "dyingExplosion", Q_irand( 300, 1000 ) ); } // int dir; // vec3_t right; // Shove to the side // AngleVectors( self->client->renderInfo.eyeAngles, NULL, right, NULL ); // VectorMA( self->client->ps.velocity, -80, right, self->client->ps.velocity ); // See which weapons are there // Randomly fire blaster if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "l_arm" )) // Is the blaster still on the model? { if (Q_irand( 1, 5) == 1) { SaveNPCGlobals(); SetNPCGlobals( self ); Mark1Dead_FireBlaster(); RestoreNPCGlobals(); } } // Randomly fire rocket if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "r_arm" )) // Is the rocket still on the model? { if (Q_irand( 1, 10) == 1) { SaveNPCGlobals(); SetNPCGlobals( self ); Mark1Dead_FireRocket(); RestoreNPCGlobals(); } } } }
void NPC_BSGrenadier_Patrol( void ) {//FIXME: pick up on bodies of dead buddies? if ( NPCInfo->confusionTime < level.time ) { //Look for any enemies if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) { if ( NPC_CheckPlayerTeamStealth() ) { //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be automatic now //NPC_AngerSound(); NPC_UpdateAngles( qtrue, qtrue ); return; } } if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) { //Is there danger nearby int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS ); if ( NPC_CheckForDanger( alertEvent ) ) { NPC_UpdateAngles( qtrue, qtrue ); return; } else {//check for other alert events //There is an event to look at if ( alertEvent >= 0 && level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) { NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID; if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED ) { if ( level.alertEvents[alertEvent].owner && level.alertEvents[alertEvent].owner->client && level.alertEvents[alertEvent].owner->health >= 0 && level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) {//an enemy G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); //NPCInfo->enemyLastSeenTime = level.time; TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); } } else {//FIXME: get more suspicious over time? //Save the position for movement (if necessary) VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal ); NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 ); if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS ) {//suspicious looks longer NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 ); } } } } if ( NPCInfo->investigateDebounceTime > level.time ) {//FIXME: walk over to it, maybe? Not if not chase enemies //NOTE: stops walking or doing anything else below vec3_t dir, angles; float o_yaw, o_pitch; VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir ); vectoangles( dir, angles ); o_yaw = NPCInfo->desiredYaw; o_pitch = NPCInfo->desiredPitch; NPCInfo->desiredYaw = angles[YAW]; NPCInfo->desiredPitch = angles[PITCH]; NPC_UpdateAngles( qtrue, qtrue ); NPCInfo->desiredYaw = o_yaw; NPCInfo->desiredPitch = o_pitch; return; } } } //If we have somewhere to go, then do that if ( UpdateGoal() ) { ucmd.buttons |= BUTTON_WALKING; NPC_MoveToGoal( qtrue ); } NPC_UpdateAngles( qtrue, qtrue ); }
static void Sniper_CheckMoveState( void ) { //See if we're a scout if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )//NPCInfo->behaviorState == BS_STAND_AND_SHOOT ) { if ( NPCInfo->goalEntity == NPC->enemy ) { doMove = qfalse; return; } } //See if we're running away else if ( NPCInfo->squadState == SQUAD_RETREAT ) { if ( TIMER_Done( NPC, "flee" ) ) { NPCInfo->squadState = SQUAD_IDLE; } else { faceEnemy = qfalse; } } else if ( NPCInfo->squadState == SQUAD_IDLE ) { if ( !NPCInfo->goalEntity ) { doMove = qfalse; return; } } if ( !TIMER_Done( NPC, "taunting" ) ) {//no doMove while taunting doMove = qfalse; return; } //See if we're moving towards a goal, not the enemy if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) ) { //Did we make it? if ( STEER::Reached(NPC, NPCInfo->goalEntity, 16, !!FlyingCreature(NPC)) || ( NPCInfo->squadState == SQUAD_SCOUT && enemyLOS && enemyDist <= 10000 ) ) { int newSquadState = SQUAD_STAND_AND_SHOOT; //we got where we wanted to go, set timers based on why we were running switch ( NPCInfo->squadState ) { case SQUAD_RETREAT://was running away if ( NPC->client->NPC_class == CLASS_SABOTEUR ) { Saboteur_Cloak( NPC ); } TIMER_Set( NPC, "duck", (NPC->max_health - NPC->health) * 100 ); TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) ); newSquadState = SQUAD_COVER; break; case SQUAD_TRANSITION://was heading for a combat point TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) ); break; case SQUAD_SCOUT://was running after player break; default: break; } NPC_ReachedGoal(); //don't attack right away TIMER_Set( NPC, "attackDelay", Q_irand( (6-NPCInfo->stats.aim)*50, (6-NPCInfo->stats.aim)*100 ) ); //FIXME: Slant for difficulty levels, too? //don't do something else just yet TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) ); //stop fleeing if ( NPCInfo->squadState == SQUAD_RETREAT ) { TIMER_Set( NPC, "flee", -level.time ); NPCInfo->squadState = SQUAD_IDLE; } return; } //keep going, hold of roamTimer until we get there TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) ); } }
void NPC_BSSniper_Attack( void ) { //Don't do anything if we're hurt if ( NPC->painDebounceTime > level.time ) { NPC_UpdateAngles( qtrue, qtrue ); return; } //NPC_CheckEnemy( qtrue, qfalse ); //If we don't have an enemy, just idle if ( NPC_CheckEnemyExt() == qfalse )//!NPC->enemy )// { NPC_BSSniper_Patrol();//FIXME: or patrol? return; } if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) {//going to run NPC_UpdateAngles( qtrue, qtrue ); return; } if ( !NPC->enemy ) {//WTF? somehow we lost our enemy? NPC_BSSniper_Patrol();//FIXME: or patrol? return; } enemyLOS = enemyCS = qfalse; doMove = qtrue; faceEnemy = qfalse; shoot = qfalse; enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); if ( enemyDist < 16384 )//128 squared {//too close, so switch to primary fire if ( NPC->client->ps.weapon == WP_DISRUPTOR || NPC->client->ps.weapon == WP_TUSKEN_RIFLE ) {//sniping... should be assumed if ( NPCInfo->scriptFlags & SCF_ALT_FIRE ) {//use primary fire trace_t trace; gi.trace ( &trace, NPC->enemy->currentOrigin, NPC->enemy->mins, NPC->enemy->maxs, NPC->currentOrigin, NPC->enemy->s.number, NPC->enemy->clipmask, (EG2_Collision)0, 0 ); if ( !trace.allsolid && !trace.startsolid && (trace.fraction == 1.0 || trace.entityNum == NPC->s.number ) ) {//he can get right to me NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; //reset fire-timing variables NPC_ChangeWeapon( NPC->client->ps.weapon ); NPC_UpdateAngles( qtrue, qtrue ); return; } } //FIXME: switch back if he gets far away again? } } else if ( enemyDist > 65536 )//256 squared { if ( NPC->client->ps.weapon == WP_DISRUPTOR || NPC->client->ps.weapon == WP_TUSKEN_RIFLE ) {//sniping... should be assumed if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) {//use primary fire NPCInfo->scriptFlags |= SCF_ALT_FIRE; //reset fire-timing variables NPC_ChangeWeapon( NPC->client->ps.weapon ); NPC_UpdateAngles( qtrue, qtrue ); return; } } } Sniper_UpdateEnemyPos(); //can we see our target? if ( NPC_ClearLOS( NPC->enemy ) )//|| (NPCInfo->stats.aim >= 5 && gi.inPVS( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin )) ) { NPCInfo->enemyLastSeenTime = level.time; VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); enemyLOS = qtrue; float maxShootDist = NPC_MaxDistSquaredForWeapon(); if ( enemyDist < maxShootDist ) { vec3_t fwd, right, up, muzzle, end; trace_t tr; AngleVectors( NPC->client->ps.viewangles, fwd, right, up ); CalcMuzzlePoint( NPC, fwd, right, up, muzzle, 0 ); VectorMA( muzzle, 8192, fwd, end ); gi.trace ( &tr, muzzle, NULL, NULL, end, NPC->s.number, MASK_SHOT, G2_RETURNONHIT, 0 ); int hit = tr.entityNum; //can we shoot our target? if ( Sniper_EvaluateShot( hit ) ) { enemyCS = qtrue; } } } /* else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) { NPCInfo->enemyLastSeenTime = level.time; faceEnemy = qtrue; } */ if ( enemyLOS ) {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? faceEnemy = qtrue; } if ( !TIMER_Done( NPC, "taunting" ) ) { doMove = qfalse; shoot = qfalse; } else if ( enemyCS ) { shoot = qtrue; } else if ( level.time - NPCInfo->enemyLastSeenTime > 3000 ) {//Hmm, have to get around this bastard... FIXME: this NPCInfo->enemyLastSeenTime builds up when ducked seems to make them want to run when they uncrouch Sniper_ResolveBlockedShot(); } else if ( NPC->client->ps.weapon == WP_TUSKEN_RIFLE && !Q_irand( 0, 100 ) ) {//start a taunt NPC_Tusken_Taunt(); TIMER_Set( NPC, "duck", -1 ); doMove = qfalse; } //Check for movement to take care of Sniper_CheckMoveState(); //See if we should override shooting decision with any special considerations Sniper_CheckFireState(); if ( doMove ) {//doMove toward goal if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist > 10000 ) )//100 squared { doMove = Sniper_Move(); } else { doMove = qfalse; } } if ( !doMove ) { if ( !TIMER_Done( NPC, "duck" ) ) { if ( TIMER_Done( NPC, "watch" ) ) {//not while watching ucmd.upmove = -127; if ( NPC->client->NPC_class == CLASS_SABOTEUR ) { Saboteur_Cloak( NPC ); } } } //FIXME: what about leaning? //FIXME: also, when stop ducking, start looking, if enemy can see me, chance of ducking back down again } else {//stop ducking! TIMER_Set( NPC, "duck", -1 ); if ( NPC->client->NPC_class == CLASS_SABOTEUR ) { Saboteur_Decloak( NPC ); } } if ( TIMER_Done( NPC, "duck" ) && TIMER_Done( NPC, "watch" ) && (TIMER_Get( NPC, "attackDelay" )-level.time) > 1000 && NPC->attackDebounceTime < level.time ) { if ( enemyLOS && (NPCInfo->scriptFlags&SCF_ALT_FIRE) ) { if ( NPC->fly_sound_debounce_time < level.time ) { NPC->fly_sound_debounce_time = level.time + 2000; } } } if ( !faceEnemy ) {//we want to face in the dir we're running if ( doMove ) {//don't run away and shoot NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; NPCInfo->desiredPitch = 0; shoot = qfalse; } NPC_UpdateAngles( qtrue, qtrue ); } else// if ( faceEnemy ) {//face the enemy Sniper_FaceEnemy(); } if ( NPCInfo->scriptFlags&SCF_DONT_FIRE ) { shoot = qfalse; } //FIXME: don't shoot right away! if ( shoot ) {//try to shoot if it's time if ( TIMER_Done( NPC, "attackDelay" ) ) { WeaponThink( qtrue ); if ( ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK) ) { G_SoundOnEnt( NPC, CHAN_WEAPON, "sound/null.wav" ); } //took a shot, now hide if ( !(NPC->spawnflags&SPF_NO_HIDE) && !Q_irand( 0, 1 ) ) { //FIXME: do this if in combat point and combat point has duck-type cover... also handle lean-type cover Sniper_StartHide(); } else { TIMER_Set( NPC, "attackDelay", NPCInfo->shotTime-level.time ); } } } }
void NPC_Rancor_Pain( gentity_t *self, gentity_t *attacker, int damage ) { qboolean hitByRancor = qfalse; if ( attacker&&attacker->client&&attacker->client->NPC_class == CLASS_RANCOR ) { hitByRancor = qtrue; } if ( attacker && attacker->inuse && attacker != self->enemy && !(attacker->flags&FL_NOTARGET) ) { if ( !self->count ) { if ( (!attacker->s.number&&!Q_irand( 0, 3 )) || !self->enemy || self->enemy->health == 0 || (self->enemy->client&&self->enemy->client->NPC_class == CLASS_RANCOR) || (self->NPC && self->NPC->consecutiveBlockedMoves >= 10 && DistanceSquared( &attacker->r.currentOrigin, &self->r.currentOrigin ) < DistanceSquared( &self->enemy->r.currentOrigin, &self->r.currentOrigin )) ) {//if my enemy is dead (or attacked by player) and I'm not still holding/eating someone, turn on the attacker //FIXME: if can't nav to my enemy, take this guy if I can nav to him G_SetEnemy( self, attacker ); TIMER_Set( self, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); if ( hitByRancor ) {//stay mad at this Rancor for 2-5 secs before looking for attacker enemies TIMER_Set( self, "rancorInfight", Q_irand( 2000, 5000 ) ); } } } } if ( (hitByRancor || (self->count == 1 && self->activator&&!Q_irand( 0, 4 )) || Q_irand( 0, 200 ) < damage)//hit by rancor, hit while holding live victim, or took a lot of damage && self->client->ps.legsAnim != BOTH_STAND1TO2 && TIMER_Done( self, "takingPain" ) ) { if ( !Rancor_CheckRoar( self ) ) { if ( self->client->ps.legsAnim != BOTH_MELEE1 && self->client->ps.legsAnim != BOTH_MELEE2 && self->client->ps.legsAnim != BOTH_ATTACK2 ) {//cant interrupt one of the big attack anims /* if ( self->count != 1 || attacker == self->activator || (self->client->ps.legsAnim != BOTH_ATTACK1&&self->client->ps.legsAnim != BOTH_ATTACK3) ) */ {//if going to bite our victim, only victim can interrupt that anim if ( self->health > 100 || hitByRancor ) { TIMER_Remove( self, "attacking" ); VectorCopy( &self->NPC->lastPathAngles, &self->s.angles ); if ( self->count == 1 ) { NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } else { NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } TIMER_Set( self, "takingPain", self->client->ps.legsTimer + Q_irand( 0, 500 ) ); if ( self->NPC ) { self->NPC->localState = LSTATE_WAITING; } } } } } //let go /* if ( !Q_irand( 0, 3 ) && self->count == 1 ) { Rancor_DropVictim( self ); } */ } }
void NPC_BSRancor_Default( void ) { AddSightEvent( NPC, &NPC->r.currentOrigin, 1024, AEL_DANGER_GREAT, 50 ); Rancor_Crush(); NPC->client->ps.eFlags2 &= ~(EF2_USE_ALT_ANIM | EF2_GENERIC_NPC_FLAG); if ( NPC->count ) {//holding someone NPC->client->ps.eFlags2 |= EF2_USE_ALT_ANIM; if ( NPC->count == 2 ) {//in my mouth NPC->client->ps.eFlags2 |= EF2_GENERIC_NPC_FLAG; } } else { NPC->client->ps.eFlags2 &= ~(EF2_USE_ALT_ANIM | EF2_GENERIC_NPC_FLAG); } if ( TIMER_Done2( NPC, "clearGrabbed", qtrue ) ) { Rancor_DropVictim( NPC ); } else if ( NPC->client->ps.legsAnim == BOTH_PAIN2 && NPC->count == 1 && NPC->activator ) { if ( !Q_irand( 0, 3 ) ) { Rancor_CheckDropVictim(); } } if ( !TIMER_Done( NPC, "rageTime" ) ) {//do nothing but roar first time we see an enemy AddSoundEvent( NPC, &NPC->r.currentOrigin, 1024, AEL_DANGER_GREAT, qfalse );//, qfalse ); NPC_FaceEnemy( qtrue ); return; } if ( NPC->enemy ) { /* if ( NPC->enemy->client //enemy is a client && (NPC->enemy->client->NPC_class == CLASS_UGNAUGHT || NPC->enemy->client->NPC_class == CLASS_JAWA )//enemy is a lowly jawa or ugnaught && NPC->enemy->enemy != NPC//enemy's enemy is not me && (!NPC->enemy->enemy || !NPC->enemy->enemy->client || NPC->enemy->enemy->client->NPC_class!=CLASS_RANCOR) )//enemy's enemy is not a client or is not a rancor (which is as scary as me anyway) {//they should be scared of ME and no-one else G_SetEnemy( NPC->enemy, NPC ); } */ if ( TIMER_Done( NPC, "angrynoise" ) ) { G_Sound( NPC, CHAN_AUTO, G_SoundIndex( va( "sound/chars/rancor/misc/anger%d.wav", Q_irand( 1, 3 ) ) ) ); TIMER_Set( NPC, "angrynoise", Q_irand( 5000, 10000 ) ); } else { AddSoundEvent( NPC, &NPC->r.currentOrigin, 512, AEL_DANGER_GREAT, qfalse );//, qfalse ); } if ( NPC->count == 2 && NPC->client->ps.legsAnim == BOTH_ATTACK3 ) {//we're still chewing our enemy up NPC_UpdateAngles( qtrue, qtrue ); return; } //else, if he's in our hand, we eat, else if he's on the ground, we keep attacking his dead body for a while if ( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_RANCOR ) {//got mad at another Rancor, look for a valid enemy if ( TIMER_Done( NPC, "rancorInfight" ) ) { NPC_CheckEnemyExt( qtrue ); } } else if ( !NPC->count ) { if ( ValidEnemy( NPC->enemy ) == qfalse ) { TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now if ( !NPC->enemy->inuse || level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 ) ) {//it's been a while since the enemy died, or enemy is completely gone, get bored with him NPC->enemy = NULL; Rancor_Patrol(); NPC_UpdateAngles( qtrue, qtrue ); return; } } if ( TIMER_Done( NPC, "lookForNewEnemy" ) ) { gentity_t *newEnemy, *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy? NPC->enemy = NULL; newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse ); NPC->enemy = sav_enemy; if ( newEnemy && newEnemy != sav_enemy ) {//picked up a new enemy! NPC->lastEnemy = NPC->enemy; G_SetEnemy( NPC, newEnemy ); //hold this one for at least 5-15 seconds TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); } else {//look again in 2-5 secs TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) ); } } } Rancor_Combat(); } else { if ( TIMER_Done( NPC, "idlenoise" ) ) { G_Sound( NPC, CHAN_AUTO, G_SoundIndex( va( "sound/chars/rancor/snort_%d.wav", Q_irand( 1, 2 ) ) ) ); TIMER_Set( NPC, "idlenoise", Q_irand( 2000, 4000 ) ); AddSoundEvent( NPC, &NPC->r.currentOrigin, 384, AEL_DANGER, qfalse );//, qfalse ); } if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) { Rancor_Patrol(); } else { Rancor_Idle(); } } NPC_UpdateAngles( qtrue, qtrue ); }
void Rancor_Combat( void ) { if ( NPC->count ) {//holding my enemy if ( TIMER_Done2( NPC, "takingPain", qtrue ) ) { NPCInfo->localState = LSTATE_CLEAR; } else { Rancor_Attack( 0, qfalse ); } NPC_UpdateAngles( qtrue, qtrue ); return; } // If we cannot see our target or we have somewhere to go, then do that if ( !NPC_ClearLOS4( NPC->enemy ) )//|| UpdateGoal( )) { NPCInfo->combatMove = qtrue; NPCInfo->goalEntity = NPC->enemy; NPCInfo->goalRadius = MIN_DISTANCE;//MAX_DISTANCE; // just get us within combat range if ( !NPC_MoveToGoal( qtrue ) ) {//couldn't go after him? Look for a new one TIMER_Set( NPC, "lookForNewEnemy", 0 ); NPCInfo->consecutiveBlockedMoves++; } else { NPCInfo->consecutiveBlockedMoves = 0; } return; } // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb NPC_FaceEnemy( qtrue ); { float distance; qboolean advance; qboolean doCharge; distance = Distance( &NPC->r.currentOrigin, &NPC->enemy->r.currentOrigin ); advance = (qboolean)(distance > (NPC->r.maxs.x + MIN_DISTANCE) ? qtrue : qfalse); doCharge = qfalse; if ( advance ) {//have to get closer vector3 yawOnlyAngles; VectorSet( &yawOnlyAngles, 0, NPC->r.currentAngles.yaw, 0 ); if ( NPC->enemy->health > 0 && fabsf( distance - 250 ) <= 80 && InFOV3( &NPC->enemy->r.currentOrigin, &NPC->r.currentOrigin, &yawOnlyAngles, 30, 30 ) ) { if ( !Q_irand( 0, 9 ) ) {//go for the charge doCharge = qtrue; advance = qfalse; } } } if ( (advance /*|| NPCInfo->localState == LSTATE_WAITING*/) && TIMER_Done( NPC, "attacking" ) ) // waiting monsters can't attack { if ( TIMER_Done2( NPC, "takingPain", qtrue ) ) { NPCInfo->localState = LSTATE_CLEAR; } else { Rancor_Move( qtrue ); } } else { Rancor_Attack( distance, doCharge ); } } }
void Rancor_Attack( float distance, qboolean doCharge ) { if ( !TIMER_Exists( NPC, "attacking" ) ) { if ( NPC->count == 2 && NPC->activator ) { } else if ( NPC->count == 1 && NPC->activator ) {//holding enemy if ( NPC->activator->health > 0 && Q_irand( 0, 1 ) ) {//quick bite NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 450 ); } else {//full eat NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 900 ); //Make victim scream in fright if ( NPC->activator->health > 0 && NPC->activator->client ) { G_AddEvent( NPC->activator, Q_irand( EV_DEATH1, EV_DEATH3 ), 0 ); NPC_SetAnim( NPC->activator, SETANIM_TORSO, BOTH_FALLDEATH1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); if ( NPC->activator->NPC ) {//no more thinking for you TossClientItems( NPC ); NPC->activator->NPC->nextBStateThink = Q3_INFINITE; } } } } else if ( NPC->enemy->health > 0 && doCharge ) {//charge vector3 fwd, yawAng; VectorSet( &yawAng, 0, NPC->client->ps.viewangles.yaw, 0 ); AngleVectors( &yawAng, &fwd, NULL, NULL ); VectorScale( &fwd, distance*1.5f, &NPC->client->ps.velocity ); NPC->client->ps.velocity.z = 150; NPC->client->ps.groundEntityNum = ENTITYNUM_NONE; NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 1250 ); } else if ( !Q_irand( 0, 1 ) ) {//smash NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 1000 ); } else {//try to grab NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 1000 ); } TIMER_Set( NPC, "attacking", NPC->client->ps.legsTimer + random() * 200 ); } // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks if ( TIMER_Done2( NPC, "attack_dmg", qtrue ) ) { vector3 shakePos; switch ( NPC->client->ps.legsAnim ) { case BOTH_MELEE1: Rancor_Smash(); G_GetBoltPosition( NPC, NPC->client->renderInfo.handLBolt, &shakePos, 0 ); G_ScreenShake( &shakePos, NULL, 4.0f, 1000, qfalse ); //CGCam_Shake( 1.0f*playerDist/128.0f, 1000 ); break; case BOTH_MELEE2: Rancor_Bite(); TIMER_Set( NPC, "attack_dmg2", 450 ); break; case BOTH_ATTACK1: if ( NPC->count == 1 && NPC->activator ) { G_Damage( NPC->activator, NPC, NPC, &vec3_origin, &NPC->activator->r.currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_ARMOR | DAMAGE_NO_KNOCKBACK, MOD_MELEE ); if ( NPC->activator->health <= 0 ) {//killed him //make it look like we bit his head off //NPC->activator->client->dismembered = qfalse; G_Dismember( NPC->activator, NPC, &NPC->activator->r.currentOrigin, G2_MODELPART_HEAD, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue ); //G_DoDismemberment( NPC->activator, NPC->activator->r.currentOrigin, MOD_SABER, 1000, HL_HEAD, qtrue ); NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE; NPC->activator->client->ps.forceHandExtendTime = 0; NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) ); } break; case BOTH_ATTACK2: //try to grab Rancor_Swing( qtrue ); break; case BOTH_ATTACK3: if ( NPC->count == 1 && NPC->activator ) { //cut in half if ( NPC->activator->client ) { //NPC->activator->client->dismembered = qfalse; G_Dismember( NPC->activator, NPC, &NPC->activator->r.currentOrigin, G2_MODELPART_WAIST, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue ); //G_DoDismemberment( NPC->activator, NPC->enemy->r.currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue ); } //KILL G_Damage( NPC->activator, NPC, NPC, &vec3_origin, &NPC->activator->r.currentOrigin, NPC->enemy->health + 10, DAMAGE_NO_PROTECTION | DAMAGE_NO_ARMOR | DAMAGE_NO_KNOCKBACK | DAMAGE_NO_HIT_LOC, MOD_MELEE );//, HL_NONE );// if ( NPC->activator->client ) { NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE; NPC->activator->client->ps.forceHandExtendTime = 0; NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } TIMER_Set( NPC, "attack_dmg2", 1350 ); G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health ); } break; default: break; } } else if ( TIMER_Done2( NPC, "attack_dmg2", qtrue ) ) { switch ( NPC->client->ps.legsAnim ) { case BOTH_MELEE1: break; case BOTH_MELEE2: Rancor_Bite(); break; case BOTH_ATTACK1: break; case BOTH_ATTACK2: break; case BOTH_ATTACK3: if ( NPC->count == 1 && NPC->activator ) {//swallow victim G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) ); //FIXME: sometimes end up with a live one in our mouths? //just make sure they're dead if ( NPC->activator->health > 0 ) { //cut in half //NPC->activator->client->dismembered = qfalse; G_Dismember( NPC->activator, NPC, &NPC->activator->r.currentOrigin, G2_MODELPART_WAIST, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue ); //G_DoDismemberment( NPC->activator, NPC->enemy->r.currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue ); //KILL G_Damage( NPC->activator, NPC, NPC, &vec3_origin, &NPC->activator->r.currentOrigin, NPC->enemy->health + 10, DAMAGE_NO_PROTECTION | DAMAGE_NO_ARMOR | DAMAGE_NO_KNOCKBACK | DAMAGE_NO_HIT_LOC, MOD_MELEE );//, HL_NONE ); NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE; NPC->activator->client->ps.forceHandExtendTime = 0; NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health ); } if ( NPC->activator->client ) {//*sigh*, can't get tags right, just remove them? NPC->activator->client->ps.eFlags |= EF_NODRAW; } NPC->count = 2; TIMER_Set( NPC, "clearGrabbed", 2600 ); } break; default: break; } } else if ( NPC->client->ps.legsAnim == BOTH_ATTACK2 ) { if ( NPC->client->ps.legsTimer >= 1200 && NPC->client->ps.legsTimer <= 1350 ) { if ( Q_irand( 0, 2 ) ) { Rancor_Swing( qfalse ); } else { Rancor_Swing( qtrue ); } } else if ( NPC->client->ps.legsTimer >= 1100 && NPC->client->ps.legsTimer <= 1550 ) { Rancor_Swing( qtrue ); } } // Just using this to remove the attacking flag at the right time TIMER_Done2( NPC, "attacking", qtrue ); }
void Rancor_Swing( qboolean tryGrab ) { int radiusEntNums[128]; int numEnts; const float radius = 88; const float radiusSquared = (radius*radius); int i; vector3 boltOrg; numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, NPC->client->renderInfo.handRBolt, &boltOrg ); for ( i = 0; i < numEnts; i++ ) { gentity_t *radiusEnt = &g_entities[radiusEntNums[i]]; if ( !radiusEnt->inuse ) { continue; } if ( radiusEnt == NPC ) {//Skip the rancor ent continue; } if ( radiusEnt->client == NULL ) {//must be a client continue; } if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) {//can't be one already being held continue; } if ( DistanceSquared( &radiusEnt->r.currentOrigin, &boltOrg ) <= radiusSquared ) { if (tryGrab && NPC->count != 1 //don't have one in hand or in mouth already - FIXME: allow one in hand and any number in mouth! && radiusEnt->client->NPC_class != CLASS_RANCOR && radiusEnt->client->NPC_class != CLASS_GALAKMECH && radiusEnt->client->NPC_class != CLASS_ATST && radiusEnt->client->NPC_class != CLASS_GONK && radiusEnt->client->NPC_class != CLASS_R2D2 && radiusEnt->client->NPC_class != CLASS_R5D2 && radiusEnt->client->NPC_class != CLASS_MARK1 && radiusEnt->client->NPC_class != CLASS_MARK2 && radiusEnt->client->NPC_class != CLASS_MOUSE && radiusEnt->client->NPC_class != CLASS_PROBE && radiusEnt->client->NPC_class != CLASS_SEEKER && radiusEnt->client->NPC_class != CLASS_REMOTE && radiusEnt->client->NPC_class != CLASS_SENTRY && radiusEnt->client->NPC_class != CLASS_INTERROGATOR && radiusEnt->client->NPC_class != CLASS_VEHICLE) {//grab if (NPC->count == 2) {//have one in my mouth, remove him TIMER_Remove(NPC, "clearGrabbed"); Rancor_DropVictim(NPC); } NPC->enemy = radiusEnt;//make him my new best friend radiusEnt->client->ps.eFlags2 |= EF2_HELD_BY_MONSTER; //FIXME: this makes it so that the victim can't hit us with shots! Just use activator or something radiusEnt->client->ps.hasLookTarget = qtrue; radiusEnt->client->ps.lookTarget = NPC->s.number; NPC->activator = radiusEnt;//remember him NPC->count = 1;//in my hand //wait to attack TIMER_Set(NPC, "attacking", NPC->client->ps.legsTimer + Q_irand(500, 2500)); if (radiusEnt->health > 0){//do pain on enemy if(radiusEnt->pain) radiusEnt->pain( radiusEnt, NPC, 100 ); JPLua::Entity_CallFunction( radiusEnt, JPLua::JPLUA_ENTITY_PAIN, (intptr_t)NPC, (intptr_t)100 ); //GEntity_PainFunc( radiusEnt, NPC, NPC, radiusEnt->r.currentOrigin, 0, MOD_CRUSH ); } else if ( radiusEnt->client ) { radiusEnt->client->ps.forceHandExtend = HANDEXTEND_NONE; radiusEnt->client->ps.forceHandExtendTime = 0; NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } } else {//smack vector3 pushDir; vector3 angs; G_Sound( radiusEnt, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); //actually push the enemy /* //VectorSubtract( radiusEnt->r.currentOrigin, boltOrg, pushDir ); VectorSubtract( radiusEnt->r.currentOrigin, NPC->r.currentOrigin, pushDir ); pushDir[2] = flrand( 100, 200 ); VectorNormalize( pushDir ); */ VectorCopy( &NPC->client->ps.viewangles, &angs ); angs.yaw += flrand( 25, 50 ); angs.pitch = flrand( -25, -15 ); AngleVectors( &angs, &pushDir, NULL, NULL ); if ( radiusEnt->client->NPC_class != CLASS_RANCOR && radiusEnt->client->NPC_class != CLASS_ATST ) { G_Damage( radiusEnt, NPC, NPC, &vec3_origin, &radiusEnt->r.currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_ARMOR | DAMAGE_NO_KNOCKBACK, MOD_MELEE ); G_Throw( radiusEnt, &pushDir, 250 ); if ( radiusEnt->health > 0 ) {//do pain on enemy G_Knockdown( radiusEnt );//, NPC, pushDir, 100, qtrue ); } } } } } }
void NPC_GM_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, vec3_t point, int damage, int mod,int hitLoc ) { if ( self->client->ps.powerups[PW_GALAK_SHIELD] == 0 ) {//shield is currently down //FIXME: allow for radius damage? if ( (hitLoc==HL_GENERIC1) && (self->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH) ) { int newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*antenna_base" ); if ( newBolt != -1 ) { GM_CreateExplosion( self, newBolt ); } gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "torso_shield_off", TURN_OFF ); gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "torso_antenna", TURN_OFF ); gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "torso_antenna_base_cap_off", TURN_ON ); self->client->ps.powerups[PW_GALAK_SHIELD] = 0;//temp, for effect self->client->ps.stats[STAT_ARMOR] = 0;//no more armor self->NPC->investigateDebounceTime = 0;//stop recharging NPC_SetAnim( self, SETANIM_BOTH, BOTH_ALERT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( self, "attackDelay", self->client->ps.torsoAnimTimer ); G_AddEvent( self, Q_irand( EV_DEATH1, EV_DEATH3 ), self->health ); } } else {//store the point for shield impact if ( point ) { VectorCopy( point, self->pos4 ); self->client->poisonTime = level.time; } } if ( !self->lockCount && !self->client->ps.torsoAnimTimer ) {//don't interrupt laser sweep attack or other special attacks/moves if ( self->count < 4 && self->health > 100 && hitLoc != HL_GENERIC1 ) { if ( self->delay < level.time ) { int speech; switch( self->count ) { default: case 0: speech = EV_PUSHED1; break; case 1: speech = EV_PUSHED2; break; case 2: speech = EV_PUSHED3; break; case 3: speech = EV_DETECTED1; break; } self->count++; self->NPC->blockedSpeechDebounceTime = 0; G_AddVoiceEvent( self, speech, Q_irand( 3000, 5000 ) ); self->delay = level.time + Q_irand( 5000, 7000 ); } } else { NPC_Pain( self, inflictor, attacker, point, damage, mod, hitLoc ); } } else if ( hitLoc == HL_GENERIC1 ) { NPC_SetPainEvent( self ); self->s.powerups |= ( 1 << PW_SHOCKED ); self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 ); } if ( inflictor && inflictor->lastEnemy == self ) {//He force-pushed my own lobfires back at me if ( mod == MOD_REPEATER_ALT && !Q_irand( 0, 2 ) ) { if ( TIMER_Done( self, "noRapid" ) ) { self->NPC->scriptFlags &= ~SCF_ALT_FIRE; self->alt_fire = qfalse; TIMER_Set( self, "noLob", Q_irand( 2000, 6000 ) ); } else {//hopefully this will make us fire the laser TIMER_Set( self, "noLob", Q_irand( 1000, 2000 ) ); } } else if ( mod == MOD_REPEATER && !Q_irand( 0, 5 ) ) { if ( TIMER_Done( self, "noLob" ) ) { self->NPC->scriptFlags |= SCF_ALT_FIRE; self->alt_fire = qtrue; TIMER_Set( self, "noRapid", Q_irand( 2000, 6000 ) ); } else {//hopefully this will make us fire the laser TIMER_Set( self, "noRapid", Q_irand( 1000, 2000 ) ); } } } }
//--------------------------------------------------------- void WP_FireRocket( gentity_t *ent, qboolean alt_fire ) //--------------------------------------------------------- { vec3_t start; int damage = weaponData[WP_ROCKET_LAUNCHER].damage; float vel = ROCKET_VELOCITY; if ( alt_fire ) { vel *= 0.5f; } VectorCopy( muzzle, start ); WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall gentity_t *missile = CreateMissile( start, forwardVec, vel, 10000, ent, alt_fire ); missile->classname = "rocket_proj"; missile->s.weapon = WP_ROCKET_LAUNCHER; missile->mass = 10; // Do the damages if ( ent->s.number != 0 ) { if ( g_spskill->integer == 0 ) { damage = ROCKET_NPC_DAMAGE_EASY; } else if ( g_spskill->integer == 1 ) { damage = ROCKET_NPC_DAMAGE_NORMAL; } else { damage = ROCKET_NPC_DAMAGE_HARD; } if (ent->client && ent->client->NPC_class==CLASS_BOBAFETT) { damage = damage/2; } } if ( alt_fire ) { int lockEntNum, lockTime; if ( ent->NPC && ent->enemy ) { lockEntNum = ent->enemy->s.number; lockTime = Q_irand( 600, 1200 ); } else { lockEntNum = g_rocketLockEntNum; lockTime = g_rocketLockTime; } // we'll consider attempting to lock this little poochie onto some baddie. if ( (lockEntNum > 0||ent->NPC&&lockEntNum>=0) && lockEntNum < ENTITYNUM_WORLD && lockTime > 0 ) { // take our current lock time and divide that by 8 wedge slices to get the current lock amount int dif = ( level.time - lockTime ) / ( 1200.0f / 8.0f ); if ( dif < 0 ) { dif = 0; } else if ( dif > 8 ) { dif = 8; } // if we are fully locked, always take on the enemy. // Also give a slight advantage to higher, but not quite full charges. // Finally, just give any amount of charge a very slight random chance of locking. if ( dif == 8 || random() * dif > 2 || random() > 0.97f ) { missile->enemy = &g_entities[lockEntNum]; if ( missile->enemy && missile->enemy->inuse )//&& DistanceSquared( missile->currentOrigin, missile->enemy->currentOrigin ) < 262144 && InFOV( missile->currentOrigin, missile->enemy->currentOrigin, missile->enemy->client->ps.viewangles, 45, 45 ) ) { if ( missile->enemy->client && (missile->enemy->client->ps.forcePowersKnown&(1<<FP_PUSH)) && missile->enemy->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_0 ) {//have force push, don't flee from homing rockets } else { vec3_t dir, dir2; AngleVectors( missile->enemy->currentAngles, dir, NULL, NULL ); AngleVectors( ent->client->renderInfo.eyeAngles, dir2, NULL, NULL ); if ( DotProduct( dir, dir2 ) < 0.0f ) { G_StartFlee( missile->enemy, ent, missile->enemy->currentOrigin, AEL_DANGER_GREAT, 3000, 5000 ); if ( !TIMER_Done( missile->enemy, "flee" ) ) { TIMER_Set( missile->enemy, "rocketChasing", 500 ); } } } } } } VectorCopy( forwardVec, missile->movedir ); missile->e_ThinkFunc = thinkF_rocketThink; missile->random = 1.0f; missile->nextthink = level.time + ROCKET_ALT_THINK_TIME; } // Make it easier to hit things VectorSet( missile->maxs, ROCKET_SIZE, ROCKET_SIZE, ROCKET_SIZE ); VectorScale( missile->maxs, -1, missile->mins ); missile->damage = damage; missile->dflags = DAMAGE_DEATH_KNOCKBACK; if ( alt_fire ) { missile->methodOfDeath = MOD_ROCKET_ALT; missile->splashMethodOfDeath = MOD_ROCKET_ALT;// ?SPLASH; } else { missile->methodOfDeath = MOD_ROCKET; missile->splashMethodOfDeath = MOD_ROCKET;// ?SPLASH; } missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; missile->splashDamage = weaponData[WP_ROCKET_LAUNCHER].splashDamage; missile->splashRadius = weaponData[WP_ROCKET_LAUNCHER].splashRadius; // we don't want it to ever bounce missile->bounceCount = 0; }
void NPC_BSGM_Attack( void ) { //Don't do anything if we're hurt if ( NPC->painDebounceTime > level.time ) { NPC_UpdateAngles( qtrue, qtrue ); return; } //FIXME: if killed enemy, use victory anim if ( NPC->enemy && NPC->enemy->health <= 0 && !NPC->enemy->s.number ) {//my enemy is dead if ( NPC->client->ps.torsoAnim == BOTH_STAND2TO1 ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 ); NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer += 500; NPC->client->ps.torsoAnimTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1START ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STARTGESTURE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer += 500; NPC->client->ps.torsoAnimTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STARTGESTURE ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer += 500; NPC->client->ps.torsoAnimTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STOP ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer = -1; NPC->client->ps.torsoAnimTimer = -1; } } else if ( NPC->wait ) { if ( TIMER_Done( NPC, "gloatTime" ) ) { GM_StartGloat(); } else if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared { NPCInfo->goalEntity = NPC->enemy; GM_Move(); } else {//got there GM_StartGloat(); } } NPC_FaceEnemy( qtrue ); NPC_UpdateAngles( qtrue, qtrue ); return; } //If we don't have an enemy, just idle if ( NPC_CheckEnemyExt() == qfalse || !NPC->enemy ) { NPC->enemy = NULL; NPC_BSGM_Patrol(); return; } enemyLOS = enemyCS = qfalse; bMove = qtrue; faceEnemy = qfalse; shoot = qfalse; hitAlly = qfalse; VectorClear( impactPos ); enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 || NPC->client->ps.torsoAnim == BOTH_ATTACK5 ) { shoot = qfalse; if ( TIMER_Done( NPC, "smackTime" ) && !NPCInfo->blockedDebounceTime ) {//time to smack //recheck enemyDist and InFront if ( enemyDist < MELEE_DIST_SQUARED && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.3f ) ) { vec3_t smackDir; VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, smackDir ); smackDir[2] += 30; VectorNormalize( smackDir ); //hurt them G_Sound( NPC->enemy, G_SoundIndex( "sound/weapons/galak/skewerhit.wav" ) ); G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->currentOrigin, (g_spskill->integer+1)*Q_irand( 5, 10), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 ) {//smackdown int knockAnim = BOTH_KNOCKDOWN1; if ( PM_CrouchAnim( NPC->enemy->client->ps.legsAnim ) ) {//knockdown from crouch knockAnim = BOTH_KNOCKDOWN4; } //throw them smackDir[2] = 1; VectorNormalize( smackDir ); G_Throw( NPC->enemy, smackDir, 50 ); NPC_SetAnim( NPC->enemy, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } else {//uppercut //throw them G_Throw( NPC->enemy, smackDir, 100 ); //make them backflip NPC_SetAnim( NPC->enemy, SETANIM_BOTH, BOTH_KNOCKDOWN5, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } //done with the damage NPCInfo->blockedDebounceTime = 1; } } } else if ( NPC->lockCount ) //already shooting laser {//sometimes use the laser beam attack, but only after he's taken down our generator shoot = qfalse; if ( NPC->lockCount == 1 ) {//charging up if ( TIMER_Done( NPC, "beamDelay" ) ) {//time to start the beam int laserAnim; if ( Q_irand( 0, 1 ) ) { laserAnim = BOTH_ATTACK2; } else { laserAnim = BOTH_ATTACK7; } NPC_SetAnim( NPC, SETANIM_BOTH, laserAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer + Q_irand( 1000, 3000 ) ); //turn on beam effect NPC->lockCount = 2; G_PlayEffect( "galak/trace_beam", NPC->s.number ); NPC->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); if ( !NPCInfo->coverTarg ) {//for moving looping sound at end of trace NPCInfo->coverTarg = G_Spawn(); if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); NPCInfo->coverTarg->svFlags |= SVF_BROADCAST; NPCInfo->coverTarg->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); } } } } else {//in the actual attack now if ( !NPC->client->ps.torsoAnimTimer ) {//attack done! NPC->lockCount = 0; G_FreeEntity( NPCInfo->coverTarg ); NPC->s.loopSound = 0; NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_DROPWEAP2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer ); } else {//attack still going //do the trace and damage trace_t trace; vec3_t end, mins={-3,-3,-3}, maxs={3,3,3}; VectorMA( NPC->client->renderInfo.muzzlePoint, 1024, NPC->client->renderInfo.muzzleDir, end ); gi.trace( &trace, NPC->client->renderInfo.muzzlePoint, mins, maxs, end, NPC->s.number, MASK_SHOT ); if ( trace.allsolid || trace.startsolid ) {//oops, in a wall if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); } } else {//clear if ( trace.fraction < 1.0f ) {//hit something gentity_t *traceEnt = &g_entities[trace.entityNum]; if ( traceEnt && traceEnt->takedamage ) {//damage it G_SoundAtSpot( trace.endpos, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); G_Damage( traceEnt, NPC, NPC, NPC->client->renderInfo.muzzleDir, trace.endpos, 10, 0, MOD_ENERGY ); } } if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, trace.endpos ); } if ( !Q_irand( 0, 5 ) ) { G_SoundAtSpot( trace.endpos, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); } } } } } else {//Okay, we're not in a special attack, see if we should switch weapons or start a special attack /* if ( NPC->s.weapon == WP_REPEATER && !(NPCInfo->scriptFlags & SCF_ALT_FIRE)//using rapid-fire && NPC->enemy->s.weapon == WP_SABER //enemy using saber && NPC->client && (NPC->client->ps.saberEventFlags&SEF_DEFLECTED) && !Q_irand( 0, 50 ) ) {//he's deflecting my shots, switch to the laser or the lob fire for a while TIMER_Set( NPC, "noRapid", Q_irand( 2000, 6000 ) ); NPCInfo->scriptFlags |= SCF_ALT_FIRE; NPC->alt_fire = qtrue; if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && (Q_irand( 0, 1 )||enemyDist < MAX_LOB_DIST_SQUARED) ) {//shield down, use laser NPC_GM_StartLaser(); } } else*/ if ( !NPC->client->ps.powerups[PW_GALAK_SHIELD] && enemyDist < MELEE_DIST_SQUARED && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.3f ) && G_StandardHumanoid( NPC->enemy->NPC_type ) )//within 80 and in front {//our shield is down, and enemy within 80, if very close, use melee attack to slap away if ( TIMER_Done( NPC, "attackDelay" ) ) { //animate me int swingAnim; if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH ) {//generator down, use random melee swingAnim = Q_irand( BOTH_ATTACK4, BOTH_ATTACK5 );//smackdown or uppercut } else {//always knock-away swingAnim = BOTH_ATTACK5;//uppercut } //FIXME: swing sound NPC_SetAnim( NPC, SETANIM_BOTH, swingAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer + Q_irand( 1000, 3000 ) ); //delay the hurt until the proper point in the anim TIMER_Set( NPC, "smackTime", 600 ); NPCInfo->blockedDebounceTime = 0; //FIXME: say something? } } else if ( !NPC->lockCount && NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && TIMER_Done( NPC, "attackDelay" ) && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.3f ) && ((!Q_irand( 0, 10*(2-g_spskill->integer))&& enemyDist > MIN_LOB_DIST_SQUARED&& enemyDist < MAX_LOB_DIST_SQUARED) ||(!TIMER_Done( NPC, "noLob" )&&!TIMER_Done( NPC, "noRapid" ))) && NPC->enemy->s.weapon != WP_TURRET ) {//sometimes use the laser beam attack, but only after he's taken down our generator shoot = qfalse; NPC_GM_StartLaser(); } else if ( enemyDist < MIN_LOB_DIST_SQUARED && (NPC->enemy->s.weapon != WP_TURRET || Q_stricmp( "PAS", NPC->enemy->classname )) && TIMER_Done( NPC, "noRapid" ) )//256 {//enemy within 256 if ( (NPC->client->ps.weapon == WP_REPEATER) && (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//shooting an explosive, but enemy too close, switch to primary fire NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; NPC->alt_fire = qfalse; //FIXME: use weap raise & lower anims NPC_ChangeWeapon( WP_REPEATER ); } } else if ( (enemyDist > MAX_LOB_DIST_SQUARED || (NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ))) && TIMER_Done( NPC, "noLob" ) )//448 {//enemy more than 448 away and we are ready to try lob fire again if ( (NPC->client->ps.weapon == WP_REPEATER) && !(NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//enemy far enough away to use lobby explosives NPCInfo->scriptFlags |= SCF_ALT_FIRE; NPC->alt_fire = qtrue; //FIXME: use weap raise & lower anims NPC_ChangeWeapon( WP_REPEATER ); } } } //can we see our target? if ( NPC_ClearLOS( NPC->enemy ) ) { NPCInfo->enemyLastSeenTime = level.time;//used here for aim debouncing, not always a clear LOS enemyLOS = qtrue; if ( NPC->client->ps.weapon == WP_NONE ) { enemyCS = qfalse;//not true, but should stop us from firing NPC_AimAdjust( -1 );//adjust aim worse longer we have no weapon } else {//can we shoot our target? if ( ((NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_LOB_DIST_SQUARED )//256 { enemyCS = qfalse;//not true, but should stop us from firing hitAlly = qtrue;//us! //FIXME: if too close, run away! } else { int hit = NPC_ShotEntity( NPC->enemy, impactPos ); gentity_t *hitEnt = &g_entities[hit]; if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) || ( hitEnt && hitEnt->takedamage ) ) {//can hit enemy or will hit glass or other breakable, so shoot anyway enemyCS = qtrue; NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); } else {//Hmm, have to get around this bastard NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam ) {//would hit an ally, don't fire!!! hitAlly = qtrue; } else {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire } } } } } else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) { if ( TIMER_Done( NPC, "talkDebounce" ) && !Q_irand( 0, 10 ) ) { if ( NPCInfo->enemyCheckDebounceTime < 8 ) { int speech = -1; switch( NPCInfo->enemyCheckDebounceTime ) { case 0: case 1: case 2: speech = EV_CHASE1 + NPCInfo->enemyCheckDebounceTime; break; case 3: case 4: case 5: speech = EV_COVER1 + NPCInfo->enemyCheckDebounceTime-3; break; case 6: case 7: speech = EV_ESCAPING1 + NPCInfo->enemyCheckDebounceTime-6; break; } NPCInfo->enemyCheckDebounceTime++; if ( speech != -1 ) { G_AddVoiceEvent( NPC, speech, Q_irand( 3000, 5000 ) ); TIMER_Set( NPC, "talkDebounce", Q_irand( 5000, 7000 ) ); } } } NPCInfo->enemyLastSeenTime = level.time; int hit = NPC_ShotEntity( NPC->enemy, impactPos ); gentity_t *hitEnt = &g_entities[hit]; if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) || ( hitEnt && hitEnt->takedamage ) ) {//can hit enemy or will hit glass or other breakable, so shoot anyway enemyCS = qtrue; } else { faceEnemy = qtrue; NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy } } if ( enemyLOS ) { faceEnemy = qtrue; } else { if ( !NPCInfo->goalEntity ) { NPCInfo->goalEntity = NPC->enemy; } if ( NPCInfo->goalEntity == NPC->enemy ) {//for now, always chase the enemy bMove = qtrue; } } if ( enemyCS ) { shoot = qtrue; //NPCInfo->enemyCheckDebounceTime = level.time;//actually used here as a last actual LOS } else { if ( !NPCInfo->goalEntity ) { NPCInfo->goalEntity = NPC->enemy; } if ( NPCInfo->goalEntity == NPC->enemy ) {//for now, always chase the enemy bMove = qtrue; } } //Check for movement to take care of GM_CheckMoveState(); //See if we should override shooting decision with any special considerations GM_CheckFireState(); if ( NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE) && shoot && TIMER_Done( NPC, "attackDelay" ) ) { vec3_t muzzle; vec3_t angles; vec3_t target; vec3_t velocity = {0,0,0}; vec3_t mins = {-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE}, maxs = {REPEATER_ALT_SIZE,REPEATER_ALT_SIZE,REPEATER_ALT_SIZE}; CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); VectorCopy( NPC->enemy->currentOrigin, target ); target[0] += Q_flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); target[1] += Q_flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); target[2] += Q_flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); //Find the desired angles qboolean clearshot = WP_LobFire( NPC, muzzle, target, mins, maxs, MASK_SHOT|CONTENTS_LIGHTSABER, velocity, qtrue, NPC->s.number, NPC->enemy->s.number, 300, 1100, 1500, qtrue ); if ( VectorCompare( vec3_origin, velocity ) || (!clearshot&&enemyLOS&&enemyCS) ) {//no clear lob shot and no lob shot that will hit something breakable if ( enemyLOS && enemyCS && TIMER_Done( NPC, "noRapid" ) ) {//have a clear straight shot, so switch to primary NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; NPC->alt_fire = qfalse; NPC_ChangeWeapon( WP_REPEATER ); //keep this weap for a bit TIMER_Set( NPC, "noLob", Q_irand( 500, 1000 ) ); } else { shoot = qfalse; } } else { vectoangles( velocity, angles ); NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); VectorCopy( velocity, NPC->client->hiddenDir ); NPC->client->hiddenDist = VectorNormalize ( NPC->client->hiddenDir ); } } else if ( faceEnemy ) {//face the enemy NPC_FaceEnemy( qtrue ); } if ( !TIMER_Done( NPC, "standTime" ) ) { bMove = qfalse; } if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) {//not supposed to chase my enemies if ( NPCInfo->goalEntity == NPC->enemy ) {//goal is my entity, so don't bMove bMove = qfalse; } } if ( bMove && !NPC->lockCount ) {//bMove toward goal if ( NPCInfo->goalEntity && NPC->client->ps.legsAnim != BOTH_ALERT1 && NPC->client->ps.legsAnim != BOTH_ATTACK2 && NPC->client->ps.legsAnim != BOTH_ATTACK4 && NPC->client->ps.legsAnim != BOTH_ATTACK5 && NPC->client->ps.legsAnim != BOTH_ATTACK7 ) { bMove = GM_Move(); } else { bMove = qfalse; } } if ( !TIMER_Done( NPC, "flee" ) ) {//running away faceEnemy = qfalse; } //FIXME: check scf_face_move_dir here? if ( !faceEnemy ) {//we want to face in the dir we're running if ( !bMove ) {//if we haven't moved, we should look in the direction we last looked? VectorCopy( NPC->client->ps.viewangles, NPCInfo->lastPathAngles ); } if ( bMove ) {//don't run away and shoot NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; NPCInfo->desiredPitch = 0; shoot = qfalse; } } NPC_UpdateAngles( qtrue, qtrue ); if ( NPCInfo->scriptFlags & SCF_DONT_FIRE ) { shoot = qfalse; } if ( NPC->enemy && NPC->enemy->enemy ) { if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER ) {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH) shoot = qfalse; } } //FIXME: don't shoot right away! if ( shoot ) {//try to shoot if it's time if ( TIMER_Done( NPC, "attackDelay" ) ) { if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here { WeaponThink( qtrue ); } } } //also: if ( NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ) ) {//crush turrets if ( G_BoundsOverlap( NPC->absmin, NPC->absmax, NPC->enemy->absmin, NPC->enemy->absmax ) ) {//have to do this test because placed turrets are not solid to NPCs (so they don't obstruct navigation) if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) { NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_ELECTROCUTE ); } else { G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); } } } else if ( NPCInfo->touchedByPlayer != NULL && NPCInfo->touchedByPlayer == NPC->enemy ) {//touched enemy if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) {//zap him! //animate me NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK6, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer ); TIMER_Set( NPC, "standTime", NPC->client->ps.legsAnimTimer ); //FIXME: debounce this? NPCInfo->touchedByPlayer = NULL; //FIXME: some shield effect? NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; vec3_t smackDir; VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, smackDir ); smackDir[2] += 30; VectorNormalize( smackDir ); G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->currentOrigin, (g_spskill->integer+1)*Q_irand( 5, 10), DAMAGE_NO_KNOCKBACK, MOD_ELECTROCUTE ); //throw them G_Throw( NPC->enemy, smackDir, 100 ); NPC->enemy->s.powerups |= ( 1 << PW_SHOCKED ); if ( NPC->enemy->client ) { NPC->enemy->client->ps.powerups[PW_SHOCKED] = level.time + 1000; } //stop any attacks ucmd.buttons = 0; } } if ( NPCInfo->movementSpeech < 3 && NPCInfo->blockedSpeechDebounceTime <= level.time ) { if ( NPC->enemy && NPC->enemy->health > 0 && NPC->enemy->painDebounceTime > level.time ) { if ( NPC->enemy->health < 50 && NPCInfo->movementSpeech == 2 ) { G_AddVoiceEvent( NPC, EV_ANGER2, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 3; } else if ( NPC->enemy->health < 75 && NPCInfo->movementSpeech == 1 ) { G_AddVoiceEvent( NPC, EV_ANGER1, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 2; } else if ( NPC->enemy->health < 100 && NPCInfo->movementSpeech == 0 ) { G_AddVoiceEvent( NPC, EV_ANGER3, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 1; } } } }
//--------------------------------------------------------- void rocketThink( gentity_t *ent ) //--------------------------------------------------------- { vec3_t newdir, targetdir, up={0,0,1}, right; vec3_t org; float dot, dot2; if ( ent->disconnectDebounceTime && ent->disconnectDebounceTime < level.time ) {//time's up, we're done, remove us if ( ent->lockCount ) {//explode when die WP_ExplosiveDie( ent, ent->owner, ent->owner, 0, MOD_UNKNOWN, 0, HL_NONE ); } else {//just remove when die G_FreeEntity( ent ); } return; } if ( ent->enemy && ent->enemy->inuse ) { float vel = (ent->spawnflags&1)?ent->speed:ROCKET_VELOCITY; float newDirMult = ent->angle?ent->angle*2.0f:1.0f; float oldDirMult = ent->angle?(1.0f-ent->angle)*2.0f:1.0f; if ( (ent->spawnflags&1) ) {//vehicle rocket if ( ent->enemy->client && ent->enemy->client->NPC_class == CLASS_VEHICLE ) {//tracking another vehicle if ( ent->enemy->client->ps.speed+ent->speed > vel ) { vel = ent->enemy->client->ps.speed+ent->speed; } } } VectorCopy( ent->enemy->currentOrigin, org ); org[2] += (ent->enemy->mins[2] + ent->enemy->maxs[2]) * 0.5f; if ( ent->enemy->client ) { switch( ent->enemy->client->NPC_class ) { case CLASS_ATST: org[2] += 80; break; case CLASS_MARK1: org[2] += 40; break; case CLASS_PROBE: org[2] += 60; break; } if ( !TIMER_Done( ent->enemy, "flee" ) ) { TIMER_Set( ent->enemy, "rocketChasing", 500 ); } } VectorSubtract( org, ent->currentOrigin, targetdir ); VectorNormalize( targetdir ); // Now the rocket can't do a 180 in space, so we'll limit the turn to about 45 degrees. dot = DotProduct( targetdir, ent->movedir ); // a dot of 1.0 means right-on-target. if ( dot < 0.0f ) { // Go in the direction opposite, start a 180. CrossProduct( ent->movedir, up, right ); dot2 = DotProduct( targetdir, right ); if ( dot2 > 0 ) { // Turn 45 degrees right. VectorMA( ent->movedir, 0.3f*newDirMult, right, newdir ); } else { // Turn 45 degrees left. VectorMA(ent->movedir, -0.3f*newDirMult, right, newdir); } // Yeah we've adjusted horizontally, but let's split the difference vertically, so we kinda try to move towards it. newdir[2] = ( (targetdir[2]*newDirMult) + (ent->movedir[2]*oldDirMult) ) * 0.5; // slowing down coupled with fairly tight turns can lead us to orbit an enemy..looks bad so don't do it! // vel *= 0.5f; } else if ( dot < 0.70f ) { // Still a bit off, so we turn a bit softer VectorMA( ent->movedir, 0.5f*newDirMult, targetdir, newdir ); } else { // getting close, so turn a bit harder VectorMA( ent->movedir, 0.9f*newDirMult, targetdir, newdir ); } // add crazy drunkenness for ( int i = 0; i < 3; i++ ) { newdir[i] += crandom() * ent->random * 0.25f; } // decay the randomness ent->random *= 0.9f; if ( ent->enemy->client && ent->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) {//tracking a client who's on the ground, aim at the floor...? // Try to crash into the ground if we get close enough to do splash damage float dis = Distance( ent->currentOrigin, org ); if ( dis < 128 ) { // the closer we get, the more we push the rocket down, heh heh. newdir[2] -= (1.0f - (dis / 128.0f)) * 0.6f; } } VectorNormalize( newdir ); VectorScale( newdir, vel * 0.5f, ent->s.pos.trDelta ); VectorCopy( newdir, ent->movedir ); SnapVector( ent->s.pos.trDelta ); // save net bandwidth VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); ent->s.pos.trTime = level.time; } ent->nextthink = level.time + ROCKET_ALT_THINK_TIME; // Nothing at all spectacular happened, continue. return; }
/* ------------------------- Mark1_AttackDecision ------------------------- */ void Mark1_AttackDecision( void ) { int blasterTest,rocketTest; float distance; distance_e distRate; qboolean visible; qboolean advance; //randomly talk if ( TIMER_Done(NPC,"patrolNoise") ) { if (TIMER_Done(NPC,"angerNoise")) { // G_Sound( NPC, G_SoundIndex(va("sound/chars/mark1/misc/talk%d.wav", Q_irand(1, 4)))); TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) ); } } // Enemy is dead or he has no enemy. if ((NPC->enemy->health<1) || ( NPC_CheckEnemyExt(qfalse) == qfalse )) { NPC->enemy = NULL; return; } // Rate our distance to the target and visibility distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); distRate = ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE; visible = NPC_ClearLOS4( NPC->enemy ); advance = (qboolean)(distance > MIN_DISTANCE_SQR); // If we cannot see our target, move to see it if ((!visible) || (!NPC_FaceEnemy(qtrue))) { Mark1_Hunt(); return; } // See if the side weapons are there blasterTest = trap_G2API_GetSurfaceRenderStatus( NPC->ghoul2, 0, "l_arm" ); rocketTest = trap_G2API_GetSurfaceRenderStatus( NPC->ghoul2, 0, "r_arm" ); // It has both side weapons if (!blasterTest && !rocketTest) { ; // So do nothing. } else if (blasterTest!=-1 &&blasterTest) { distRate = DIST_LONG; } else if (rocketTest!=-1 &&rocketTest) { distRate = DIST_MELEE; } else // It should never get here, but just in case { NPC->health = 0; NPC->client->ps.stats[STAT_HEALTH] = 0; //GEntity_DieFunc(NPC, NPC, NPC, 100, MOD_UNKNOWN); if (NPC->die) { NPC->die(NPC, NPC, NPC, 100, MOD_UNKNOWN); } } // We can see enemy so shoot him if timers let you. NPC_FaceEnemy( qtrue ); if (distRate == DIST_MELEE) { Mark1_BlasterAttack(advance); } else if (distRate == DIST_LONG) { Mark1_RocketAttack(advance); } }
/* ------------------------- Droid_Patrol ------------------------- */ void Droid_Patrol( void ) { NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]); if ( NPC->client && NPC->client->NPC_class != CLASS_GONK ) { R2D2_PartsMove(); // Get his eye moving. R2D2_TurnAnims(); } //If we have somewhere to go, then do that if ( UpdateGoal() ) { ucmd.buttons |= BUTTON_WALKING; NPC_MoveToGoal( qtrue ); if( NPC->client && NPC->client->NPC_class == CLASS_MOUSE ) { NPCInfo->desiredYaw += sin(level.time*.5) * 25; // Weaves side to side a little if (TIMER_Done(NPC,"patrolNoise")) { G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/mouse/misc/mousego%d.wav", Q_irand(1, 3)) ); TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); } } else if( NPC->client && NPC->client->NPC_class == CLASS_R2D2 ) { if (TIMER_Done(NPC,"patrolNoise")) { G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/r2d2/misc/r2d2talk0%d.wav", Q_irand(1, 3)) ); TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); } } else if( NPC->client && NPC->client->NPC_class == CLASS_R5D2 ) { if (TIMER_Done(NPC,"patrolNoise")) { G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/r5d2/misc/r5talk%d.wav", Q_irand(1, 4)) ); TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); } } if( NPC->client && NPC->client->NPC_class == CLASS_GONK ) { if (TIMER_Done(NPC,"patrolNoise")) { G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/gonk/misc/gonktalk%d.wav", Q_irand(1, 2)) ); TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); } } // else // { // R5D2_LookAround(); // } } NPC_UpdateAngles( qtrue, qtrue ); }
static void Grenadier_CheckMoveState( void ) { //See if we're a scout if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )//behaviorState == BS_STAND_AND_SHOOT ) { if ( NPCInfo->goalEntity == NPC->enemy ) { AImove = qfalse; return; } } //See if we're running away else if ( NPCInfo->squadState == SQUAD_RETREAT ) { if ( TIMER_Done( NPC, "flee" ) ) { NPCInfo->squadState = SQUAD_IDLE; } else { faceEnemy = qfalse; } } /* else if ( NPCInfo->squadState == SQUAD_IDLE ) { if ( !NPCInfo->goalEntity ) { AImove = qfalse; return; } //Should keep moving toward player when we're out of range... right? } */ //See if we're moving towards a goal, not the enemy if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) ) { //Did we make it? if ( NAV_HitNavGoal( NPC->currentOrigin, NPC->mins, NPC->maxs, NPCInfo->goalEntity->currentOrigin, 16, FlyingCreature( NPC ) ) || ( NPCInfo->squadState == SQUAD_SCOUT && enemyLOS && enemyDist <= 10000 ) ) { //int newSquadState = SQUAD_STAND_AND_SHOOT; //we got where we wanted to go, set timers based on why we were running switch ( NPCInfo->squadState ) { case SQUAD_RETREAT://was running away TIMER_Set( NPC, "duck", (NPC->max_health - NPC->health) * 100 ); TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) ); //newSquadState = SQUAD_COVER; break; case SQUAD_TRANSITION://was heading for a combat point TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) ); break; case SQUAD_SCOUT://was running after player break; default: break; } NPC_ReachedGoal(); //don't attack right away TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); //FIXME: Slant for difficulty levels //don't do something else just yet TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) ); //stop fleeing if ( NPCInfo->squadState == SQUAD_RETREAT ) { TIMER_Set( NPC, "flee", -level.time ); NPCInfo->squadState = SQUAD_IDLE; } return; } //keep going, hold of roamTimer until we get there TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) ); } if ( !NPCInfo->goalEntity ) { if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) { NPCInfo->goalEntity = NPC->enemy; } } }
/* ------------------------- NPC_BSDroid_Pain ------------------------- */ void NPC_Droid_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod,int hitLoc ) { int anim; float pain_chance; VectorCopy( self->NPC->lastPathAngles, self->s.angles ); if ( self->client->NPC_class == CLASS_R5D2 ) { pain_chance = NPC_GetPainChance( self, damage ); // Put it in pain if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance ) // Spin around in pain? Demp2 always does this { // Health is between 0-30 or was hit by a DEMP2 so pop his head if ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) { if (!(self->spawnflags & 2)) // Doesn't have to ALWAYSDIE { if ((self->NPC->localState != LSTATE_SPINNING) && (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "head" ))) { gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "head", TURN_OFF ); // G_PlayEffect( "small_chunks" , self->currentOrigin ); G_PlayEffect( "r5d2head", self->currentOrigin ); self->s.powerups |= ( 1 << PW_SHOCKED ); self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; TIMER_Set( self, "droidsmoketotal", 5000); TIMER_Set( self, "droidspark", 100); self->NPC->localState = LSTATE_SPINNING; } } } // Just give him normal pain for a little while else { anim = self->client->ps.legsAnim; if ( anim == BOTH_STAND2 ) // On two legs? { anim = BOTH_PAIN1; } else // On three legs { anim = BOTH_PAIN2; } NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); // Spin around in pain self->NPC->localState = LSTATE_SPINNING; TIMER_Set( self, "roam", Q_irand(1000,2000)); } } } else if (self->client->NPC_class == CLASS_MOUSE) { if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) { self->NPC->localState = LSTATE_SPINNING; self->s.powerups |= ( 1 << PW_SHOCKED ); self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; } else { self->NPC->localState = LSTATE_BACKINGUP; } self->NPC->scriptFlags &= ~SCF_LOOK_FOR_ENEMIES; } else if ((self->client->NPC_class == CLASS_R2D2)) { pain_chance = NPC_GetPainChance( self, damage ); if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance ) // Spin around in pain? Demp2 always does this { anim = self->client->ps.legsAnim; if ( anim == BOTH_STAND2 ) // On two legs? { anim = BOTH_PAIN1; } else // On three legs { anim = BOTH_PAIN2; } NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); // Spin around in pain self->NPC->localState = LSTATE_SPINNING; TIMER_Set( self, "roam", Q_irand(1000,2000)); } } else if ( self->client->NPC_class == CLASS_INTERROGATOR && ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) && other ) { vec3_t dir; VectorSubtract( self->currentOrigin, other->currentOrigin, dir ); VectorNormalize( dir ); VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity ); self->client->ps.velocity[2] -= 127; } NPC_Pain( self, inflictor, other, point, damage, mod); }
void Grenadier_ClearTimers( gentity_t *ent ) { TIMER_Set( ent, "chatter", 0 ); TIMER_Set( ent, "duck", 0 ); TIMER_Set( ent, "stand", 0 ); TIMER_Set( ent, "shuffleTime", 0 ); TIMER_Set( ent, "sleepTime", 0 ); TIMER_Set( ent, "enemyLastVisible", 0 ); TIMER_Set( ent, "roamTime", 0 ); TIMER_Set( ent, "hideTime", 0 ); TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels TIMER_Set( ent, "stick", 0 ); TIMER_Set( ent, "scoutTime", 0 ); TIMER_Set( ent, "flee", 0 ); }
void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) { int respawn = 0; if (!other->client) return; if (other->health < 1) return; // dead people can't pickup if ( other->client->ps.pm_time > 0 ) {//cant pick up when out of control return; } // Only monsters can pick it up if ((ent->spawnflags & ITMSF_MONSTER) && (other->client->playerTeam == TEAM_PLAYER)) { return; } // Only starfleet can pick it up if ((ent->spawnflags & ITMSF_TEAM) && (other->client->playerTeam != TEAM_PLAYER)) { return; } if ( other->client->NPC_class == CLASS_ATST || other->client->NPC_class == CLASS_GONK || other->client->NPC_class == CLASS_MARK1 || other->client->NPC_class == CLASS_MARK2 || other->client->NPC_class == CLASS_MOUSE || other->client->NPC_class == CLASS_PROBE || other->client->NPC_class == CLASS_PROTOCOL || other->client->NPC_class == CLASS_R2D2 || other->client->NPC_class == CLASS_R5D2 || other->client->NPC_class == CLASS_SEEKER || other->client->NPC_class == CLASS_REMOTE || other->client->NPC_class == CLASS_SENTRY ) {//FIXME: some flag would be better //droids can't pick up items/weapons! return; } //FIXME: need to make them run toward a dropped weapon when fleeing without one? //FIXME: need to make them come out of flee mode when pick up their old weapon? if ( CheckItemCanBePickedUpByNPC( ent, other ) ) { if ( other->NPC && other->NPC->goalEntity && other->NPC->goalEntity->enemy == ent ) {//they were running to pick me up, they did, so clear goal other->NPC->goalEntity = NULL; other->NPC->squadState = SQUAD_STAND_AND_SHOOT; } } else if (!(ent->spawnflags & ITMSF_TEAM) && !(ent->spawnflags & ITMSF_MONSTER)) {// Only player can pick it up if ( other->s.number != 0 ) // Not the player? { return; } } // the same pickup rules are used for client side and server side if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps ) ) { return; } if ( other->client ) { if ( other->client->ps.eFlags&EF_FORCE_GRIPPED ) {//can't pick up anything while being gripped return; } if ( PM_InKnockDown( &other->client->ps ) && !PM_InGetUp( &other->client->ps ) ) {//can't pick up while in a knockdown return; } } if (!ent->item) { //not an item! gi.Printf( "Touch_Item: %s is not an item!\n", ent->classname); return; } qboolean bHadWeapon = qfalse; // call the item-specific pickup function switch( ent->item->giType ) { case IT_WEAPON: if ( other->NPC && other->s.weapon == WP_NONE ) {//Make them duck and sit here for a few seconds int pickUpTime = Q_irand( 1000, 3000 ); TIMER_Set( other, "duck", pickUpTime ); TIMER_Set( other, "roamTime", pickUpTime ); TIMER_Set( other, "stick", pickUpTime ); TIMER_Set( other, "verifyCP", pickUpTime ); TIMER_Set( other, "attackDelay", 600 ); respawn = 0; } if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) ) { bHadWeapon = qtrue; } respawn = Pickup_Weapon(ent, other); break; case IT_AMMO: respawn = Pickup_Ammo(ent, other); break; case IT_ARMOR: respawn = Pickup_Armor(ent, other); break; case IT_HEALTH: respawn = Pickup_Health(ent, other); break; case IT_HOLDABLE: respawn = Pickup_Holdable(ent, other); break; case IT_BATTERY: respawn = Pickup_Battery( ent, other ); break; case IT_HOLOCRON: respawn = Pickup_Holocron( ent, other ); break; default: return; } if ( !respawn ) { return; } // play the normal pickup sound if ( !other->s.number && g_timescale->value < 1.0f ) {//SIGH... with timescale on, you lose events left and right extern void CG_ItemPickup( int itemNum, qboolean bHadItem ); // but we're SP so we'll cheat cgi_S_StartSound( NULL, other->s.number, CHAN_AUTO, cgi_S_RegisterSound( ent->item->pickup_sound ) ); // show icon and name on status bar CG_ItemPickup( ent->s.modelindex, bHadWeapon ); } else { if ( bHadWeapon ) { G_AddEvent( other, EV_ITEM_PICKUP, -ent->s.modelindex ); } else { G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex ); } } // fire item targets G_UseTargets (ent, other); // wait of -1 will not respawn // if ( ent->wait == -1 ) { //why not just remove me? G_FreeEntity( ent ); /* //NOTE: used to do this: (for respawning?) ent->svFlags |= SVF_NOCLIENT; ent->s.eFlags |= EF_NODRAW; ent->contents = 0; ent->unlinkAfterEvent = qtrue; */ return; } }
/* ------------------------- Sentry_Fire ------------------------- */ void Sentry_Fire (void) { vec3_t muzzle; static vec3_t forward, vright, up; gentity_t *missile; mdxaBone_t boltMatrix; int bolt, which; NPCS.NPC->flags &= ~FL_SHIELDED; if ( NPCS.NPCInfo->localState == LSTATE_POWERING_UP ) { if ( TIMER_Done( NPCS.NPC, "powerup" )) { NPCS.NPCInfo->localState = LSTATE_ATTACKING; NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } else { // can't do anything right now return; } } else if ( NPCS.NPCInfo->localState == LSTATE_ACTIVE ) { NPCS.NPCInfo->localState = LSTATE_POWERING_UP; G_Sound( NPCS.NPC, CHAN_AUTO, G_SoundIndex("sound/chars/sentry/misc/sentry_shield_open") ); NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPCS.NPC, "powerup", 250 ); return; } else if ( NPCS.NPCInfo->localState != LSTATE_ATTACKING ) { // bad because we are uninitialized NPCS.NPCInfo->localState = LSTATE_ACTIVE; return; } // Which muzzle to fire from? which = NPCS.NPCInfo->burstCount % 3; switch( which ) { case 0: bolt = trap_G2API_AddBolt(NPCS.NPC->ghoul2, 0, "*flash1"); break; case 1: bolt = trap_G2API_AddBolt(NPCS.NPC->ghoul2, 0, "*flash2"); break; case 2: default: bolt = trap_G2API_AddBolt(NPCS.NPC->ghoul2, 0, "*flash03"); } trap_G2API_GetBoltMatrix( NPCS.NPC->ghoul2, 0, bolt, &boltMatrix, NPCS.NPC->r.currentAngles, NPCS.NPC->r.currentOrigin, level.time, NULL, NPCS.NPC->modelScale ); BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, muzzle ); AngleVectors( NPCS.NPC->r.currentAngles, forward, vright, up ); // G_Sound( NPC, G_SoundIndex("sound/chars/sentry/misc/shoot.wav")); G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), muzzle, forward ); missile = CreateMissile( muzzle, forward, 1600, 10000, NPCS.NPC, qfalse ); missile->classname = "bryar_proj"; missile->s.weapon = WP_BRYAR_PISTOL; missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->methodOfDeath = MOD_BRYAR_PISTOL; missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; NPCS.NPCInfo->burstCount++; NPCS.NPC->attackDebounceTime = level.time + 50; missile->damage = 5; // now scale for difficulty if ( g_npcspskill.integer == 0 ) { NPCS.NPC->attackDebounceTime += 200; missile->damage = 1; } else if ( g_npcspskill.integer == 1 ) { NPCS.NPC->attackDebounceTime += 100; missile->damage = 3; } }
/* ------------------------- Mark2_AttackDecision ------------------------- */ void Mark2_AttackDecision( void ) { float distance; qboolean visible; qboolean advance; NPC_FaceEnemy( qtrue ); distance = (int) DistanceHorizontalSquared( NPCS.NPC->r.currentOrigin, NPCS.NPC->enemy->r.currentOrigin ); visible = NPC_ClearLOS4( NPCS.NPC->enemy ); advance = (qboolean)(distance > MIN_DISTANCE_SQR); // He's been ordered to get up if (NPCS.NPCInfo->localState == LSTATE_RISINGUP) { NPCS.NPC->flags &= ~FL_SHIELDED; NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_RUN1START, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); if ((NPCS.NPC->client->ps.legsTimer<=0) && NPCS.NPC->client->ps.torsoAnim == BOTH_RUN1START ) { NPCS.NPCInfo->localState = LSTATE_NONE; // He's up again. } return; } // If we cannot see our target, move to see it if ((!visible) || (!NPC_FaceEnemy(qtrue))) { // If he's going down or is down, make him get up if ((NPCS.NPCInfo->localState == LSTATE_DOWN) || (NPCS.NPCInfo->localState == LSTATE_DROPPINGDOWN)) { if ( TIMER_Done( NPCS.NPC, "downTime" ) ) // Down being down?? (The delay is so he doesn't pop up and down when the player goes in and out of range) { NPCS.NPCInfo->localState = LSTATE_RISINGUP; NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); TIMER_Set( NPCS.NPC, "runTime", Q_irand( 3000, 8000) ); // So he runs for a while before testing to see if he should drop down. } } else { Mark2_Hunt(); } return; } // He's down but he could advance if he wants to. if ((advance) && (TIMER_Done( NPCS.NPC, "downTime" )) && (NPCS.NPCInfo->localState == LSTATE_DOWN)) { NPCS.NPCInfo->localState = LSTATE_RISINGUP; NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); TIMER_Set( NPCS.NPC, "runTime", Q_irand( 3000, 8000) ); // So he runs for a while before testing to see if he should drop down. } NPC_FaceEnemy( qtrue ); // Dropping down to shoot if (NPCS.NPCInfo->localState == LSTATE_DROPPINGDOWN) { NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); TIMER_Set( NPCS.NPC, "downTime", Q_irand( 3000, 9000) ); if ((NPCS.NPC->client->ps.legsTimer<=0) && NPCS.NPC->client->ps.torsoAnim == BOTH_RUN1STOP ) { NPCS.NPC->flags |= FL_SHIELDED; NPCS.NPCInfo->localState = LSTATE_DOWN; } } // He's down and shooting else if (NPCS.NPCInfo->localState == LSTATE_DOWN) { NPCS.NPC->flags |= FL_SHIELDED;//only damagable by lightsabers and missiles Mark2_BlasterAttack(qfalse); } else if (TIMER_Done( NPCS.NPC, "runTime" )) // Lowering down to attack. But only if he's done running at you. { NPCS.NPCInfo->localState = LSTATE_DROPPINGDOWN; } else if (advance) { // We can see enemy so shoot him if timer lets you. Mark2_BlasterAttack(advance); } }
//------------------------------ void Wampa_Attack( float distance, qboolean doCharge ) { if ( !TIMER_Exists( NPC, "attacking" ) ) { if ( Q_irand(0, 2) && !doCharge ) {//double slash NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 750 ); } else if ( doCharge || (distance > 270 && distance < 430 && !Q_irand(0, 1)) ) {//leap vec3_t fwd, yawAng; VectorSet( yawAng, 0, NPC->client->ps.viewangles[YAW], 0 ); NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 500 ); AngleVectors( yawAng, fwd, NULL, NULL ); VectorScale( fwd, distance*1.5f, NPC->client->ps.velocity ); NPC->client->ps.velocity[2] = 150; NPC->client->ps.groundEntityNum = ENTITYNUM_NONE; } else {//backhand NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 250 ); } TIMER_Set( NPC, "attacking", NPC->client->ps.legsTimer + random() * 200 ); //allow us to re-evaluate our running speed/anim TIMER_Set( NPC, "runfar", -1 ); TIMER_Set( NPC, "runclose", -1 ); TIMER_Set( NPC, "walk", -1 ); } // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks if ( TIMER_Done2( NPC, "attack_dmg", qtrue ) ) { switch ( NPC->client->ps.legsAnim ) { case BOTH_ATTACK1: Wampa_Slash( NPC->client->renderInfo.handRBolt, qfalse ); //do second hit TIMER_Set( NPC, "attack_dmg2", 100 ); break; case BOTH_ATTACK2: Wampa_Slash( NPC->client->renderInfo.handRBolt, qfalse ); TIMER_Set( NPC, "attack_dmg2", 100 ); break; case BOTH_ATTACK3: Wampa_Slash( NPC->client->renderInfo.handLBolt, qtrue ); break; } } else if ( TIMER_Done2( NPC, "attack_dmg2", qtrue ) ) { switch ( NPC->client->ps.legsAnim ) { case BOTH_ATTACK1: Wampa_Slash( NPC->client->renderInfo.handLBolt, qfalse ); break; case BOTH_ATTACK2: Wampa_Slash( NPC->client->renderInfo.handLBolt, qfalse ); break; } } // Just using this to remove the attacking flag at the right time TIMER_Done2( NPC, "attacking", qtrue ); if ( NPC->client->ps.legsAnim == BOTH_ATTACK1 && distance > (NPC->r.maxs[0]+MIN_DISTANCE) ) {//okay to keep moving ucmd.buttons |= BUTTON_WALKING; Wampa_Move( 1 ); } }
void GM_Dying( gentity_t *self ) { if ( level.time - self->s.time < 4000 ) {//FIXME: need a real effect self->s.powerups |= ( 1 << PW_SHOCKED ); self->client->ps.powerups[PW_SHOCKED] = level.time + 1000; if ( TIMER_Done( self, "dyingExplosion" ) ) { int newBolt; switch ( Q_irand( 1, 14 ) ) { // Find place to generate explosion case 1: if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "r_hand" )) {//r_hand still there GM_CreateExplosion( self, self->handRBolt, qtrue ); gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "r_hand", TURN_OFF ); } else if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "r_arm_middle" )) {//r_arm_middle still there newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*r_arm_elbow" ); gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "r_arm_middle", TURN_OFF ); } break; case 2: //FIXME: do only once? if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "l_hand" )) {//l_hand still there GM_CreateExplosion( self, self->handLBolt ); gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "l_hand", TURN_OFF ); } else if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "l_arm_wrist" )) {//l_arm_wrist still there newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*l_arm_cap_l_hand" ); gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "l_arm_wrist", TURN_OFF ); } else if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "l_arm_middle" )) {//l_arm_middle still there newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*l_arm_cap_l_hand" ); gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "l_arm_middle", TURN_OFF ); } else if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "l_arm_augment" )) {//l_arm_augment still there newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*l_arm_elbow" ); gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "l_arm_augment", TURN_OFF ); } break; case 3: case 4: newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*hip_fr" ); GM_CreateExplosion( self, newBolt ); break; case 5: case 6: newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*shldr_l" ); GM_CreateExplosion( self, newBolt ); break; case 7: case 8: newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*uchest_r" ); GM_CreateExplosion( self, newBolt ); break; case 9: case 10: GM_CreateExplosion( self, self->headBolt ); break; case 11: newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*l_leg_knee" ); GM_CreateExplosion( self, newBolt, qtrue ); break; case 12: newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*r_leg_knee" ); GM_CreateExplosion( self, newBolt, qtrue ); break; case 13: newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*l_leg_foot" ); GM_CreateExplosion( self, newBolt, qtrue ); break; case 14: newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*r_leg_foot" ); GM_CreateExplosion( self, newBolt, qtrue ); break; } TIMER_Set( self, "dyingExplosion", Q_irand( 300, 1100 ) ); } } else {//one final, huge explosion G_PlayEffect( "galak/explode", self->currentOrigin ); // G_PlayEffect( "small_chunks", self->currentOrigin ); // G_PlayEffect( "env/exp_trail_comp", self->currentOrigin, self->currentAngles ); self->nextthink = level.time + FRAMETIME; self->e_ThinkFunc = thinkF_G_FreeEntity; } }
/* ------------------------- NPC_BSWampa_Default ------------------------- */ void NPC_BSWampa_Default( void ) { NPC->client->ps.eFlags2 &= ~EF2_USE_ALT_ANIM; //NORMAL ANIMS // stand1 = normal stand // walk1 = normal, non-angry walk // walk2 = injured // run1 = far away run // run2 = close run //VICTIM ANIMS // grabswipe = melee1 - sweep out and grab // stand2 attack = attack4 - while holding victim, swipe at him // walk3_drag = walk5 - walk with drag // stand2 = hold victim // stand2to1 = drop victim if ( !TIMER_Done( NPC, "rageTime" ) ) {//do nothing but roar first time we see an enemy NPC_FaceEnemy( qtrue ); return; } if ( NPC->enemy ) { if ( !TIMER_Done(NPC,"attacking") ) {//in middle of attack //face enemy NPC_FaceEnemy( qtrue ); //continue attack logic enemyDist = Distance( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); Wampa_Attack( enemyDist, qfalse ); return; } else { if ( TIMER_Done(NPC,"angrynoise") ) { G_Sound( NPC, CHAN_VOICE, G_SoundIndex( va("sound/chars/wampa/misc/anger%d.wav", Q_irand(1, 2)) ) ); TIMER_Set( NPC, "angrynoise", Q_irand( 5000, 10000 ) ); } //else, if he's in our hand, we eat, else if he's on the ground, we keep attacking his dead body for a while if( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_WAMPA ) {//got mad at another Wampa, look for a valid enemy if ( TIMER_Done( NPC, "wampaInfight" ) ) { NPC_CheckEnemyExt( qtrue ); } } else { if ( ValidEnemy( NPC->enemy ) == qfalse ) { TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now if ( !NPC->enemy->inuse || level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 ) ) {//it's been a while since the enemy died, or enemy is completely gone, get bored with him NPC->enemy = NULL; Wampa_Patrol(); NPC_UpdateAngles( qtrue, qtrue ); //just lost my enemy if ( (NPC->spawnflags&2) ) {//search around me if I don't have an enemy NPC_BSSearchStart( NPC->waypoint, BS_SEARCH ); NPCInfo->tempBehavior = BS_DEFAULT; } else if ( (NPC->spawnflags&1) ) {//wander if I don't have an enemy NPC_BSSearchStart( NPC->waypoint, BS_WANDER ); NPCInfo->tempBehavior = BS_DEFAULT; } return; } } if ( TIMER_Done( NPC, "lookForNewEnemy" ) ) { gentity_t *newEnemy, *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy? NPC->enemy = NULL; newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse ); NPC->enemy = sav_enemy; if ( newEnemy && newEnemy != sav_enemy ) {//picked up a new enemy! NPC->lastEnemy = NPC->enemy; G_SetEnemy( NPC, newEnemy ); //hold this one for at least 5-15 seconds TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); } else {//look again in 2-5 secs TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) ); } } } Wampa_Combat(); return; } } else { if ( TIMER_Done(NPC,"idlenoise") ) { G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/wampa/misc/anger3.wav" ) ); TIMER_Set( NPC, "idlenoise", Q_irand( 2000, 4000 ) ); } if ( (NPC->spawnflags&2) ) {//search around me if I don't have an enemy if ( NPCInfo->homeWp == WAYPOINT_NONE ) {//no homewap, initialize the search behavior NPC_BSSearchStart( WAYPOINT_NONE, BS_SEARCH ); NPCInfo->tempBehavior = BS_DEFAULT; } ucmd.buttons |= BUTTON_WALKING; NPC_BSSearch();//this automatically looks for enemies } else if ( (NPC->spawnflags&1) ) {//wander if I don't have an enemy if ( NPCInfo->homeWp == WAYPOINT_NONE ) {//no homewap, initialize the wander behavior NPC_BSSearchStart( WAYPOINT_NONE, BS_WANDER ); NPCInfo->tempBehavior = BS_DEFAULT; } ucmd.buttons |= BUTTON_WALKING; NPC_BSWander(); if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) { if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) { Wampa_Idle(); } else { Wampa_CheckRoar( NPC ); TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); } } } else { if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) { Wampa_Patrol(); } else { Wampa_Idle(); } } } NPC_UpdateAngles( qtrue, qtrue ); }
//------------------------------------ void Seeker_MaintainHeight( void ) { float dif; // Update our angles regardless NPC_UpdateAngles( qtrue, qtrue ); // If we have an enemy, we should try to hover at or a little below enemy eye level if ( NPC->enemy ) { if (TIMER_Done( NPC, "heightChange" )) { TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 )); // Find the height difference dif = (NPC->enemy->currentOrigin[2] + Q_flrand( NPC->enemy->maxs[2]/2, NPC->enemy->maxs[2]+8 )) - NPC->currentOrigin[2]; float difFactor = 1.0f; if ( NPC->client->NPC_class == CLASS_BOBAFETT ) { if ( TIMER_Done( NPC, "flameTime" ) ) { difFactor = 10.0f; } } // cap to prevent dramatic height shifts if ( fabs( dif ) > 2*difFactor ) { if ( fabs( dif ) > 24*difFactor ) { dif = ( dif < 0 ? -24*difFactor : 24*difFactor ); } NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; } if ( NPC->client->NPC_class == CLASS_BOBAFETT ) { NPC->client->ps.velocity[2] *= Q_flrand( 0.85f, 3.0f ); } } } else { gentity_t *goal = NULL; if ( NPCInfo->goalEntity ) // Is there a goal? { goal = NPCInfo->goalEntity; } else { goal = NPCInfo->lastGoalEntity; } if ( goal ) { dif = goal->currentOrigin[2] - NPC->currentOrigin[2]; if ( fabs( dif ) > 24 ) { ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); } else { if ( NPC->client->ps.velocity[2] ) { NPC->client->ps.velocity[2] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) { NPC->client->ps.velocity[2] = 0; } } } } } // Apply friction if ( NPC->client->ps.velocity[0] ) { NPC->client->ps.velocity[0] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) { NPC->client->ps.velocity[0] = 0; } } if ( NPC->client->ps.velocity[1] ) { NPC->client->ps.velocity[1] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) { NPC->client->ps.velocity[1] = 0; } } }