static void ReadGEntities(qboolean qbAutosave) { int iCount; gi.ReadFromSaveGame('NMED', (void *)&iCount, sizeof(iCount)); int iPreviousEntRead = -1; int i; for (i=0; i<iCount; i++) { int iEntIndex; gi.ReadFromSaveGame('EDNM', (void *)&iEntIndex, sizeof(iEntIndex)); if (iEntIndex >= globals.num_entities) { globals.num_entities = iEntIndex + 1; } if (iPreviousEntRead != iEntIndex-1) { for (int j=iPreviousEntRead+1; j!=iEntIndex; j++) { if ( g_entities[j].inuse ) // not actually necessary { G_FreeEntity(&g_entities[j]); } } } iPreviousEntRead = iEntIndex; // slightly naff syntax here, but makes a few ops clearer later... // gentity_t entity; gentity_t* pEntOriginal = &entity; gentity_t* pEnt = &g_entities[iEntIndex]; *pEntOriginal = *pEnt; // struct copy, so we can refer to original pEntOriginal->ghoul2.kill(); gi.unlinkentity(pEnt); Quake3Game()->FreeEntity( pEnt ); // // sneaky: destroy the ghoul2 object within this struct before binary-loading over the top of it... // gi.G2API_LoadSaveCodeDestructGhoul2Info(pEnt->ghoul2); pEnt->ghoul2.kill(); EvaluateFields(savefields_gEntity, (byte *)pEnt, (byte *)pEntOriginal, 'GENT', sizeof(*pEnt),qfalse); pEnt->ghoul2.kill(); // now for any fiddly bits... // if (pEnt->NPC) // will be qtrue/qfalse { gNPC_t tempNPC; EvaluateFields(savefields_gNPC, (byte *)&tempNPC,(byte *)pEntOriginal->NPC, 'GNPC', sizeof (*pEnt->NPC),qfalse); // so can we pinch the original's one or do we have to alloc a new one?... // if (pEntOriginal->NPC) { // pinch this G_Alloc handle... // pEnt->NPC = pEntOriginal->NPC; } else { // original didn't have one (hmmm...), so make a new one... // //assert(0); // I want to know about this, though not in release pEnt->NPC = (gNPC_t *) G_Alloc(sizeof(*pEnt->NPC)); } // copy over the one we've just loaded... // *pEnt->NPC = tempNPC; // struct copy //FIXME: do we need to do these too? /* if ( pEnt->s.number ) {//not player G_LoadAnimFileSet( *pEnt, *pEnt->NPC_type ); G_SetSkin( *pEnt, *pEnt->NPC_type, NULL );// it probably wasn't the default skin, do we need this at all? } */ } if (pEnt->client == (gclient_t*) -2) // one of Mike G's NPC clients? { gclient_t tempGClient; EvaluateFields(savefields_gClient, (byte *)&tempGClient, (byte *)pEntOriginal->client, 'GCLI', sizeof(*pEnt->client),qtrue);//qfalse); // can we pinch the original's client handle or do we have to alloc a new one?... // if (pEntOriginal->client) { // pinch this G_Alloc handle... // pEnt->client = pEntOriginal->client; } else { // original didn't have one (hmmm...) so make a new one... // pEnt->client = (gclient_t *) G_Alloc(sizeof(*pEnt->client)); } // copy over the one we've just loaded.... // *pEnt->client = tempGClient; // struct copy if ( pEnt->s.number ) {//not player G_ReloadSaberData( pEnt ); } } // Some Icarus thing... (probably) // if (pEnt->parms) // will be qtrue/qfalse { parms_t tempParms; gi.ReadFromSaveGame('PARM', &tempParms, sizeof(tempParms)); // so can we pinch the original's one or do we have to alloc a new one?... // if (pEntOriginal->parms) { // pinch this G_Alloc handle... // pEnt->parms = pEntOriginal->parms; } else { // original didn't have one, so make a new one... // pEnt->parms = (parms_t *) G_Alloc(sizeof(*pEnt->parms)); } // copy over the one we've just loaded... // *pEnt->parms = tempParms; // struct copy } if (pEnt->m_pVehicle) // will be qtrue/qfalse { Vehicle_t tempVehicle; EvaluateFields(savefields_gVHIC, (byte *)&tempVehicle,(byte *)pEntOriginal->m_pVehicle, 'VHIC', sizeof (*pEnt->m_pVehicle),qfalse); // so can we pinch the original's one or do we have to alloc a new one?... // if (pEntOriginal->m_pVehicle) { // pinch this G_Alloc handle... // pEnt->m_pVehicle = pEntOriginal->m_pVehicle; } else { // original didn't have one, so make a new one... // pEnt->m_pVehicle = (Vehicle_t *) gi.Malloc( sizeof(Vehicle_t), TAG_G_ALLOC, qfalse ); } // copy over the one we've just loaded... // *pEnt->m_pVehicle = tempVehicle; // struct copy } // the scary ghoul2 stuff... (fingers crossed) // { char *pGhoul2Data = NULL; gi.ReadFromSaveGame('GHL2', 0, 0, (void**)&pGhoul2Data); gi.G2API_LoadGhoul2Models(pEnt->ghoul2, pGhoul2Data); // if it's going to crash anywhere... <g> gi.Free(pGhoul2Data); } // gi.unlinkentity (pEntOriginal); // ICARUS_FreeEnt( pEntOriginal ); // *pEntOriginal = *pEnt; // struct copy // qboolean qbLinked = pEntOriginal->linked; // pEntOriginal->linked = qfalse; // if (qbLinked) // { // gi.linkentity (pEntOriginal); // } // because the sytem stores sfx_t handles directly instead of the set, we have to reget the set's sfx_t... // if (pEnt->s.eType == ET_MOVER && pEnt->s.loopSound>0) { if ( VALIDSTRING( pEnt->soundSet )) { extern int BMS_MID; // from g_mover pEnt->s.loopSound = CAS_GetBModelSound( pEnt->soundSet, BMS_MID ); if (pEnt->s.loopSound == -1) { pEnt->s.loopSound = 0; } } } // NPCs and other ents store waypoints that aren't valid after a load pEnt->waypoint = 0; qboolean qbLinked = pEnt->linked; pEnt->linked = qfalse; if (qbLinked) { gi.linkentity (pEnt); } } //Read in all the entity timers TIMER_Load();//ReadEntityTimers(); if (!qbAutosave) { // now zap any g_ents that were inuse when the level was loaded, but are no longer in use in the saved version // that we've just loaded... // for (i=iPreviousEntRead+1; i<globals.num_entities; i++) { if ( g_entities[i].inuse ) // not actually necessary { G_FreeEntity(&g_entities[i]); } } //Load ICARUS information Quake3Game()->ClearEntityList(); IIcarusInterface::GetIcarus()->Load(); // check that Icarus has loaded everything it saved out by having a marker chunk after it... // static int iBlah = 1234; gi.ReadFromSaveGame('ICOK', &iBlah, sizeof(iBlah)); } if (!qbAutosave) { ReadInUseBits();//really shouldn't need to read these bits in at all, just restore them from the ents... } }
//----------------------------------------------------- static qboolean turret_find_enemies( gentity_t *self ) //----------------------------------------------------- { qboolean found = qfalse; int i, count; float bestDist = self->radius * self->radius; float enemyDist; vec3_t enemyDir, org, org2; gentity_t *entity_list[MAX_GENTITIES], *target, *bestTarget = NULL; trace_t tr; gentity_t *top = &g_entities[self->r.ownerNum]; if ( !top ) { return qfalse; } if ( self->aimDebounceTime > level.time ) // time since we've been shut off { // We were active and alert, i.e. had an enemy in the last 3 secs if ( self->timestamp < level.time ) { //G_Sound(self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/ping.wav" )); self->timestamp = level.time + 1000; } } VectorCopy( top->r.currentOrigin, org2 ); count = G_RadiusList( org2, self->radius, self, qtrue, entity_list ); for ( i = 0; i < count; i++ ) { target = entity_list[i]; if ( !target->client ) { // only attack clients continue; } if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET )) { continue; } if ( target->client->sess.sessionTeam == TEAM_SPECTATOR ) { continue; } if ( self->alliedTeam ) { if ( target->client ) { if ( target->client->sess.sessionTeam == self->alliedTeam ) { // A bot/client/NPC we don't want to shoot continue; } } else if ( target->teamnodmg == self->alliedTeam ) { // An ent we don't want to shoot continue; } } if ( !trap_InPVS( org2, target->r.currentOrigin )) { continue; } VectorCopy( target->r.currentOrigin, org ); org[2] += target->r.maxs[2]*0.5f; trap_Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT ); if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number )) { // Only acquire if have a clear shot, Is it in range and closer than our best? VectorSubtract( target->r.currentOrigin, top->r.currentOrigin, enemyDir ); enemyDist = VectorLengthSquared( enemyDir ); if ( enemyDist < bestDist // all things equal, keep current || (!Q_stricmp( "atst_vehicle", target->NPC_type ) && bestTarget && Q_stricmp( "atst_vehicle", bestTarget->NPC_type ) ) )//target AT-STs over non-AT-STs... FIXME: must be a better, easier way to tell this, no? { if ( self->attackDebounceTime < level.time ) { // We haven't fired or acquired an enemy in the last 2 seconds-start-up sound //G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" )); // Wind up turrets for a bit self->attackDebounceTime = level.time + 1400; } bestTarget = target; bestDist = enemyDist; found = qtrue; } } } if ( found ) { G_SetEnemy( self, bestTarget ); if ( VALIDSTRING( self->target2 )) { G_UseTargets2( self, self, self->target2 ); } } return found; }
//---------------------------------------------------------- void fx_runner_link( gentity_t *ent ) { vec3_t dir; if ( ent->target ) { // try to use the target to override the orientation gentity_t *target = NULL; target = G_Find( target, FOFS(targetname), ent->target ); if ( !target ) { // Bah, no good, dump a warning, but continue on and use the UP vector Com_Printf( "fx_runner_link: target specified but not found: %s\n", ent->target ); Com_Printf( " -assuming UP orientation.\n" ); } else { // Our target is valid so let's override the default UP vector VectorSubtract( target->s.origin, ent->s.origin, dir ); VectorNormalize( dir ); vectoangles( dir, ent->s.angles ); } } // don't really do anything with this right now other than do a check to warn the designers if the target2 is bogus if ( ent->target2 ) { gentity_t *target = NULL; target = G_Find( target, FOFS(targetname), ent->target2 ); if ( !target ) { // Target2 is bogus, but we can still continue Com_Printf( "fx_runner_link: target2 was specified but is not valid: %s\n", ent->target2 ); } } G_SetAngles( ent, ent->s.angles ); if ( ent->spawnflags & 1 || ent->spawnflags & 2 ) // STARTOFF || ONESHOT { // We won't even consider thinking until we are used ent->nextthink = -1; } else { if ( VALIDSTRING( ent->soundSet ) == true ) { ent->s.loopSound = CAS_GetBModelSound( ent->soundSet, BMS_MID ); if ( ent->s.loopSound < 0 ) { ent->s.loopSound = 0; } } // Let's get to work right now! ent->e_ThinkFunc = thinkF_fx_runner_think; ent->nextthink = level.time + 200; // wait a small bit, then start working } // make us useable if we can be targeted if ( ent->targetname ) { ent->e_UseFunc = useF_fx_runner_use; } }
//---------------------------------------------------------- void fx_runner_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { if (self->s.isPortalEnt) { //rww - mark it as broadcast upon first use if it's within the area of a skyportal self->svFlags |= SVF_BROADCAST; } if ( self->spawnflags & 2 ) // ONESHOT { // call the effect with the desired position and orientation, as a safety thing, // make sure we aren't thinking at all. fx_runner_think( self ); self->nextthink = -1; if ( self->target2 ) { // let our target know that we have spawned an effect G_UseTargets2( self, self, self->target2 ); } if ( VALIDSTRING( self->soundSet ) == true ) { G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_START )); } } else { // ensure we are working with the right think function self->e_ThinkFunc = thinkF_fx_runner_think; // toggle our state if ( self->nextthink == -1 ) { // NOTE: we fire the effect immediately on use, the fx_runner_think func will set // up the nextthink time. fx_runner_think( self ); if ( VALIDSTRING( self->soundSet ) == true ) { G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_START )); self->s.loopSound = CAS_GetBModelSound( self->soundSet, BMS_MID ); if ( self->s.loopSound < 0 ) { self->s.loopSound = 0; } } } else { // turn off for now self->nextthink = -1; if ( VALIDSTRING( self->soundSet ) == true ) { G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_END )); self->s.loopSound = 0; } } } }
//----------------------------------------------------- static qboolean turretG2_find_enemies( gentity_t *self ) //----------------------------------------------------- { qboolean found = qfalse; int i, count; float bestDist = self->radius * self->radius; float enemyDist; vec3_t enemyDir, org, org2; qboolean foundClient = qfalse; gentity_t *entity_list[MAX_GENTITIES], *target, *bestTarget = NULL; if ( self->aimDebounceTime > level.time ) // time since we've been shut off { // We were active and alert, i.e. had an enemy in the last 3 secs if ( self->painDebounceTime < level.time ) { if ( !(self->spawnflags&SPF_TURRETG2_TURBO) ) { G_Sound(self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/ping.wav" )); } self->painDebounceTime = level.time + 1000; } } VectorCopy( self->r.currentOrigin, org2 ); if ( self->spawnflags & 2 ) { org2[2] += 20; } else { org2[2] -= 20; } count = G_RadiusList( org2, self->radius, self, qtrue, entity_list ); for ( i = 0; i < count; i++ ) { trace_t tr; target = entity_list[i]; if ( !target->client ) { // only attack clients if ( !(target->flags&FL_BBRUSH)//not a breakable brush || !target->takedamage//is a bbrush, but invincible || (target->NPC_targetname&&self->targetname&&Q_stricmp(target->NPC_targetname,self->targetname)!=0) )//not in invicible bbrush, but can only be broken by an NPC that is not me { continue; } //else: we will shoot at bbrushes! } if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET )) { continue; } if ( target->client && target->client->sess.sessionTeam == TEAM_SPECTATOR ) { continue; } if ( self->alliedTeam ) { if ( target->client ) { if ( target->client->sess.sessionTeam == self->alliedTeam ) { // A bot/client/NPC we don't want to shoot continue; } } else if ( target->teamnodmg == self->alliedTeam ) { // An ent we don't want to shoot continue; } } if ( !trap_InPVS( org2, target->r.currentOrigin )) { continue; } if ( target->client ) { VectorCopy( target->client->renderInfo.eyePoint, org ); } else { VectorCopy( target->r.currentOrigin, org ); } if ( self->spawnflags & 2 ) { org[2] -= 15; } else { org[2] += 5; } trap_Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT ); if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number )) { // Only acquire if have a clear shot, Is it in range and closer than our best? VectorSubtract( target->r.currentOrigin, self->r.currentOrigin, enemyDir ); enemyDist = VectorLengthSquared( enemyDir ); if ( enemyDist < bestDist || (target->client && !foundClient))// all things equal, keep current { if ( self->attackDebounceTime < level.time ) { // We haven't fired or acquired an enemy in the last 2 seconds-start-up sound if ( !(self->spawnflags&SPF_TURRETG2_TURBO) ) { G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" )); } // Wind up turrets for a bit self->attackDebounceTime = level.time + 1400; } bestTarget = target; bestDist = enemyDist; found = qtrue; if ( target->client ) { //prefer clients over non-clients foundClient = qtrue; } } } } if ( found ) { /* if ( !self->enemy ) {//just aquired one AddSoundEvent( bestTarget, self->r.currentOrigin, 256, AEL_DISCOVERED ); AddSightEvent( bestTarget, self->r.currentOrigin, 512, AEL_DISCOVERED, 20 ); } */ G_SetEnemy( self, bestTarget ); if ( VALIDSTRING( self->target2 )) { G_UseTargets2( self, self, self->target2 ); } } return found; }
void ReadGEntities(qboolean qbAutosave) { int iCount; int i; gi.ReadFromSaveGame('NMED', (void *)&iCount, sizeof(iCount), NULL); int iPreviousEntRead = -1; for (i=0; i<iCount; i++) { int iEntIndex; gi.ReadFromSaveGame('EDNM', (void *)&iEntIndex, sizeof(iEntIndex), NULL); if (iEntIndex >= globals.num_entities) { globals.num_entities = iEntIndex + 1; } if (iPreviousEntRead != iEntIndex-1) { for (int j=iPreviousEntRead+1; j!=iEntIndex; j++) { if ( g_entities[j].inuse ) // not actually necessary { G_FreeEntity(&g_entities[j]); } } } iPreviousEntRead = iEntIndex; // slightly naff syntax here, but makes a few ops clearer later... // gentity_t entity; // gentity_t* pEntOriginal = &g_entities[iEntIndex]; // gentity_t* pEnt = &entity; gentity_t* pEntOriginal = &entity; gentity_t* pEnt = &g_entities[iEntIndex]; *pEntOriginal = *pEnt; // struct copy, so we can refer to original pEntOriginal->ghoul2.kill(); gi.unlinkentity(pEnt); ICARUS_FreeEnt (pEnt); // // sneaky: destroy the ghoul2 object within this struct before binary-loading over the top of it... // gi.G2API_LoadSaveCodeDestructGhoul2Info(pEnt->ghoul2); pEnt->ghoul2.kill(); EvaluateFields(savefields_gEntity, (byte *)pEnt, (byte *)pEntOriginal, 'GENT', sizeof(*pEnt),qfalse); pEnt->ghoul2.kill(); // now for any fiddly bits... // if (pEnt->NPC) // will be qtrue/qfalse { gNPC_t tempNPC; EvaluateFields(savefields_gNPC, (byte *)&tempNPC,(byte *)pEntOriginal->NPC, 'GNPC', sizeof (*pEnt->NPC),qfalse); // so can we pinch the original's one or do we have to alloc a new one?... // if (pEntOriginal->NPC) { // pinch this G_Alloc handle... // pEnt->NPC = pEntOriginal->NPC; } else { // original didn't have one (hmmm...), so make a new one... // //assert(0); // I want to know about this, though not in release pEnt->NPC = (gNPC_t *) G_Alloc(sizeof(*pEnt->NPC)); } // copy over the one we've just loaded... // *pEnt->NPC = tempNPC; // struct copy } if (pEnt->client == (gclient_t*) -2) // one of Mike G's NPC clients? { gclient_t tempGClient; EvaluateFields(savefields_gClient, (byte *)&tempGClient, (byte *)pEntOriginal->client, 'GCLI', sizeof(*pEnt->client),qfalse); // can we pinch the original's client handle or do we have to alloc a new one?... // if (pEntOriginal->client) { // pinch this G_Alloc handle... // pEnt->client = pEntOriginal->client; } else { // original didn't have one (hmmm...) so make a new one... // pEnt->client = (gclient_t *) G_Alloc(sizeof(*pEnt->client)); } // copy over the one we've just loaded.... // *pEnt->client = tempGClient; // struct copy } // Some Icarus thing... (probably) // if (pEnt->parms) // will be qtrue/qfalse { parms_t tempParms; gi.ReadFromSaveGame('PARM', &tempParms, sizeof(tempParms), NULL); // so can we pinch the original's one or do we have to alloc a new one?... // if (pEntOriginal->parms) { // pinch this G_Alloc handle... // pEnt->parms = pEntOriginal->parms; } else { // original didn't have one, so make a new one... // pEnt->parms = (parms_t *) G_Alloc(sizeof(*pEnt->parms)); } // copy over the one we've just loaded... // *pEnt->parms = tempParms; // struct copy } // the scary ghoul2 stuff... (fingers crossed) // { char *pGhoul2Data = NULL; int iGhoul2Size = 0; gi.ReadFromSaveGame('GL2S', &iGhoul2Size, sizeof(iGhoul2Size), NULL); pGhoul2Data = (char *) gi.Malloc(iGhoul2Size, TAG_TEMP_WORKSPACE, qfalse); /* if (pGhoul2Data == 0) { G_Error("ReadGEntities(): ent %d/%d (targetname: '%s'), failed to alloc %d bytes for Ghoul2 load",i,iCount,pEnt->targetname,iGhoul2Size); } */ gi.ReadFromSaveGame('GHL2', pGhoul2Data, iGhoul2Size, NULL); gi.G2API_LoadGhoul2Models(pEnt->ghoul2, pGhoul2Data); // if it's going to crash anywhere... <g> gi.Free(pGhoul2Data); } // gi.unlinkentity (pEntOriginal); // ICARUS_FreeEnt( pEntOriginal ); // *pEntOriginal = *pEnt; // struct copy // qboolean qbLinked = pEntOriginal->linked; // pEntOriginal->linked = qfalse; // if (qbLinked) // { // gi.linkentity (pEntOriginal); // } // because the sytem stores sfx_t handles directly instead of the set, we have to reget the set's sfx_t... // if (pEnt->s.eType == ET_MOVER && pEnt->s.loopSound>0) { if ( VALIDSTRING( pEnt->soundSet )) { extern int BMS_MID; // from g_mover pEnt->s.loopSound = CAS_GetBModelSound( pEnt->soundSet, BMS_MID ); if (pEnt->s.loopSound == -1) { pEnt->s.loopSound = 0; } } } qboolean qbLinked = pEnt->linked; pEnt->linked = qfalse; if (qbLinked) { gi.linkentity (pEnt); } } //Read in all the entity timers TIMER_Load();//ReadEntityTimers(); if (!qbAutosave) { // now zap any g_ents that were inuse when the level was loaded, but are no longer in use in the saved version // that we've just loaded... // for (i=iPreviousEntRead+1; i<globals.num_entities; i++) { if ( g_entities[i].inuse ) // not actually necessary { G_FreeEntity(&g_entities[i]); } } //Load ICARUS information ICARUS_EntList.clear(); iICARUS->Load(); // check that Icarus has loaded everything it saved out by having a marker chunk after it... // static int iBlah = 1234; gi.ReadFromSaveGame('ICOK', &iBlah, sizeof(iBlah), NULL); } if (!qbAutosave) { ReadInUseBits();//really shouldn't need to read these bits in at all, just restore them from the ents... } }
/* =============== NPC_Pain =============== */ void NPC_Pain( gentity_t *self, gentity_t *other, int damage ) { team_t otherTeam = TEAM_FREE; if ( self->NPC == NULL ) return; if ( other == NULL ) return; //or just remove ->pain in player_die? if ( self->client->ps.pm_type == PM_DEAD ) return; if ( other == self ) return; //MCG: Ignore damage from your own team for now if ( other->client ) { otherTeam = other->client->playerTeam; if ( otherTeam == TEAM_DISGUISE ) { otherTeam = TEAM_STARFLEET; } } if ( other != self->enemy && self->client->playerTeam && other->client && otherTeam == self->client->playerTeam ) {//Still run pain and flee scripts if ( self->client && self->NPC ) {//Run any pain instructions if ( self->health <= (self->max_health/3) && ( VALIDSTRING( self->behaviorSet[BSET_FLEE] ) ) ) { G_ActivateBehavior(self, BSET_FLEE); } else if( VALIDSTRING( self->behaviorSet[BSET_PAIN] ) ) { G_ActivateBehavior(self, BSET_PAIN); } } return; } //Hirogen boss with shield if ( ( self->client->playerTeam == TEAM_HIROGEN ) ) { if ( ( Q_stricmp( self->NPC_type, "hirogenalpha" ) == 0 ) && ( self->s.powerups & ( 1 << PW_HIROGEN_SHIELD ) ) ) return; } SaveNPCGlobals(); SetNPCGlobals( self ); //Do extra bits if ( NPCInfo->ignorePain == qfalse ) { //Check to take a new enemy NPC_CheckAttacker( other ); if ( damage != -1 ) {//don't play pain anim //Set our proper pain animation NPC_ChoosePainAnimation( self, damage ); } } //Attempt to run any pain instructions if(self->client && self->NPC) { //FIXME: This needs better heuristics perhaps if(self->health <= (self->max_health/3) && ( VALIDSTRING( self->behaviorSet[BSET_FLEE] ) ) ) { G_ActivateBehavior(self, BSET_FLEE); } else if( VALIDSTRING( self->behaviorSet[BSET_PAIN] ) ) { G_ActivateBehavior(self, BSET_PAIN); } } //Attempt to fire any paintargets we might have if( self->paintarget && self->paintarget[0] ) { G_UseTargets2(self, other, self->paintarget); } RestoreNPCGlobals(); }