/* static void G_DynamicMusicUpdate( usercmd_t *ucmd ) FIXME: can we merge any of this with the G_ChooseLookEnemy stuff? */ static void G_DynamicMusicUpdate( void ) { gentity_t *ent; gentity_t *entityList[MAX_GENTITIES]; int numListedEntities; vec3_t mins, maxs; int i, e; int distSq, radius = 2048; vec3_t center; int danger = 0; int battle = 0; int entTeam; qboolean dangerNear = qfalse; qboolean suspicious = qfalse; qboolean LOScalced = qfalse, clearLOS = qfalse; //FIXME: intro and/or other cues? (one-shot music sounds) //loops //player-based if ( !player ) {//WTF? player = &g_entities[0]; return; } if ( !player->client || player->client->pers.teamState.state != TEAM_ACTIVE || level.time - player->client->pers.enterTime < 100 ) {//player hasn't spawned yet return; } if ( player->health <= 0 && player->max_health > 0 ) {//defeat music if ( level.dmState != DM_DEATH ) { level.dmState = DM_DEATH; } } if ( level.dmState == DM_DEATH ) { gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "death" ); return; } if ( level.dmState == DM_BOSS ) { gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "boss" ); return; } if ( level.dmState == DM_SILENCE ) { gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "silence" ); return; } if ( level.dmBeatTime > level.time ) {//not on a beat return; } level.dmBeatTime = level.time + 1000;//1 second beats if ( player->health <= 20 ) { danger = 1; } //enemy-based VectorCopy( player->currentOrigin, center ); for ( i = 0 ; i < 3 ; i++ ) { mins[i] = center[i] - radius; maxs[i] = center[i] + radius; } numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for ( e = 0 ; e < numListedEntities ; e++ ) { ent = entityList[ e ]; if ( !ent || !ent->inuse ) { continue; } if ( !ent->client || !ent->NPC ) { if ( ent->classname && (!Q_stricmp( "PAS", ent->classname )||!Q_stricmp( "misc_turret", ent->classname )) ) {//a turret entTeam = ent->noDamageTeam; } else { continue; } } else {//an NPC entTeam = ent->client->playerTeam; } if ( entTeam == player->client->playerTeam ) {//ally continue; } if ( entTeam == TEAM_NEUTRAL && (!ent->enemy || !ent->enemy->client || ent->enemy->client->playerTeam != player->client->playerTeam) ) {//a droid that is not mad at me or my allies continue; } if ( !gi.inPVS( player->currentOrigin, ent->currentOrigin ) ) {//not potentially visible continue; } if ( ent->client && ent->s.weapon == WP_NONE ) {//they don't have a weapon... FIXME: only do this for droids? continue; } LOScalced = clearLOS = qfalse; if ( (ent->enemy==player&&(!ent->NPC||ent->NPC->confusionTime<level.time)) || (ent->client&&ent->client->ps.weaponTime) || (!ent->client&&ent->attackDebounceTime>level.time)) {//mad if ( ent->health > 0 ) {//alive //FIXME: do I really need this check? if ( ent->s.weapon == WP_SABER && ent->client && !ent->client->ps.saberActive && ent->enemy != player ) {//a Jedi who has not yet gotten made at me continue; } if ( ent->NPC && ent->NPC->behaviorState == BS_CINEMATIC ) {//they're not actually going to do anything about being mad at me... continue; } //okay, they're in my PVS, but how close are they? Are they actively attacking me? if ( !ent->client && ent->s.weapon == WP_TURRET && ent->fly_sound_debounce_time && ent->fly_sound_debounce_time - level.time < 10000 ) {//a turret that shot at me less than ten seconds ago } else if ( ent->client && ent->client->ps.lastShotTime && ent->client->ps.lastShotTime - level.time < 10000 ) {//an NPC that shot at me less than ten seconds ago } else {//not actively attacking me lately, see how far away they are distSq = DistanceSquared( ent->currentOrigin, player->currentOrigin ); if ( distSq > 4194304/*2048*2048*/ ) {//> 2048 away continue; } else if ( distSq > 1048576/*1024*1024*/ ) {//> 1024 away clearLOS = G_ClearLOS( player, player->client->renderInfo.eyePoint, ent ); LOScalced = qtrue; if ( clearLOS == qfalse ) {//No LOS continue; } } } battle++; } } if ( level.dmState == DM_EXPLORE ) {//only do these visibility checks if you're still in exploration mode if ( !InFront( ent->currentOrigin, player->currentOrigin, player->client->ps.viewangles, 0.0f) ) {//not in front continue; } if ( !LOScalced ) { clearLOS = G_ClearLOS( player, player->client->renderInfo.eyePoint, ent ); } if ( !clearLOS ) {//can't see them directly continue; } } if ( ent->health <= 0 ) {//dead if ( !ent->client || level.time - ent->s.time > 10000 ) {//corpse has been dead for more than 10 seconds //FIXME: coming across corpses should cause danger sounds too? continue; } } //we see enemies and/or corpses danger++; } if ( !battle ) {//no active enemies, but look for missiles, shot impacts, etc... int alert = G_CheckAlertEvents( player, qtrue, qtrue, 1024, 1024, -1, qfalse, AEL_SUSPICIOUS ); if ( alert != -1 ) {//FIXME: maybe tripwires and other FIXED things need their own sound, some kind of danger/caution theme if ( G_CheckForDanger( player, alert ) ) {//found danger near by danger++; battle = 1; } else if ( level.alertEvents[alert].owner && (level.alertEvents[alert].owner == player->enemy || (level.alertEvents[alert].owner->client && level.alertEvents[alert].owner->client->playerTeam == player->client->enemyTeam) ) ) {//NPC on enemy team of player made some noise switch ( level.alertEvents[alert].level ) { case AEL_DISCOVERED: dangerNear = qtrue; break; case AEL_SUSPICIOUS: suspicious = qtrue; break; case AEL_MINOR: //distraction = qtrue; break; } } } } if ( battle ) {//battle - this can interrupt level.dmDebounceTime of lower intensity levels //play battle if ( level.dmState != DM_ACTION ) { gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "action" ); } level.dmState = DM_ACTION; if ( battle > 5 ) { //level.dmDebounceTime = level.time + 8000;//don't change again for 5 seconds } else { //level.dmDebounceTime = level.time + 3000 + 1000*battle; } } else { if ( level.dmDebounceTime > level.time ) {//not ready to switch yet return; } else {//at least 1 second (for beats) //level.dmDebounceTime = level.time + 1000;//FIXME: define beat time? } /* if ( danger || dangerNear ) {//danger //stay on whatever we were on, action or exploration if ( !danger ) {//minimum danger = 1; } if ( danger > 3 ) { level.dmDebounceTime = level.time + 5000; } else { level.dmDebounceTime = level.time + 2000 + 1000*danger; } } else */ {//still nothing dangerous going on if ( level.dmState != DM_EXPLORE ) {//just went to explore, hold it for a couple seconds at least //level.dmDebounceTime = level.time + 2000; gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "explore" ); } level.dmState = DM_EXPLORE; //FIXME: look for interest points and play "mysterious" music instead of exploration? //FIXME: suspicious and distraction sounds should play some cue or change music in a subtle way? //play exploration } //FIXME: when do we go to silence? } }
void DoGripAction(gentity_t *self, forcePowers_t forcePower) {//racc - have someone in our grip, deal with them. gentity_t *gripEnt; int gripLevel = 0; trace_t tr; vec3_t a; self->client->dangerTime = level.time; self->client->ps.eFlags &= ~EF_INVULNERABLE; self->client->invulnerableTimer = 0; gripEnt = &g_entities[self->client->ps.fd.forceGripEntityNum]; if (!ValidGripEnt(self,gripEnt)) { WP_ForcePowerStop(self,FP_GRIP); self->client->ps.fd.forceGripEntityNum = ENTITYNUM_NONE; if (gripEnt && gripEnt->client && gripEnt->inuse) { gripEnt->client->ps.forceGripChangeMovetype = PM_NORMAL; } return; } if(gripEnt->client) VectorSubtract(gripEnt->client->ps.origin, self->client->ps.origin, a); else VectorSubtract(gripEnt->s.pos.trBase,self->client->ps.origin,a); if(gripEnt->client) trap_Trace(&tr, self->client->ps.origin, NULL, NULL, gripEnt->client->ps.origin, self->s.number, MASK_PLAYERSOLID); else trap_Trace(&tr, self->client->ps.origin, NULL, NULL, gripEnt->s.pos.trBase, self->s.number, MASK_PLAYERSOLID); //[ForceSys] gripLevel = self->client->ps.fd.forcePowerLevel[FP_GRIP]; if (VectorLength(a) > MAX_GRIP_DISTANCE) { WP_ForcePowerStop(self, forcePower); return; } if(gripEnt->client) { if ( !InFront( gripEnt->client->ps.origin, self->client->ps.origin, self->client->ps.viewangles, 0.9f ) && gripLevel < FORCE_LEVEL_3) { WP_ForcePowerStop(self, forcePower); return; } } else { if ( !InFront( gripEnt->s.pos.trBase, self->client->ps.origin, self->client->ps.viewangles, 0.9f ) && gripLevel < FORCE_LEVEL_3) { WP_ForcePowerStop(self, forcePower); return; } } if (self->client->ps.fd.forcePowerDebounce[FP_GRIP] < level.time) { //2 damage per second while choking, resulting in 10 damage total (not including The Squeeze<tm>) self->client->ps.fd.forcePowerDebounce[FP_GRIP] = level.time + 1000; G_Damage(gripEnt, self, self, NULL, NULL, 2, DAMAGE_NO_ARMOR, MOD_FORCE_DARK); } Jetpack_Off(gripEnt); //make sure the guy being gripped has his jetpack off. if (gripLevel == FORCE_LEVEL_1) { if(gripEnt->client) gripEnt->client->ps.fd.forceGripBeingGripped = level.time + 1000; if(gripEnt->client) if ((level.time - gripEnt->client->ps.fd.forceGripStarted) > 5000) { WP_ForcePowerStop(self, forcePower); return; } } if (gripLevel == FORCE_LEVEL_2||gripLevel==FORCE_LEVEL_3) { gripEnt->client->ps.fd.forceGripBeingGripped = level.time + 1000; if (gripEnt->client->ps.forceGripMoveInterval < level.time) { if(gripLevel == FORCE_LEVEL_2) gripEnt->client->ps.velocity[2] = 15; else gripEnt->client->ps.velocity[2] = 30; gripEnt->client->ps.forceGripMoveInterval = level.time + 300; //only update velocity every 300ms, so as to avoid heavy bandwidth usage } gripEnt->client->ps.otherKiller = self->s.number; gripEnt->client->ps.otherKillerTime = level.time + 5000; gripEnt->client->ps.otherKillerDebounceTime = level.time + 100; //[Asteroids] gripEnt->client->otherKillerMOD = MOD_UNKNOWN; gripEnt->client->otherKillerVehWeapon = 0; gripEnt->client->otherKillerWeaponType = WP_NONE; //[/Asteroids] gripEnt->client->ps.forceGripChangeMovetype = PM_FLOAT; if(gripEnt->client) { if ((level.time - gripEnt->client->ps.fd.forceGripStarted) > 3000 && !self->client->ps.fd.forceGripDamageDebounceTime) { //if we managed to lift him into the air for 2 seconds, give him a crack self->client->ps.fd.forceGripDamageDebounceTime = 1; if(gripLevel == FORCE_LEVEL_2) G_Damage(gripEnt, self, self, NULL, NULL, 20, DAMAGE_NO_ARMOR, MOD_FORCE_DARK); else G_Damage(gripEnt, self, self, NULL, NULL, 75, DAMAGE_NO_ARMOR, MOD_FORCE_DARK); //Must play custom sounds on the actual entity. Don't use G_Sound (it creates a temp entity for the sound) G_EntitySound( gripEnt, CHAN_VOICE, G_SoundIndex(va( "*choke%d.wav", Q_irand( 1, 3 ) )) ); gripEnt->client->ps.forceHandExtend = HANDEXTEND_CHOKE; gripEnt->client->ps.forceHandExtendTime = level.time + 2000; if (gripEnt->client->ps.fd.forcePowersActive & (1 << FP_GRIP)) { //choking, so don't let him keep gripping himself WP_ForcePowerStop(gripEnt, FP_GRIP); return; } } else if ((level.time - gripEnt->client->ps.fd.forceGripStarted) > 4000) { WP_ForcePowerStop(self, forcePower); return; } } } }
float BoidsManager::MatchAndAvoidNeighbors(short theBoid, Velocity *matchNeighborVel, Velocity *avoidNeighborVel) { short i, j, neighbor; double distSqr; double dist, distH, distV; double tempSpeed; short numClose = 0; Velocity totalVel = {0.0,0.0}; /**********************/ /* Find the neighbors */ /**********************/ /* special case of one neighbor */ if (m_numNeighbors == 1) { m_boids[theBoid].neighborDistSqr[0] = kMaxLong; for (i = 0; i < m_numBoids; i++) { if (i != theBoid) { distSqr = DistSqrToPt(m_boids[theBoid].oldPos, m_boids[i].oldPos); /* if this one is closer than the closest so far, then remember it */ if (m_boids[theBoid].neighborDistSqr[0] > distSqr) { m_boids[theBoid].neighborDistSqr[0] = distSqr; m_boids[theBoid].neighbor[0] = i; } } } } /* more than one neighbor */ else { for (j = 0; j < m_numNeighbors; j++) m_boids[theBoid].neighborDistSqr[j] = kMaxLong; for (i = 0 ; i < m_numBoids; i++) { /* if this one is not me... */ if (i != theBoid) { distSqr = DistSqrToPt(m_boids[theBoid].oldPos, m_boids[i].oldPos); /* if distSqr is less than the distance at the bottom of the array, sort into array */ if (distSqr < m_boids[theBoid].neighborDistSqr[m_numNeighbors-1]) { j = m_numNeighbors - 1; /* sort distSqr in to keep array in size order, smallest first */ while ((distSqr < m_boids[theBoid].neighborDistSqr[j-1]) && (j > 0)) { m_boids[theBoid].neighborDistSqr[j] = m_boids[theBoid].neighborDistSqr[j - 1]; m_boids[theBoid].neighbor[j] = m_boids[theBoid].neighbor[j - 1]; j--; } m_boids[theBoid].neighborDistSqr[j] = distSqr; m_boids[theBoid].neighbor[j] = i; } } } } /*********************************/ /* Match and avoid the neighbors */ /*********************************/ matchNeighborVel->x = 0; matchNeighborVel->y = 0; // set tempSpeed to old speed tempSpeed = m_boids[theBoid].speed; for (i = 0; i < m_numNeighbors; i++) { neighbor = m_boids[theBoid].neighbor[i]; // calculate matchNeighborVel by averaging the neighbor velocities matchNeighborVel->x += m_boids[neighbor].oldDir.x; matchNeighborVel->y += m_boids[neighbor].oldDir.y; // if distance is less than preferred distance, then neighbor influences boid distSqr = m_boids[theBoid].neighborDistSqr[i]; if (distSqr < m_prefDistSqr) { dist = sqrt(distSqr); distH = m_boids[neighbor].oldPos.x - m_boids[theBoid].oldPos.x; distV = m_boids[neighbor].oldPos.y - m_boids[theBoid].oldPos.y; if(dist == 0.0) dist = 0.0000001; totalVel.x = totalVel.x - distH - (distH * ((float) m_prefDist / (dist))); totalVel.y = totalVel.y - distV - (distV * ((float) m_prefDist / (dist))); numClose++; } // adjust speed if (InFront(&(m_boids[theBoid]), &(m_boids[neighbor]))) { if (distSqr < m_prefDistSqr) tempSpeed /= m_accelFactor; //tempSpeed /= (m_accelFactor / 100.0); else tempSpeed *= m_accelFactor; } else { if (distSqr < m_prefDistSqr) tempSpeed *= m_accelFactor; else tempSpeed /= m_accelFactor; } } if (numClose) { avoidNeighborVel->x = totalVel.x / numClose; avoidNeighborVel->y = totalVel.y / numClose; NormalizeVelocity(matchNeighborVel); } else { avoidNeighborVel->x = 0; avoidNeighborVel->y = 0; } return(tempSpeed); }
//------------------------------------------------------ void G_MissileImpact( gentity_t *ent, trace_t *trace, int hitLoc=HL_NONE ) { gentity_t *other; vec3_t diff; other = &g_entities[trace->entityNum]; if ( other == ent ) { assert(0&&"missile hit itself!!!"); return; } if ( trace->plane.normal[0] == 0.0f && trace->plane.normal[1] == 0.0f && trace->plane.normal[2] == 0.0f ) {//model moved into missile in flight probably... trace->plane.normal[0] = -ent->s.pos.trDelta[0]; trace->plane.normal[1] = -ent->s.pos.trDelta[1]; trace->plane.normal[2] = -ent->s.pos.trDelta[2]; VectorNormalize(trace->plane.normal); } if ( ent->owner && (other->takedamage||other->client) ) { if ( !ent->lastEnemy || ent->lastEnemy == ent->owner ) {//a missile that was not reflected or, if so, still is owned by original owner if( LogAccuracyHit( other, ent->owner ) ) { ent->owner->client->ps.persistant[PERS_ACCURACY_HITS]++; } if ( ent->owner->client && !ent->owner->s.number ) { if ( W_AccuracyLoggableWeapon( ent->s.weapon, qfalse, ent->methodOfDeath ) ) { ent->owner->client->sess.missionStats.hits++; } } } } // check for bounce //OR: if the surfaceParm is has a reflect property (magnetic shielding) and the missile isn't an exploding missile qboolean bounce = !!( (!other->takedamage && (ent->s.eFlags&(EF_BOUNCE|EF_BOUNCE_HALF))) || (((trace->surfaceFlags&SURF_FORCEFIELD)||(other->flags&FL_SHIELDED))&&!ent->splashDamage&&!ent->splashRadius&&ent->s.weapon != WP_NOGHRI_STICK) ); if ( ent->dflags & DAMAGE_HEAVY_WEAP_CLASS ) { // heavy class missiles generally never bounce. bounce = qfalse; } if ( other->flags & (FL_DMG_BY_HEAVY_WEAP_ONLY | FL_SHIELDED )) { // Dumb assumption, but I guess we must be a shielded ion_cannon?? We should probably verify // if it's an ion_cannon that's Heavy Weapon only, we don't want to make it shielded do we...? if ( (!strcmp( "misc_ion_cannon", other->classname )) && (other->flags & FL_SHIELDED) ) { // Anything will bounce off of us. bounce = qtrue; // Not exactly the debounce time, but rather the impact time for the shield effect...play effect for 1 second other->painDebounceTime = level.time + 1000; } } if ( ent->s.weapon == WP_DEMP2 ) { // demp2 shots can never bounce bounce = qfalse; // in fact, alt-charge shots will not call the regular impact functions if ( ent->alt_fire ) { // detonate at the trace end VectorCopy( trace->endpos, ent->currentOrigin ); VectorCopy( trace->plane.normal, ent->pos1 ); DEMP2_AltDetonate( ent ); return; } } if ( bounce ) { // Check to see if there is a bounce count if ( ent->bounceCount ) { // decrement number of bounces and then see if it should be done bouncing if ( !(--ent->bounceCount) ) { // He (or she) will bounce no more (after this current bounce, that is). ent->s.eFlags &= ~( EF_BOUNCE | EF_BOUNCE_HALF ); } } if ( other->NPC ) { G_Damage( other, ent, ent->owner, ent->currentOrigin, ent->s.pos.trDelta, 0, DAMAGE_NO_DAMAGE, MOD_UNKNOWN ); } G_BounceMissile( ent, trace ); if ( ent->owner )//&& ent->owner->s.number == 0 ) { G_MissileAddAlerts( ent ); } G_MissileBounceEffect( ent, trace->endpos, trace->plane.normal, trace->entityNum==ENTITYNUM_WORLD ); return; } // I would glom onto the EF_BOUNCE code section above, but don't feel like risking breaking something else if ( (!other->takedamage && ( ent->s.eFlags&(EF_BOUNCE_SHRAPNEL) ) ) || ((trace->surfaceFlags&SURF_FORCEFIELD)&&!ent->splashDamage&&!ent->splashRadius) ) { if ( !(other->contents&CONTENTS_LIGHTSABER) || g_spskill->integer <= 0//on easy, it reflects all shots || (g_spskill->integer == 1 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 )//on medium it won't reflect flechette or demp shots || (g_spskill->integer >= 2 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 && ent->s.weapon != WP_BOWCASTER && ent->s.weapon != WP_REPEATER )//on hard it won't reflect flechette, demp, repeater or bowcaster shots ) { G_BounceMissile( ent, trace ); if ( --ent->bounceCount < 0 ) { ent->s.eFlags &= ~EF_BOUNCE_SHRAPNEL; } G_MissileBounceEffect( ent, trace->endpos, trace->plane.normal, trace->entityNum==ENTITYNUM_WORLD ); return; } } if ( (!other->takedamage || (other->client && other->health <= 0)) && ent->s.weapon == WP_THERMAL && !ent->alt_fire ) {//rolling thermal det - FIXME: make this an eFlag like bounce & stick!!! //G_BounceRollMissile( ent, trace ); if ( ent->owner )//&& ent->owner->s.number == 0 ) { G_MissileAddAlerts( ent ); } //gi.linkentity( ent ); return; } // check for sticking if ( ent->s.eFlags & EF_MISSILE_STICK ) { if ( ent->owner )//&& ent->owner->s.number == 0 ) { //Add the event if ( ent->s.weapon == WP_TRIP_MINE ) { AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius/2, AEL_DISCOVERED/*AEL_DANGER*/, qfalse, qtrue ); AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DISCOVERED/*AEL_DANGER*/, 60 ); /* AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, qfalse, qtrue ); AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, 60 ); */ } else { AddSoundEvent( ent->owner, ent->currentOrigin, 128, AEL_DISCOVERED, qfalse, qtrue ); AddSightEvent( ent->owner, ent->currentOrigin, 256, AEL_DISCOVERED, 10 ); } } G_MissileStick( ent, other, trace ); return; } extern bool WP_DoingMoronicForcedAnimationForForcePowers(gentity_t *ent); // check for hitting a lightsaber if ( other->contents & CONTENTS_LIGHTSABER ) { if ( other->owner && !other->owner->s.number && other->owner->client ) { other->owner->client->sess.missionStats.saberBlocksCnt++; } if ( ( g_spskill->integer <= 0//on easy, it reflects all shots || (g_spskill->integer == 1 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 )//on medium it won't reflect flechette or demp shots || (g_spskill->integer >= 2 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 && ent->s.weapon != WP_BOWCASTER && ent->s.weapon != WP_REPEATER )//on hard it won't reflect flechette, demp, repeater or bowcaster shots ) && (!ent->splashDamage || !ent->splashRadius) //this would be cool, though, to "bat" the thermal det away... && ent->s.weapon != WP_NOGHRI_STICK )//gas bomb, don't reflect { //FIXME: take other's owner's FP_SABER_DEFENSE into account here somehow? if ( !other->owner || !other->owner->client || other->owner->client->ps.saberInFlight || (InFront( ent->currentOrigin, other->owner->currentOrigin, other->owner->client->ps.viewangles, SABER_REFLECT_MISSILE_CONE ) && !WP_DoingMoronicForcedAnimationForForcePowers(other)) )//other->owner->s.number != 0 || {//Jedi cannot block shots from behind! int blockChance = 0; switch ( other->owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) {//level 1 reflects 50% of the time, level 2 reflects 75% of the time case FORCE_LEVEL_3: blockChance = 10; break; case FORCE_LEVEL_2: blockChance = 3; break; case FORCE_LEVEL_1: blockChance = 1; break; } if ( blockChance && (other->owner->client->ps.forcePowersActive&(1<<FP_SPEED)) ) {//in in force speed, better chance of deflecting the shot blockChance += other->owner->client->ps.forcePowerLevel[FP_SPEED]*2; } if ( Q_irand( 0, blockChance ) ) { VectorSubtract(ent->currentOrigin, other->currentOrigin, diff); VectorNormalize(diff); G_ReflectMissile( other, ent, diff); if ( other->owner && other->owner->client ) { other->owner->client->ps.saberEventFlags |= SEF_DEFLECTED; } //do the effect VectorCopy( ent->s.pos.trDelta, diff ); VectorNormalize( diff ); G_MissileReflectEffect( ent, trace->endpos, trace->plane.normal ); return; } } } else {//still do the bounce effect G_MissileReflectEffect( ent, trace->endpos, trace->plane.normal ); } } G_MissileImpacted( ent, other, trace->endpos, trace->plane.normal, hitLoc ); }
void G_RunMissile( gentity_t *ent ) { vec3_t oldOrg; trace_t tr; int trHitLoc=HL_NONE; if ( (ent->s.eFlags&EF_HELD_BY_SAND_CREATURE) ) {//in a sand creature's mouth if ( ent->activator ) { mdxaBone_t boltMatrix; // Getting the bolt here //in hand vec3_t scAngles = {0}; scAngles[YAW] = ent->activator->currentAngles[YAW]; gi.G2API_GetBoltMatrix( ent->activator->ghoul2, ent->activator->playerModel, ent->activator->gutBolt, &boltMatrix, scAngles, ent->activator->currentOrigin, (cg.time?cg.time:level.time), NULL, ent->activator->s.modelScale ); // Storing ent position, bolt position, and bolt axis gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->currentOrigin ); G_SetOrigin( ent, ent->currentOrigin ); } // check think function G_RunThink( ent ); return; } VectorCopy( ent->currentOrigin, oldOrg ); // get current position if ( ent->s.pos.trType == TR_INTERPOLATE ) {//rolling missile? //FIXME: WTF?!! Sticks to stick missiles? //FIXME: they stick inside the player G_RollMissile( ent ); if ( ent->s.eType != ET_GENERAL ) {//didn't explode VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); gi.trace( &tr, oldOrg, ent->mins, ent->maxs, ent->currentOrigin, ent->s.number, ent->clipmask, G2_RETURNONHIT, 10 ); if ( VectorCompare( ent->s.pos.trDelta, vec3_origin ) ) { //VectorCopy( ent->currentAngles, ent->s.apos.trBase ); VectorClear( ent->s.apos.trDelta ); } else { vec3_t ang, fwdDir, rtDir; float speed; ent->s.apos.trType = TR_INTERPOLATE; VectorSet( ang, 0, ent->s.apos.trBase[1], 0 ); AngleVectors( ang, fwdDir, rtDir, NULL ); speed = VectorLength( ent->s.pos.trDelta )*4; //HMM, this works along an axis-aligned dir, but not along diagonals //This is because when roll gets to 90, pitch becomes yaw, and vice-versa //Maybe need to just set the angles directly? ent->s.apos.trDelta[0] = DotProduct( fwdDir, ent->s.pos.trDelta ); ent->s.apos.trDelta[1] = 0;//never spin! ent->s.apos.trDelta[2] = DotProduct( rtDir, ent->s.pos.trDelta ); VectorNormalize( ent->s.apos.trDelta ); VectorScale( ent->s.apos.trDelta, speed, ent->s.apos.trDelta ); ent->s.apos.trTime = level.previousTime; } } } else { vec3_t origin; EvaluateTrajectory( &ent->s.pos, level.time, origin ); // trace a line from the previous position to the current position, // ignoring interactions with the missile owner gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, ent->owner ? ent->owner->s.number : ent->s.number, ent->clipmask, G2_COLLIDE, 10 ); if ( tr.entityNum != ENTITYNUM_NONE ) { gentity_t *other = &g_entities[tr.entityNum]; // check for hitting a lightsaber if ( other->contents & CONTENTS_LIGHTSABER ) {//hit a lightsaber bbox if ( other->owner && other->owner->client && !other->owner->client->ps.saberInFlight && ( Q_irand( 0, (other->owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]*other->owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) ) == 0 || !InFront( ent->currentOrigin, other->owner->currentOrigin, other->owner->client->ps.viewangles, SABER_REFLECT_MISSILE_CONE ) ) )//other->owner->s.number == 0 && {//Jedi cannot block shots from behind! //re-trace from here, ignoring the lightsaber gi.trace( &tr, tr.endpos, ent->mins, ent->maxs, origin, tr.entityNum, ent->clipmask, G2_RETURNONHIT, 10 ); } } } VectorCopy( tr.endpos, ent->currentOrigin ); } // get current angles VectorMA( ent->s.apos.trBase, (level.time - ent->s.apos.trTime) * 0.001, ent->s.apos.trDelta, ent->s.apos.trBase ); //FIXME: Rolling things hitting G2 polys is weird /////////////////////////////////////////////////////// //? if ( tr.fraction != 1 ) { // did we hit or go near a Ghoul2 model? // qboolean hitModel = qfalse; for (int i=0; i < MAX_G2_COLLISIONS; i++) { if (tr.G2CollisionMap[i].mEntityNum == -1) { break; } CCollisionRecord &coll = tr.G2CollisionMap[i]; gentity_t *hitEnt = &g_entities[coll.mEntityNum]; // process collision records here... // make sure we only do this once, not for all the entrance wounds we might generate if ((coll.mFlags & G2_FRONTFACE)/* && !(hitModel)*/ && hitEnt->health) { if (trHitLoc==HL_NONE) { G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex ), &trHitLoc, coll.mCollisionPosition, NULL, NULL, ent->methodOfDeath ); } break; // NOTE: the way this whole section was working, it would only get inside of this IF once anyway, might as well break out now } } } ///////////////////////////////////////////////////////// if ( tr.startsolid ) { tr.fraction = 0; } gi.linkentity( ent ); if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) ) {//stuck missiles should check some special stuff G_RunStuckMissile( ent ); return; } // check think function G_RunThink( ent ); if ( ent->s.eType != ET_MISSILE ) { return; // exploded } if ( ent->mass ) { G_MoverTouchPushTriggers( ent, oldOrg ); } /* if ( !(ent->s.eFlags & EF_TELEPORT_BIT) ) { G_MoverTouchTeleportTriggers( ent, oldOrg ); if ( ent->s.eFlags & EF_TELEPORT_BIT ) {//was teleported return; } } else { ent->s.eFlags &= ~EF_TELEPORT_BIT; } */ AddSightEvent( ent->owner, ent->currentOrigin, 512, AEL_DISCOVERED, 75 );//wakes them up when see a shot passes in front of them if ( !Q_irand( 0, 10 ) ) {//not so often... if ( ent->splashDamage && ent->splashRadius ) {//I'm an exploder, let people around me know danger is coming if ( ent->s.weapon == WP_TRIP_MINE ) {//??? } else { if ( ent->s.weapon == WP_ROCKET_LAUNCHER && ent->e_ThinkFunc == thinkF_rocketThink ) {//homing rocket- run like hell! AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius, AEL_DANGER_GREAT, 50 ); } else { AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius, AEL_DANGER, 50 ); } AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius, AEL_DANGER ); } } else {//makes them run from near misses AddSightEvent( ent->owner, ent->currentOrigin, 48, AEL_DANGER, 50 ); } } if ( tr.fraction == 1 ) { if ( ent->s.weapon == WP_THERMAL && ent->s.pos.trType == TR_INTERPOLATE ) {//a rolling thermal that didn't hit anything G_MissileAddAlerts( ent ); } return; } // never explode or bounce on sky if ( tr.surfaceFlags & SURF_NOIMPACT ) { G_FreeEntity( ent ); return; } G_MissileImpact( ent, &tr, trHitLoc ); }
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 G_RunMissile( gentity_t *ent ) { vec3_t origin, oldOrg; trace_t tr; int trHitLoc=HL_NONE; VectorCopy( ent->currentOrigin, oldOrg ); // get current position if ( ent->s.pos.trType == TR_INTERPOLATE ) {//rolling missile? //FIXME: WTF?!! Sticks to stick missiles? //FIXME: they stick inside the player G_RollMissile( ent ); if ( ent->s.eType != ET_GENERAL ) {//didn't explode VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); gi.trace( &tr, oldOrg, ent->mins, ent->maxs, ent->currentOrigin, ent->s.number, ent->clipmask, G2_RETURNONHIT, 10 ); if ( VectorCompare( ent->s.pos.trDelta, vec3_origin ) ) { //VectorCopy( ent->currentAngles, ent->s.apos.trBase ); VectorClear( ent->s.apos.trDelta ); } else { vec3_t ang, fwdDir, rtDir; float speed; ent->s.apos.trType = TR_INTERPOLATE; VectorSet( ang, 0, ent->s.apos.trBase[1], 0 ); AngleVectors( ang, fwdDir, rtDir, NULL ); speed = VectorLength( ent->s.pos.trDelta )*4; //HMM, this works along an axis-aligned dir, but not along diagonals //This is because when roll gets to 90, pitch becomes yaw, and vice-versa //Maybe need to just set the angles directly? ent->s.apos.trDelta[0] = DotProduct( fwdDir, ent->s.pos.trDelta ); ent->s.apos.trDelta[1] = 0;//never spin! ent->s.apos.trDelta[2] = DotProduct( rtDir, ent->s.pos.trDelta ); VectorNormalize( ent->s.apos.trDelta ); VectorScale( ent->s.apos.trDelta, speed, ent->s.apos.trDelta ); ent->s.apos.trTime = level.previousTime; } } } else { EvaluateTrajectory( &ent->s.pos, level.time, origin ); // trace a line from the previous position to the current position, // ignoring interactions with the missile owner /* gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, ent->owner ? ent->owner->s.number : ENTITYNUM_NONE, ent->clipmask, G2_RETURNONHIT, 10 ); */ gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, ent->owner ? ent->owner->s.number : ent->s.number, ent->clipmask, G2_COLLIDE, 10 ); /* if ( !VectorCompare( ent->mins, vec3_origin ) || !VectorCompare( ent->maxs, vec3_origin ) ) {//don't do ghoul trace if ent has size because g2 just ignores that anyway gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, ent->owner ? ent->owner->s.number : ENTITYNUM_NONE, ent->clipmask, G2_NOCOLLIDE, 10 ); } else //Now we always do ghoul trace, regardless of bbox size of missile, this is presuming that non-point ghoul traces will be possible...? { gi.trace( &tr, ent->currentOrigin, vec3_origin, vec3_origin, origin, ent->owner ? ent->owner->s.number : ENTITYNUM_NONE, ent->clipmask, G2_RETURNONHIT, 10 ); } */ /* if ( tr.fraction == 0.0f && tr.plane.normal[2] == 1.0f && origin[2] < ent->currentOrigin[2] ) { if ( ent->s.pos.trType == TR_GRAVITY && !(ent->s.eFlags&EF_BOUNCE) && !(ent->s.eFlags&EF_BOUNCE_HALF) && ent->s.weapon == WP_THERMAL )//FIXME: EF_ROLLING { //FIXME: Needs to stop sometime! ent->s.pos.trType = TR_LINEAR; ent->s.pos.trDelta[2] = 0; EvaluateTrajectory( &ent->s.pos, level.time, origin ); // trace a line from the previous position to the current position, // ignoring interactions with the missile owner gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, ent->owner ? ent->owner->s.number : ENTITYNUM_NONE, ent->clipmask | CONTENTS_GHOUL2 ); if ( tr.fraction == 1.0f ) { VectorCopy( tr.endpos, ent->s.pos.trBase ); VectorScale( ent->s.pos.trDelta, 0.975f, ent->s.pos.trDelta ); ent->s.pos.trTime = level.time; } ent->s.pos.trType = TR_GRAVITY; } } */ if ( tr.entityNum != ENTITYNUM_NONE && &g_entities[tr.entityNum] != NULL ) { gentity_t *other = &g_entities[tr.entityNum]; // check for hitting a lightsaber if ( other->contents & CONTENTS_LIGHTSABER ) {//hit a lightsaber bbox if ( other->owner && other->owner->client && !other->owner->client->ps.saberInFlight && !InFront( ent->currentOrigin, other->owner->currentOrigin, other->owner->client->ps.viewangles, SABER_REFLECT_MISSILE_CONE ) )//other->owner->s.number == 0 && {//Jedi cannot block shots from behind! //re-trace from here, ignoring the lightsaber gi.trace( &tr, tr.endpos, ent->mins, ent->maxs, origin, tr.entityNum, ent->clipmask, G2_RETURNONHIT, 10 ); } } } VectorCopy( tr.endpos, ent->currentOrigin ); } // get current angles VectorMA( ent->s.apos.trBase, (level.time - ent->s.apos.trTime) * 0.001, ent->s.apos.trDelta, ent->s.apos.trBase ); //FIXME: Rolling things hitting G2 polys is weird /////////////////////////////////////////////////////// //? if ( tr.fraction != 1 ) { // did we hit or go near a Ghoul2 model? // qboolean hitModel = qfalse; for (int i=0; i < MAX_G2_COLLISIONS; i++) { if (tr.G2CollisionMap[i].mEntityNum == -1) { break; } CCollisionRecord &coll = tr.G2CollisionMap[i]; gentity_t *hitEnt = &g_entities[coll.mEntityNum]; /* Sorry...this was just getting in the way.... #if _DEBUG vec3_t delta; VectorSubtract(origin, coll.mCollisionPosition, delta); VectorNormalize(delta); VectorScale(delta, 30, delta); if (coll.mFlags & G2_BACKFACE) { VectorAdd(delta, coll.mCollisionPosition, delta); G_DebugLine(coll.mCollisionPosition, delta, 10000, 0x00ff0000, qtrue); } else { VectorSubtract(coll.mCollisionPosition, delta, delta); G_DebugLine(coll.mCollisionPosition, delta, 10000, 0x0000ff00, qtrue); } //loadsavecrash // VectorCopy(hitEnt->mins, hitEnt->s.mins); // VectorCopy(hitEnt->maxs, hitEnt->s.maxs); #endif */ // process collision records here... // make sure we only do this once, not for all the entrance wounds we might generate if ((coll.mFlags & G2_FRONTFACE)/* && !(hitModel)*/ && hitEnt->health) { // create a new surface using the details of what poly/surface/model we hit // int newSurface = gi.G2API_AddSurface(&hitEnt->ghoul2[coll.mModelIndex], coll.mSurfaceIndex, coll.mPolyIndex, coll.mBarycentricI, coll.mBarycentricJ, 10); // surfaceInfo_t *newSuf = &hitEnt->ghoul2[coll.mModelIndex].mSlist[newSurface]; // attach a bolt to this surface // int newBolt = gi.G2API_AddBoltSurfNum(&hitEnt->ghoul2[coll.mModelIndex], newSurface); // now attach an effect to this new bolt // Bolting on this effect just looks dumb and adds lots of unnecessary effects to the scene // // G_PlayEffect( G_EffectIndex( "blaster/smoke_bolton") , coll.mModelIndex, newBolt, hitEnt->s.number); // // // G_SetBoltSurfaceRemoval(coll.mEntityNum, coll.mModelIndex, newBolt, newSurface, 10000); // hitModel = qtrue; if (trHitLoc==HL_NONE) { G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex ), &trHitLoc, coll.mCollisionPosition, NULL, NULL, ent->methodOfDeath ); } break; // NOTE: the way this whole section was working, it would only get inside of this IF once anyway, might as well break out now } } } ///////////////////////////////////////////////////////// if ( tr.startsolid ) { tr.fraction = 0; } gi.linkentity( ent ); if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) ) {//stuck missiles should check some special stuff G_RunStuckMissile( ent ); return; } // check think function G_RunThink( ent ); if ( ent->s.eType != ET_MISSILE ) { return; // exploded } if ( ent->mass ) { G_MoverTouchPushTriggers( ent, oldOrg ); } /* if ( !(ent->s.eFlags & EF_TELEPORT_BIT) ) { G_MoverTouchTeleportTriggers( ent, oldOrg ); if ( ent->s.eFlags & EF_TELEPORT_BIT ) {//was teleported return; } } else { ent->s.eFlags &= ~EF_TELEPORT_BIT; } */ AddSightEvent( ent->owner, ent->currentOrigin, 512, AEL_DISCOVERED, 75 );//wakes them up when see a shot passes in front of them if ( !Q_irand( 0, 10 ) ) {//not so often... if ( ent->splashDamage && ent->splashRadius ) {//I'm an exploder, let people around me know danger is coming if ( ent->s.weapon == WP_TRIP_MINE ) {//??? } else { if ( ent->s.weapon == WP_ROCKET_LAUNCHER && ent->e_ThinkFunc == thinkF_rocketThink ) {//homing rocket- run like hell! AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius, AEL_DANGER_GREAT, 50 ); } else { AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius, AEL_DANGER, 50 ); } AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius, AEL_DANGER ); } } else {//makes them run from near misses AddSightEvent( ent->owner, ent->currentOrigin, 48, AEL_DANGER, 50 ); } } if ( tr.fraction == 1 ) { return; } // never explode or bounce on sky if ( tr.surfaceFlags & SURF_NOIMPACT ) { G_FreeEntity( ent ); return; } G_MissileImpact( ent, &tr, trHitLoc ); }
float MatchAndAvoidNeighbors(t_jit_boids2d *flockPtr, long theBoid, double *matchNeighborVel, double *avoidNeighborVel) { long i, j, neighbor; double distSqr; double dist, distH, distV,distD; double tempSpeed; short numClose = 0; double totalVel[2] = {0.0,0.0}; /**********************/ /* Find the neighbors */ /**********************/ /* special case of one neighbor */ if (flockPtr->neighbors == 1) { flockPtr->boid[theBoid].neighborDistSqr[0] = kMaxLong; for (i = 0; i < flockPtr->number; i++) { if (i != theBoid) { distSqr = DistSqrToPt(flockPtr->boid[theBoid].oldPos, flockPtr->boid[i].oldPos); /* if this one is closer than the closest so far, then remember it */ if (flockPtr->boid[theBoid].neighborDistSqr[0] > distSqr) { flockPtr->boid[theBoid].neighborDistSqr[0] = distSqr; flockPtr->boid[theBoid].neighbor[0] = i; } } } } /* more than one neighbor */ else { for (j = 0; j < flockPtr->neighbors; j++) flockPtr->boid[theBoid].neighborDistSqr[j] = kMaxLong; for (i = 0 ; i < flockPtr->number; i++) { /* if this one is not me... */ if (i != theBoid) { distSqr = DistSqrToPt(flockPtr->boid[theBoid].oldPos, flockPtr->boid[i].oldPos); /* if distSqr is less than the distance at the bottom of the array, sort into array */ if (distSqr < flockPtr->boid[theBoid].neighborDistSqr[flockPtr->neighbors-1]) { j = flockPtr->neighbors - 1; /* sort distSqr in to keep array in size order, smallest first */ while ((distSqr < flockPtr->boid[theBoid].neighborDistSqr[j-1]) && (j > 0)) { flockPtr->boid[theBoid].neighborDistSqr[j] = flockPtr->boid[theBoid].neighborDistSqr[j - 1]; flockPtr->boid[theBoid].neighbor[j] = flockPtr->boid[theBoid].neighbor[j - 1]; j--; } flockPtr->boid[theBoid].neighborDistSqr[j] = distSqr; flockPtr->boid[theBoid].neighbor[j] = i; } } } } /*********************************/ /* Match and avoid the neighbors */ /*********************************/ matchNeighborVel[x] = 0; matchNeighborVel[y] = 0; //matchNeighborVel[z] = 0; // set tempSpeed to old speed tempSpeed = flockPtr->boid[theBoid].speed; for (i = 0; i < flockPtr->neighbors; i++) { neighbor = flockPtr->boid[theBoid].neighbor[i]; // calculate matchNeighborVel by averaging the neighbor velocities matchNeighborVel[x] += flockPtr->boid[neighbor].oldDir[x]; matchNeighborVel[y] += flockPtr->boid[neighbor].oldDir[y]; //matchNeighborVel[z] += flockPtr->boid[neighbor].oldDir[z]; // if distance is less than preferred distance, then neighbor influences boid distSqr = flockPtr->boid[theBoid].neighborDistSqr[i]; if (distSqr < flockPtr->prefDistSqr) { dist = jit_math_sqrt(distSqr); distH = flockPtr->boid[neighbor].oldPos[x] - flockPtr->boid[theBoid].oldPos[x]; distV = flockPtr->boid[neighbor].oldPos[y] - flockPtr->boid[theBoid].oldPos[y]; //distD = flockPtr->boid[neighbor].oldPos[z] - flockPtr->boid[theBoid].oldPos[z]; if(dist == 0.0) dist = 0.0000001; totalVel[x] = totalVel[x] - distH - (distH * ((float) flockPtr->prefdist / (dist))); totalVel[y] = totalVel[y] - distV - (distV * ((float) flockPtr->prefdist / (dist))); //totalVel[z] = totalVel[z] - distD - (distV * ((float) flockPtr->prefdist / (dist))); numClose++; } if (InFront(&(flockPtr->boid[theBoid]), &(flockPtr->boid[neighbor]))) { // adjust speed if (distSqr < flockPtr->prefDistSqr) tempSpeed /= (flockPtr->accel / 100.0); else tempSpeed *= (flockPtr->accel / 100.0); } else { if (distSqr < flockPtr->prefDistSqr) tempSpeed *= (flockPtr->accel / 100.0); else tempSpeed /= (flockPtr->accel / 100.0); } } if (numClose) { avoidNeighborVel[x] = totalVel[x] / numClose; avoidNeighborVel[y] = totalVel[y] / numClose; //avoidNeighborVel[z] = totalVel[z] / numClose; NormalizeVelocity(matchNeighborVel); } else { avoidNeighborVel[x] = 0; avoidNeighborVel[y] = 0; //avoidNeighborVel[z] = 0; } return(tempSpeed); }