//NPC_utils.cpp #include "b_local.h" #include "../icarus/Q3_Interface.h" #include "../ghoul2/G2.h" int teamNumbers[TEAM_NUM_TEAMS]; int teamStrength[TEAM_NUM_TEAMS]; int teamCounter[TEAM_NUM_TEAMS]; #define VALID_ATTACK_CONE 2.0f //Degrees extern void G_DebugPrint( int level, const char *format, ... ); /* void CalcEntitySpot ( gentity_t *ent, spot_t spot, vec3_t point ) Added: Uses shootAngles if a NPC has them */ void CalcEntitySpot ( const gentity_t *ent, const spot_t spot, vec3_t point ) { vec3_t forward, up, right; vec3_t start, end; trace_t tr; if ( !ent ) { return; } switch ( spot ) { case SPOT_ORIGIN: if(VectorCompare(ent->r.currentOrigin, vec3_origin)) {//brush VectorSubtract(ent->r.absmax, ent->r.absmin, point);//size VectorMA(ent->r.absmin, 0.5, point, point); } else { VectorCopy ( ent->r.currentOrigin, point ); } break; case SPOT_CHEST: case SPOT_HEAD: if ( ent->client && VectorLengthSquared( ent->client->renderInfo.eyePoint ) /*&& (ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD)*/ ) {//Actual tag_head eyespot! //FIXME: Stasis aliens may have a problem here... VectorCopy( ent->client->renderInfo.eyePoint, point ); if ( ent->client->NPC_class == CLASS_ATST ) {//adjust up some point[2] += 28;//magic number :) } if ( ent->NPC ) {//always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards point[0] = ent->r.currentOrigin[0]; point[1] = ent->r.currentOrigin[1]; } /* else if (ent->s.eType == ET_PLAYER ) { SubtractLeanOfs( ent, point ); } */ } else { VectorCopy ( ent->r.currentOrigin, point ); if ( ent->client ) { point[2] += ent->client->ps.viewheight; } } if ( spot == SPOT_CHEST && ent->client ) { if ( ent->client->NPC_class != CLASS_ATST ) {//adjust up some point[2] -= ent->r.maxs[2]*0.2f; } } break; case SPOT_HEAD_LEAN: if ( ent->client && VectorLengthSquared( ent->client->renderInfo.eyePoint ) /*&& (ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD*/ ) {//Actual tag_head eyespot! //FIXME: Stasis aliens may have a problem here... VectorCopy( ent->client->renderInfo.eyePoint, point ); if ( ent->client->NPC_class == CLASS_ATST ) {//adjust up some point[2] += 28;//magic number :) } if ( ent->NPC ) {//always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards point[0] = ent->r.currentOrigin[0]; point[1] = ent->r.currentOrigin[1]; } /* else if ( ent->s.eType == ET_PLAYER ) { SubtractLeanOfs( ent, point ); } */ //NOTE: automatically takes leaning into account! } else { VectorCopy ( ent->r.currentOrigin, point ); if ( ent->client ) { point[2] += ent->client->ps.viewheight; } //AddLeanOfs ( ent, point ); } break; //FIXME: implement... //case SPOT_CHEST: //Returns point 3/4 from tag_torso to tag_head? //break; case SPOT_LEGS: VectorCopy ( ent->r.currentOrigin, point ); point[2] += (ent->r.mins[2] * 0.5); break; case SPOT_WEAPON: if( ent->NPC && !VectorCompare( ent->NPC->shootAngles, vec3_origin ) && !VectorCompare( ent->NPC->shootAngles, ent->client->ps.viewangles )) { AngleVectors( ent->NPC->shootAngles, forward, right, up ); } else { AngleVectors( ent->client->ps.viewangles, forward, right, up ); } CalcMuzzlePoint( (gentity_t*)ent, forward, right, up, point ); //NOTE: automatically takes leaning into account! break; case SPOT_GROUND: // if entity is on the ground, just use it's absmin if ( ent->s.groundEntityNum != -1 ) { VectorCopy( ent->r.currentOrigin, point ); point[2] = ent->r.absmin[2]; break; } // if it is reasonably close to the ground, give the point underneath of it VectorCopy( ent->r.currentOrigin, start ); start[2] = ent->r.absmin[2]; VectorCopy( start, end ); end[2] -= 64; trap_Trace( &tr, start, ent->r.mins, ent->r.maxs, end, ent->s.number, MASK_PLAYERSOLID ); if ( tr.fraction < 1.0 ) { VectorCopy( tr.endpos, point); break; } // otherwise just use the origin VectorCopy( ent->r.currentOrigin, point ); break; default: VectorCopy ( ent->r.currentOrigin, point ); break; } } //=================================================================================== /* qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ) Added: option to do just pitch or just yaw Does not include "aim" in it's calculations FIXME: stop compressing angles into shorts!!!! */ qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ) { #if 1 float error; float decay; float targetPitch = 0; float targetYaw = 0; float yawSpeed; qboolean exact = qtrue; // if angle changes are locked; just keep the current angles // aimTime isn't even set anymore... so this code was never reached, but I need a way to lock NPC's yaw, so instead of making a new SCF_ flag, just use the existing render flag... - dmv if ( !NPC->enemy && ( (level.time < NPCInfo->aimTime) /*|| NPC->client->renderInfo.renderFlags & RF_LOCKEDANGLE*/) ) { if(doPitch) targetPitch = NPCInfo->lockedDesiredPitch; if(doYaw) targetYaw = NPCInfo->lockedDesiredYaw; } else { // we're changing the lockedDesired Pitch/Yaw below so it's lost it's original meaning, get rid of the lock flag // NPC->client->renderInfo.renderFlags &= ~RF_LOCKEDANGLE; if(doPitch) { targetPitch = NPCInfo->desiredPitch; NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; } if(doYaw) { targetYaw = NPCInfo->desiredYaw; NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; } } if ( NPC->s.weapon == WP_EMPLACED_GUN ) { // FIXME: this seems to do nothing, actually... yawSpeed = 20; } else { yawSpeed = NPCInfo->stats.yawSpeed; } if ( NPC->s.weapon == WP_SABER && NPC->client->ps.fd.forcePowersActive&(1<client->ps.viewangles[YAW], targetYaw ); if( fabs(error) > MIN_ANGLE_ERROR ) { if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0f / 1000.0f;//msec if ( error < 0.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - client->ps.delta_angles[YAW]; } //FIXME: have a pitchSpeed? if( doPitch ) { // decay pitch error error = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); if ( fabs(error) > MIN_ANGLE_ERROR ) { if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0f / 1000.0f;//msec if ( error < 0.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - client->ps.delta_angles[PITCH]; } ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; if ( exact && trap_ICARUS_TaskIDPending( NPC, TID_ANGLE_FACE ) ) { trap_ICARUS_TaskIDComplete( NPC, TID_ANGLE_FACE ); } return exact; #else float error; float decay; float targetPitch = 0; float targetYaw = 0; float yawSpeed; //float runningMod = NPCInfo->currentSpeed/100.0f; qboolean exact = qtrue; qboolean doSound = qfalse; // if angle changes are locked; just keep the current angles if ( level.time < NPCInfo->aimTime ) { if(doPitch) targetPitch = NPCInfo->lockedDesiredPitch; if(doYaw) targetYaw = NPCInfo->lockedDesiredYaw; } else { if(doPitch) targetPitch = NPCInfo->desiredPitch; if(doYaw) targetYaw = NPCInfo->desiredYaw; // NPCInfo->aimTime = level.time + 250; if(doPitch) NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; if(doYaw) NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; } yawSpeed = NPCInfo->stats.yawSpeed; if(doYaw) { // decay yaw error error = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); if( fabs(error) > MIN_ANGLE_ERROR ) { /* if(NPC->client->playerTeam == TEAM_BORG&& NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE) {//HACK - borg turn more jittery if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0 / 1000.0;//msec //Snap to if(fabs(error) > 10) { if(random() > 0.6) { doSound = qtrue; } } if ( error < 0.0)//-10.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else if ( error > 0.0)//10.0 ) { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } else*/ if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0 / 1000.0;//msec if ( error < 0.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - client->ps.delta_angles[YAW]; } //FIXME: have a pitchSpeed? if(doPitch) { // decay pitch error error = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); if ( fabs(error) > MIN_ANGLE_ERROR ) { /* if(NPC->client->playerTeam == TEAM_BORG&& NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE) {//HACK - borg turn more jittery if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0 / 1000.0;//msec //Snap to if(fabs(error) > 10) { if(random() > 0.6) { doSound = qtrue; } } if ( error < 0.0)//-10.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else if ( error > 0.0)//10.0 ) { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } else*/ if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0 / 1000.0;//msec if ( error < 0.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - client->ps.delta_angles[PITCH]; } ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; /* if(doSound) { G_Sound(NPC, G_SoundIndex(va("sound/enemies/borg/borgservo%d.wav", Q_irand(1, 8)))); } */ return exact; #endif } void NPC_AimWiggle( vec3_t enemy_org ) { //shoot for somewhere between the head and torso //NOTE: yes, I know this looks weird, but it works if ( NPCInfo->aimErrorDebounceTime < level.time ) { NPCInfo->aimOfs[0] = 0.3*flrand(NPC->enemy->r.mins[0], NPC->enemy->r.maxs[0]); NPCInfo->aimOfs[1] = 0.3*flrand(NPC->enemy->r.mins[1], NPC->enemy->r.maxs[1]); if ( NPC->enemy->r.maxs[2] > 0 ) { NPCInfo->aimOfs[2] = NPC->enemy->r.maxs[2]*flrand(0.0f, -1.0f); } } VectorAdd( enemy_org, NPCInfo->aimOfs, enemy_org ); } /* qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) Includes aim when determining angles - so they don't always hit... */ qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) { #if 0 float diff; float error; float targetPitch = 0; float targetYaw = 0; qboolean exact = qtrue; if ( level.time < NPCInfo->aimTime ) { if( doPitch ) targetPitch = NPCInfo->lockedDesiredPitch; if( doYaw ) targetYaw = NPCInfo->lockedDesiredYaw; } else { if( doPitch ) { targetPitch = NPCInfo->desiredPitch; NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; } if( doYaw ) { targetYaw = NPCInfo->desiredYaw; NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; } } if( doYaw ) { // add yaw error based on NPCInfo->aim value error = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); if(Q_irand(0, 1)) error *= -1; diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); if ( diff ) exact = qfalse; ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW]; } if( doPitch ) { // add pitch error based on NPCInfo->aim value error = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); if ( diff ) exact = qfalse; ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH]; } ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; return exact; #else float error, diff; float decay; float targetPitch = 0; float targetYaw = 0; qboolean exact = qtrue; // if angle changes are locked; just keep the current angles if ( level.time < NPCInfo->aimTime ) { if(doPitch) targetPitch = NPCInfo->lockedDesiredPitch; if(doYaw) targetYaw = NPCInfo->lockedDesiredYaw; } else { if(doPitch) targetPitch = NPCInfo->desiredPitch; if(doYaw) targetYaw = NPCInfo->desiredYaw; // NPCInfo->aimTime = level.time + 250; if(doPitch) NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; if(doYaw) NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; } if ( NPCInfo->aimErrorDebounceTime < level.time ) { if ( Q_irand(0, 1 ) ) { NPCInfo->lastAimErrorYaw = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); } if ( Q_irand(0, 1 ) ) { NPCInfo->lastAimErrorPitch = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); } NPCInfo->aimErrorDebounceTime = level.time + Q_irand(250, 2000); } if(doYaw) { // decay yaw diff diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); if ( diff) { exact = qfalse; decay = 60.0 + 80.0; decay *= 50.0f / 1000.0f;//msec if ( diff < 0.0 ) { diff += decay; if ( diff > 0.0 ) { diff = 0.0; } } else { diff -= decay; if ( diff < 0.0 ) { diff = 0.0; } } } // add yaw error based on NPCInfo->aim value error = NPCInfo->lastAimErrorYaw; /* if(Q_irand(0, 1)) { error *= -1; } */ ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW]; } if(doPitch) { // decay pitch diff diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); if ( diff) { exact = qfalse; decay = 60.0 + 80.0; decay *= 50.0f / 1000.0f;//msec if ( diff < 0.0 ) { diff += decay; if ( diff > 0.0 ) { diff = 0.0; } } else { diff -= decay; if ( diff < 0.0 ) { diff = 0.0; } } } error = NPCInfo->lastAimErrorPitch; ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH]; } ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; return exact; #endif } //=================================================================================== /* static void NPC_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw ) Does update angles on shootAngles */ void NPC_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw ) {//FIXME: shoot angles either not set right or not used! float error; float decay; float targetPitch = 0; float targetYaw = 0; if(doPitch) targetPitch = angles[PITCH]; if(doYaw) targetYaw = angles[YAW]; if(doYaw) { // decay yaw error error = AngleDelta ( NPCInfo->shootAngles[YAW], targetYaw ); if ( error ) { decay = 60.0 + 80.0 * NPCInfo->stats.aim; decay *= 100.0f / 1000.0f;//msec if ( error < 0.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } NPCInfo->shootAngles[YAW] = targetYaw + error; } if(doPitch) { // decay pitch error error = AngleDelta ( NPCInfo->shootAngles[PITCH], targetPitch ); if ( error ) { decay = 60.0 + 80.0 * NPCInfo->stats.aim; decay *= 100.0f / 1000.0f;//msec if ( error < 0.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } NPCInfo->shootAngles[PITCH] = targetPitch + error; } } /* void SetTeamNumbers (void) Sets the number of living clients on each team FIXME: Does not account for non-respawned players! FIXME: Don't include medics? */ void SetTeamNumbers (void) { gentity_t *found; int i; for( i = 0; i < TEAM_NUM_TEAMS; i++ ) { teamNumbers[i] = 0; teamStrength[i] = 0; } for( i = 0; i < 1 ; i++ ) { found = &g_entities[i]; if( found->client ) { if( found->health > 0 )//FIXME: or if a player! { teamNumbers[found->client->playerTeam]++; teamStrength[found->client->playerTeam] += found->health; } } } for( i = 0; i < TEAM_NUM_TEAMS; i++ ) {//Get the average health teamStrength[i] = floor( ((float)(teamStrength[i])) / ((float)(teamNumbers[i])) ); } } extern stringID_table_t BSTable[]; extern stringID_table_t BSETTable[]; qboolean G_ActivateBehavior (gentity_t *self, int bset ) { bState_t bSID = (bState_t)-1; char *bs_name = NULL; if ( !self ) { return qfalse; } bs_name = self->behaviorSet[bset]; if( !(VALIDSTRING( bs_name )) ) { return qfalse; } if ( self->NPC ) { bSID = (bState_t)(GetIDForString( BSTable, bs_name )); } if(bSID > -1) { self->NPC->tempBehavior = BS_DEFAULT; self->NPC->behaviorState = bSID; } else { /* char newname[MAX_FILENAME_LENGTH]; sprintf((char *) &newname, "%s/%s", Q3_SCRIPT_DIR, bs_name ); */ //FIXME: between here and actually getting into the ICARUS_RunScript function, the stack gets blown! //if ( ( ICARUS_entFilter == -1 ) || ( ICARUS_entFilter == self->s.number ) ) if (0) { G_DebugPrint( WL_VERBOSE, "%s attempting to run bSet %s (%s)\n", self->targetname, GetStringForID( BSETTable, bset ), bs_name ); } trap_ICARUS_RunScript( self, va( "%s/%s", Q3_SCRIPT_DIR, bs_name ) ); } return qtrue; } /* ============================================================================= Extended Functions ============================================================================= */ //rww - special system for sync'ing bone angles between client and server. void NPC_SetBoneAngles(gentity_t *ent, char *bone, vec3_t angles) { #ifdef _XBOX byte *thebone = &ent->s.boneIndex1; byte *firstFree = NULL; #else int *thebone = &ent->s.boneIndex1; int *firstFree = NULL; #endif int i = 0; int boneIndex = G_BoneIndex(bone); int flags, up, right, forward; vec3_t *boneVector = &ent->s.boneAngles1; vec3_t *freeBoneVec = NULL; while (thebone) { if (!*thebone && !firstFree) { //if the value is 0 then this index is clear, we can use it if we don't find the bone we want already existing. firstFree = thebone; freeBoneVec = boneVector; } else if (*thebone) { if (*thebone == boneIndex) { //this is it break; } } switch (i) { case 0: thebone = &ent->s.boneIndex2; boneVector = &ent->s.boneAngles2; break; case 1: thebone = &ent->s.boneIndex3; boneVector = &ent->s.boneAngles3; break; case 2: thebone = &ent->s.boneIndex4; boneVector = &ent->s.boneAngles4; break; default: thebone = NULL; boneVector = NULL; break; } i++; } if (!thebone) { //didn't find it, create it if (!firstFree) { //no free bones.. can't do a thing then. Com_Printf("WARNING: NPC has no free bone indexes\n"); return; } thebone = firstFree; *thebone = boneIndex; boneVector = freeBoneVec; } //If we got here then we have a vector and an index. //Copy the angles over the vector in the entitystate, so we can use the corresponding index //to set the bone angles on the client. VectorCopy(angles, *boneVector); //Now set the angles on our server instance if we have one. if (!ent->ghoul2) { return; } flags = BONE_ANGLES_POSTMULT; up = POSITIVE_X; right = NEGATIVE_Y; forward = NEGATIVE_Z; //first 3 bits is forward, second 3 bits is right, third 3 bits is up ent->s.boneOrient = ((forward)|(right<<3)|(up<<6)); trap_G2API_SetBoneAngles(ent->ghoul2, 0, bone, angles, flags, up, right, forward, NULL, 100, level.time); } //rww - and another method of automatically managing surface status for the client and server at once #define TURN_ON 0x00000000 #define TURN_OFF 0x00000100 void NPC_SetSurfaceOnOff(gentity_t *ent, const char *surfaceName, int surfaceFlags) { int i = 0; qboolean foundIt = qfalse; while (i < BG_NUM_TOGGLEABLE_SURFACES && bgToggleableSurfaces[i]) { if (!Q_stricmp(surfaceName, bgToggleableSurfaces[i])) { //got it foundIt = qtrue; break; } i++; } if (!foundIt) { Com_Printf("WARNING: Tried to toggle NPC surface that isn't in toggleable surface list (%s)\n", surfaceName); return; } if (surfaceFlags == TURN_ON) { //Make sure the entitystate values reflect this surface as on now. ent->s.surfacesOn |= (1 << i); ent->s.surfacesOff &= ~(1 << i); } else { //Otherwise make sure they're off. ent->s.surfacesOn &= ~(1 << i); ent->s.surfacesOff |= (1 << i); } if (!ent->ghoul2) { return; } trap_G2API_SetSurfaceOnOff(ent->ghoul2, surfaceName, surfaceFlags); } //rww - cheap check to see if an armed client is looking in our general direction qboolean NPC_SomeoneLookingAtMe(gentity_t *ent) { int i = 0; gentity_t *pEnt; while (i < MAX_CLIENTS) { pEnt = &g_entities[i]; if (pEnt && pEnt->inuse && pEnt->client && pEnt->client->sess.sessionTeam != TEAM_SPECTATOR && !(pEnt->client->ps.pm_flags & PMF_FOLLOW) && pEnt->s.weapon != WP_NONE) { if (trap_InPVS(ent->r.currentOrigin, pEnt->r.currentOrigin)) { if (InFOV( ent, pEnt, 30, 30 )) { //I'm in a 30 fov or so cone from this player.. that's enough I guess. return qtrue; } } } i++; } return qfalse; } qboolean NPC_ClearLOS( const vec3_t start, const vec3_t end ) { return G_ClearLOS( NPC, start, end ); } qboolean NPC_ClearLOS5( const vec3_t end ) { return G_ClearLOS5( NPC, end ); } qboolean NPC_ClearLOS4( gentity_t *ent ) { return G_ClearLOS4( NPC, ent ); } qboolean NPC_ClearLOS3( const vec3_t start, gentity_t *ent ) { return G_ClearLOS3( NPC, start, ent ); } qboolean NPC_ClearLOS2( gentity_t *ent, const vec3_t end ) { return G_ClearLOS2( NPC, ent, end ); } /* ------------------------- NPC_ValidEnemy ------------------------- */ qboolean NPC_ValidEnemy( gentity_t *ent ) { int entTeam = TEAM_FREE; //Must be a valid pointer if ( ent == NULL ) return qfalse; //Must not be me if ( ent == NPC ) return qfalse; //Must not be deleted if ( ent->inuse == qfalse ) return qfalse; //Must be alive if ( ent->health <= 0 ) return qfalse; //In case they're in notarget mode if ( ent->flags & FL_NOTARGET ) return qfalse; //Must be an NPC if ( ent->client == NULL ) { // if ( ent->svFlags&SVF_NONNPC_ENEMY ) if (ent->s.eType != ET_NPC) {//still potentially valid if ( ent->alliedTeam == NPC->client->playerTeam ) { return qfalse; } else { return qtrue; } } else { return qfalse; } } else if ( ent->client && ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {//don't go after spectators return qfalse; } if ( ent->NPC && ent->client ) { entTeam = ent->client->playerTeam; } else if ( ent->client ) { if (g_gametype.integer < GT_TEAM) { entTeam = NPCTEAM_PLAYER; } else { if ( ent->client->sess.sessionTeam == TEAM_BLUE ) { entTeam = NPCTEAM_PLAYER; } else if ( ent->client->sess.sessionTeam == TEAM_RED ) { entTeam = NPCTEAM_ENEMY; } else { entTeam = NPCTEAM_NEUTRAL; } } } //Can't be on the same team if ( ent->client->playerTeam == NPC->client->playerTeam ) return qfalse; //if haven't seen him in a while, give up //if ( NPCInfo->enemyLastSeenTime != 0 && level.time - NPCInfo->enemyLastSeenTime > 7000 )//FIXME: make a stat? // return qfalse; if ( entTeam == NPC->client->enemyTeam //simplest case: they're on my enemy team || (NPC->client->enemyTeam == NPCTEAM_FREE && ent->client->NPC_class != NPC->client->NPC_class )//I get mad at anyone and this guy isn't the same class as me || (ent->client->NPC_class == CLASS_WAMPA && ent->enemy )//a rampaging wampa || (ent->client->NPC_class == CLASS_RANCOR && ent->enemy )//a rampaging rancor || (entTeam == NPCTEAM_FREE && ent->client->enemyTeam == NPCTEAM_FREE && ent->enemy && ent->enemy->client && (ent->enemy->client->playerTeam == NPC->client->playerTeam||(ent->enemy->client->playerTeam != NPCTEAM_ENEMY&&NPC->client->playerTeam==NPCTEAM_PLAYER))) //enemy is a rampaging non-aligned creature who is attacking someone on our team or a non-enemy (this last condition is used only if we're a good guy - in effect, we protect the innocent) ) { return qtrue; } return qfalse; } /* ------------------------- NPC_TargetVisible ------------------------- */ qboolean NPC_TargetVisible( gentity_t *ent ) { //Make sure we're in a valid range if ( DistanceSquared( ent->r.currentOrigin, NPC->r.currentOrigin ) > ( NPCInfo->stats.visrange * NPCInfo->stats.visrange ) ) return qfalse; //Check our FOV if ( InFOV( ent, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse ) return qfalse; //Check for sight if ( NPC_ClearLOS4( ent ) == qfalse ) return qfalse; return qtrue; } /* ------------------------- NPC_GetCheckDelta ------------------------- */ /* #define CHECK_TIME_BASE 250 #define CHECK_TIME_BASE_SQUARED ( CHECK_TIME_BASE * CHECK_TIME_BASE ) static int NPC_GetCheckDelta( void ) { if ( NPC_ValidEnemy( NPC->enemy ) == qfalse ) { int distance = DistanceSquared( NPC->r.currentOrigin, g_entities[0].currentOrigin ); distance /= CHECK_TIME_BASE_SQUARED; return ( CHECK_TIME_BASE * distance ); } return 0; } */ /* ------------------------- NPC_FindNearestEnemy ------------------------- */ #define MAX_RADIUS_ENTS 256 //NOTE: This can cause entities to be lost #define NEAR_DEFAULT_RADIUS 256 int NPC_FindNearestEnemy( gentity_t *ent ) { int iradiusEnts[ MAX_RADIUS_ENTS ]; gentity_t *radEnt; vec3_t mins, maxs; int nearestEntID = -1; float nearestDist = (float)WORLD_SIZE*(float)WORLD_SIZE; float distance; int numEnts, numChecks = 0; int i; //Setup the bbox to search in for ( i = 0; i < 3; i++ ) { mins[i] = ent->r.currentOrigin[i] - NPCInfo->stats.visrange; maxs[i] = ent->r.currentOrigin[i] + NPCInfo->stats.visrange; } //Get a number of entities in a given space numEnts = trap_EntitiesInBox( mins, maxs, iradiusEnts, MAX_RADIUS_ENTS ); for ( i = 0; i < numEnts; i++ ) { radEnt = &g_entities[iradiusEnts[i]]; //Don't consider self if ( radEnt == ent ) continue; //Must be valid if ( NPC_ValidEnemy( radEnt ) == qfalse ) continue; numChecks++; //Must be visible if ( NPC_TargetVisible( radEnt ) == qfalse ) continue; distance = DistanceSquared( ent->r.currentOrigin, radEnt->r.currentOrigin ); //Found one closer to us if ( distance < nearestDist ) { nearestEntID = radEnt->s.number; nearestDist = distance; } } return nearestEntID; } /* ------------------------- NPC_PickEnemyExt ------------------------- */ gentity_t *NPC_PickEnemyExt( qboolean checkAlerts ) { //Check for Hazard Team status and remove this check /* if ( NPC->client->playerTeam != TEAM_STARFLEET ) { //If we've found the player, return it if ( NPC_FindPlayer() ) return &g_entities[0]; } */ //If we've asked for the closest enemy int entID = NPC_FindNearestEnemy( NPC ); //If we have a valid enemy, use it if ( entID >= 0 ) return &g_entities[entID]; if ( checkAlerts ) { int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qtrue, AEL_DISCOVERED ); //There is an event to look at if ( alertEvent >= 0 ) { alertEvent_t *event = &level.alertEvents[alertEvent]; //Don't pay attention to our own alerts if ( event->owner == NPC ) return NULL; if ( event->level >= AEL_DISCOVERED ) { //If it's the player, attack him if ( event->owner == &g_entities[0] ) return event->owner; //If it's on our team, then take its enemy as well if ( ( event->owner->client ) && ( event->owner->client->playerTeam == NPC->client->playerTeam ) ) return event->owner->enemy; } } } return NULL; } /* ------------------------- NPC_FindPlayer ------------------------- */ qboolean NPC_FindPlayer( void ) { return NPC_TargetVisible( &g_entities[0] ); } /* ------------------------- NPC_CheckPlayerDistance ------------------------- */ static qboolean NPC_CheckPlayerDistance( void ) { return qfalse;//MOOT in MP /* float distance; //Make sure we have an enemy if ( NPC->enemy == NULL ) return qfalse; //Only do this for non-players if ( NPC->enemy->s.number == 0 ) return qfalse; //must be set up to get mad at player if ( !NPC->client || NPC->client->enemyTeam != NPCTEAM_PLAYER ) return qfalse; //Must be within our FOV if ( InFOV( &g_entities[0], NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse ) return qfalse; distance = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); if ( distance > DistanceSquared( NPC->r.currentOrigin, g_entities[0].r.currentOrigin ) ) { //rwwFIXMEFIXME: care about all clients not just client 0 G_SetEnemy( NPC, &g_entities[0] ); return qtrue; } return qfalse; */ } /* ------------------------- NPC_FindEnemy ------------------------- */ qboolean NPC_FindEnemy( qboolean checkAlerts ) { gentity_t *newenemy; //We're ignoring all enemies for now //if( NPC->svFlags & SVF_IGNORE_ENEMIES ) if (0) //rwwFIXMEFIXME: support for flag { G_ClearEnemy( NPC ); return qfalse; } //we can't pick up any enemies for now if( NPCInfo->confusionTime > level.time ) { return qfalse; } //Don't want a new enemy //rwwFIXMEFIXME: support for locked enemy //if ( ( ValidEnemy( NPC->enemy ) ) && ( NPC->svFlags & SVF_LOCKEDENEMY ) ) // return qtrue; //See if the player is closer than our current enemy if ( NPC_CheckPlayerDistance() ) { return qtrue; } //Otherwise, turn off the flag // NPC->svFlags &= ~SVF_LOCKEDENEMY; //See if the player is closer than our current enemy if ( NPC->client->NPC_class != CLASS_RANCOR && NPC->client->NPC_class != CLASS_WAMPA //&& NPC->client->NPC_class != CLASS_SAND_CREATURE && NPC_CheckPlayerDistance() ) {//rancors, wampas & sand creatures don't care if player is closer, they always go with closest return qtrue; } //If we've gotten here alright, then our target it still valid if ( NPC_ValidEnemy( NPC->enemy ) ) return qtrue; newenemy = NPC_PickEnemyExt( checkAlerts ); //if we found one, take it as the enemy if( NPC_ValidEnemy( newenemy ) ) { G_SetEnemy( NPC, newenemy ); return qtrue; } return qfalse; } /* ------------------------- NPC_CheckEnemyExt ------------------------- */ qboolean NPC_CheckEnemyExt( qboolean checkAlerts ) { //Make sure we're ready to think again /* if ( NPCInfo->enemyCheckDebounceTime > level.time ) return qfalse; //Get our next think time NPCInfo->enemyCheckDebounceTime = level.time + NPC_GetCheckDelta(); //Attempt to find an enemy return NPC_FindEnemy(); */ return NPC_FindEnemy( checkAlerts ); } /* ------------------------- NPC_FacePosition ------------------------- */ qboolean NPC_FacePosition( vec3_t position, qboolean doPitch ) { vec3_t muzzle; vec3_t angles; float yawDelta; qboolean facing = qtrue; //Get the positions if ( NPC->client && (NPC->client->NPC_class == CLASS_RANCOR || NPC->client->NPC_class == CLASS_WAMPA) )// || NPC->client->NPC_class == CLASS_SAND_CREATURE) ) { CalcEntitySpot( NPC, SPOT_ORIGIN, muzzle ); muzzle[2] += NPC->r.maxs[2] * 0.75f; } else if ( NPC->client && NPC->client->NPC_class == CLASS_GALAKMECH ) { CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); } else { CalcEntitySpot( NPC, SPOT_HEAD_LEAN, muzzle );//SPOT_HEAD } //Find the desired angles GetAnglesForDirection( muzzle, position, angles ); NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); if ( NPC->enemy && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_ATST ) { // FIXME: this is kind of dumb, but it was the easiest way to get it to look sort of ok NPCInfo->desiredYaw += flrand( -5, 5 ) + sin( level.time * 0.004f ) * 7; NPCInfo->desiredPitch += flrand( -2, 2 ); } //Face that yaw NPC_UpdateAngles( qtrue, qtrue ); //Find the delta between our goal and our current facing yawDelta = AngleNormalize360( NPCInfo->desiredYaw - ( SHORT2ANGLE( ucmd.angles[YAW] + client->ps.delta_angles[YAW] ) ) ); //See if we are facing properly if ( fabs( yawDelta ) > VALID_ATTACK_CONE ) facing = qfalse; if ( doPitch ) { //Find the delta between our goal and our current facing float currentAngles = ( SHORT2ANGLE( ucmd.angles[PITCH] + client->ps.delta_angles[PITCH] ) ); float pitchDelta = NPCInfo->desiredPitch - currentAngles; //See if we are facing properly if ( fabs( pitchDelta ) > VALID_ATTACK_CONE ) facing = qfalse; } return facing; } /* ------------------------- NPC_FaceEntity ------------------------- */ qboolean NPC_FaceEntity( gentity_t *ent, qboolean doPitch ) { vec3_t entPos; //Get the positions CalcEntitySpot( ent, SPOT_HEAD_LEAN, entPos ); return NPC_FacePosition( entPos, doPitch ); } /* ------------------------- NPC_FaceEnemy ------------------------- */ qboolean NPC_FaceEnemy( qboolean doPitch ) { if ( NPC == NULL ) return qfalse; if ( NPC->enemy == NULL ) return qfalse; return NPC_FaceEntity( NPC->enemy, doPitch ); } /* ------------------------- NPC_CheckCanAttackExt ------------------------- */ qboolean NPC_CheckCanAttackExt( void ) { //We don't want them to shoot if( NPCInfo->scriptFlags & SCF_DONT_FIRE ) return qfalse; //Turn to face if ( NPC_FaceEnemy( qtrue ) == qfalse ) return qfalse; //Must have a clear line of sight to the target if ( NPC_ClearShot( NPC->enemy ) == qfalse ) return qfalse; return qtrue; } /* ------------------------- NPC_ClearLookTarget ------------------------- */ void NPC_ClearLookTarget( gentity_t *self ) { if ( !self->client ) { return; } if ( (self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) {//lookTarget is set by and to the monster that's holding you, no other operations can change that return; } self->client->renderInfo.lookTarget = ENTITYNUM_NONE;//ENTITYNUM_WORLD; self->client->renderInfo.lookTargetClearTime = 0; } /* ------------------------- NPC_SetLookTarget ------------------------- */ void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ) { if ( !self->client ) { return; } if ( (self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) {//lookTarget is set by and to the monster that's holding you, no other operations can change that return; } self->client->renderInfo.lookTarget = entNum; self->client->renderInfo.lookTargetClearTime = clearTime; } /* ------------------------- NPC_CheckLookTarget ------------------------- */ qboolean NPC_CheckLookTarget( gentity_t *self ) { if ( self->client ) { if ( self->client->renderInfo.lookTarget >= 0 && self->client->renderInfo.lookTarget < ENTITYNUM_WORLD ) {//within valid range if ( (&g_entities[self->client->renderInfo.lookTarget] == NULL) || !g_entities[self->client->renderInfo.lookTarget].inuse ) {//lookTarget not inuse or not valid anymore NPC_ClearLookTarget( self ); } else if ( self->client->renderInfo.lookTargetClearTime && self->client->renderInfo.lookTargetClearTime < level.time ) {//Time to clear lookTarget NPC_ClearLookTarget( self ); } else if ( g_entities[self->client->renderInfo.lookTarget].client && self->enemy && (&g_entities[self->client->renderInfo.lookTarget] != self->enemy) ) {//should always look at current enemy if engaged in battle... FIXME: this could override certain scripted lookTargets...??? NPC_ClearLookTarget( self ); } else { return qtrue; } } } return qfalse; } /* ------------------------- NPC_CheckCharmed ------------------------- */ extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); void NPC_CheckCharmed( void ) { if ( NPCInfo->charmedTime && NPCInfo->charmedTime < level.time && NPC->client ) {//we were charmed, set us back! NPC->client->playerTeam = NPC->genericValue1; NPC->client->enemyTeam = NPC->genericValue2; NPC->s.teamowner = NPC->genericValue3; NPC->client->leader = NULL; if ( NPCInfo->tempBehavior == BS_FOLLOW_LEADER ) { NPCInfo->tempBehavior = BS_DEFAULT; } G_ClearEnemy( NPC ); NPCInfo->charmedTime = 0; //say something to let player know you've snapped out of it G_AddVoiceEvent( NPC, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); } } void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex ) { mdxaBone_t boltMatrix; vec3_t result, angles; if (!self || !self->inuse) { return; } if (self->client) { //clients don't actually even keep r.currentAngles maintained VectorSet(angles, 0, self->client->ps.viewangles[YAW], 0); } else { VectorSet(angles, 0, self->r.currentAngles[YAW], 0); } if ( /*!self || ...haha (sorry, i'm tired)*/ !self->ghoul2 ) { return; } trap_G2API_GetBoltMatrix( self->ghoul2, modelIndex, boltIndex, &boltMatrix, angles, self->r.currentOrigin, level.time, NULL, self->modelScale ); if ( pos ) { BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, result ); VectorCopy( result, pos ); } } float NPC_EntRangeFromBolt( gentity_t *targEnt, int boltIndex ) { vec3_t org; if ( !targEnt ) { return Q3_INFINITE; } G_GetBoltPosition( NPC, boltIndex, org, 0 ); return (Distance( targEnt->r.currentOrigin, org )); } float NPC_EnemyRangeFromBolt( int boltIndex ) { return (NPC_EntRangeFromBolt( NPC->enemy, boltIndex )); } int NPC_GetEntsNearBolt( int *radiusEnts, float radius, int boltIndex, vec3_t boltOrg ) { vec3_t mins, maxs; int i; //get my handRBolt's position vec3_t org; G_GetBoltPosition( NPC, boltIndex, org, 0 ); VectorCopy( org, boltOrg ); //Setup the bbox to search in for ( i = 0; i < 3; i++ ) { mins[i] = boltOrg[i] - radius; maxs[i] = boltOrg[i] + radius; } //Get the number of entities in a given space return (trap_EntitiesInBox( mins, maxs, radiusEnts, 128 )); }