/*QUAKED target_print (1 0 0) (-8 -8 -8) (8 8 8) redteam blueteam private "message" text to print "wait" don't fire off again if triggered within this many milliseconds ago If "private", only the activator gets the message. If no checks, all clients get the message. */ void Use_Target_Print (gentity_t *ent, gentity_t *other, gentity_t *activator) { if (!ent || !ent->inuse) { Com_Printf("ERROR: Bad ent in Use_Target_Print"); return; } if (ent->wait) { if (ent->genericValue14 >= level.time) { return; } ent->genericValue14 = level.time + ent->wait; } #ifndef FINAL_BUILD if (!ent || !ent->inuse) { Com_Error(ERR_DROP, "Bad ent in Use_Target_Print"); } else if (!activator || !activator->inuse) { Com_Error(ERR_DROP, "Bad activator in Use_Target_Print"); } if (ent->genericValue15 > level.time) { Com_Printf("TARGET PRINT ERRORS:\n"); if (activator && activator->classname && activator->classname[0]) { Com_Printf("activator classname: %s\n", activator->classname); } if (activator && activator->target && activator->target[0]) { Com_Printf("activator target: %s\n", activator->target); } if (activator && activator->targetname && activator->targetname[0]) { Com_Printf("activator targetname: %s\n", activator->targetname); } if (ent->targetname && ent->targetname[0]) { Com_Printf("print targetname: %s\n", ent->targetname); } Com_Printf( "target_print used in quick succession, fix it! See the console for details." ); } ent->genericValue15 = level.time + 5000; #endif G_ActivateBehavior(ent,BSET_USE); if ( ( ent->spawnflags & 4 ) ) {//private, to one client only if (!activator || !activator->inuse) { Com_Printf("ERROR: Bad activator in Use_Target_Print"); } if ( activator && activator->client ) {//make sure there's a valid client ent to send it to if (ent->message[0] == '@' && ent->message[1] != '@') { trap_SendServerCommand( activator-g_entities, va("cps \"%s\"", ent->message )); } else { trap_SendServerCommand( activator-g_entities, va("cp \"%s\"", ent->message )); } } //NOTE: change in functionality - if there *is* no valid client ent, it won't send it to anyone at all return; } if ( ent->spawnflags & 3 ) { if ( ent->spawnflags & 1 ) { if (ent->message[0] == '@' && ent->message[1] != '@') { G_TeamCommand( TEAM_RED, va("cps \"%s\"", ent->message) ); } else { G_TeamCommand( TEAM_RED, va("cp \"%s\"", ent->message) ); } } if ( ent->spawnflags & 2 ) { if (ent->message[0] == '@' && ent->message[1] != '@') { G_TeamCommand( TEAM_BLUE, va("cps \"%s\"", ent->message) ); } else { G_TeamCommand( TEAM_BLUE, va("cp \"%s\"", ent->message) ); } } return; } if (ent->message[0] == '@' && ent->message[1] != '@') { trap_SendServerCommand( -1, va("cps \"%s\"", ent->message )); } else { trap_SendServerCommand( -1, va("cp \"%s\"", ent->message )); } }
void target_random_use(gentity_t *self, gentity_t *other, gentity_t *activator) { int t_count = 0, pick; gentity_t *t = NULL; //gi.Printf("target_random %s used by %s (entnum %d)\n", self->targetname, activator->targetname, activator->s.number ); G_ActivateBehavior(self,BSET_USE); if(self->spawnflags & 1) { self->e_UseFunc = useF_NULL; } while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL ) { if (t != self) { t_count++; } } if(!t_count) { return; } if(t_count == 1) { G_UseTargets (self, activator); return; } //FIXME: need a seed pick = Q_irand(1, t_count); t_count = 0; while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL ) { if (t != self) { t_count++; } else { continue; } if (t == self) { // gi.Printf ("WARNING: Entity used itself.\n"); } else if(t_count == pick) { if (t->e_UseFunc != useF_NULL) // check can be omitted { GEntity_UseFunc(t, self, activator); return; } } if (!self->inuse) { gi.Printf("entity was removed while using targets\n"); return; } } }
void scriptrunner_run (gentity_t *self) { /* if (self->behaviorSet[BSET_USE]) { char newname[MAX_FILENAME_LENGTH]; sprintf((char *) &newname, "%s/%s", Q3_SCRIPT_DIR, self->behaviorSet[BSET_USE] ); ICARUS_RunScript( self, newname ); } */ if ( self->count != -1 ) { if ( self->count <= 0 ) { self->e_UseFunc = useF_NULL; self->behaviorSet[BSET_USE] = NULL; return; } else { --self->count; } } if (self->behaviorSet[BSET_USE]) { if ( self->spawnflags & 1 ) { if ( !self->activator ) { Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "target_scriptrunner tried to run on invalid entity!\n"); return; } if ( self->activator->m_iIcarusID == IIcarusInterface::ICARUS_INVALID ) {//Need to be initialized through ICARUS if ( !self->activator->script_targetname || !self->activator->script_targetname[0] ) { //We don't have a script_targetname, so create a new one self->activator->script_targetname = va( "newICARUSEnt%d", numNewICARUSEnts++ ); } if ( Quake3Game()->ValidEntity( self->activator ) ) { Quake3Game()->InitEntity( self->activator ); } else { Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "target_scriptrunner tried to run on invalid ICARUS activator!\n"); return; } } Quake3Game()->DebugPrint( IGameInterface::WL_VERBOSE, "target_scriptrunner running %s on activator %s\n", self->behaviorSet[BSET_USE], self->activator->targetname ); Quake3Game()->RunScript( self->activator, self->behaviorSet[BSET_USE] ); } else { if ( self->activator ) { Quake3Game()->DebugPrint( IGameInterface::WL_VERBOSE, "target_scriptrunner %s used by %s\n", self->targetname, self->activator->targetname ); } G_ActivateBehavior( self, BSET_USE ); } } if ( self->wait ) { self->nextthink = level.time + self->wait; } }
void trigger_entdist_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { vec3_t diff; gentity_t *found = NULL; gentity_t *owner = NULL; qboolean useflag; const char *token, *holdString; if ( self->svFlags & SVF_INACTIVE ) // Don't use INACTIVE return; G_ActivateBehavior(self,BSET_USE); if(self->ownername && self->ownername[0]) { owner = G_Find(NULL, FOFS(targetname), self->ownername); } if(owner == NULL) { owner = self; } self->activator = activator; useflag = qfalse; self->svFlags |= SVF_INACTIVE; // Make it inactive after one use if (self->spawnflags & ENTDIST_PLAYER) // Look for player??? { found = &g_entities[0]; if (found) { VectorSubtract(owner->currentOrigin, found->currentOrigin, diff); if(VectorLength(diff) < self->count) { useflag = qtrue; } } } if ((self->spawnflags & ENTDIST_NPC) && (!useflag)) { holdString = self->NPC_target; while (holdString) { token = COM_Parse( &holdString); if ( !token ) // Nothing left to look at { break; } found = G_Find(found, FOFS(targetname), token); // Look for the specified NPC if (found) //Found??? { VectorSubtract(owner->currentOrigin, found->currentOrigin, diff); if(VectorLength(diff) < self->count) // Within distance { useflag = qtrue; break; } } } } if (useflag) { G_UseTargets2 (self, self->activator, self->target); } else if (self->target2) { // This is the negative target G_UseTargets2 (self, self->activator, self->target2); } }
// the trigger was just activated // ent->activator should be set to the activator so it can be held through a delay // so wait for the delay time before firing void multi_trigger_run( gentity_t *ent ) { ent->think = 0; G_ActivateBehavior( ent, BSET_USE ); if ( ent->soundSet && ent->soundSet[0] ) { trap->SetConfigstring( CS_GLOBAL_AMBIENT_SET, ent->soundSet ); } if (ent->genericValue4) { //we want to activate target3 for team1 or target4 for team2 if (ent->genericValue4 == SIEGETEAM_TEAM1 && ent->target3 && ent->target3[0]) { G_UseTargets2(ent, ent->activator, ent->target3); } else if (ent->genericValue4 == SIEGETEAM_TEAM2 && ent->target4 && ent->target4[0]) { G_UseTargets2(ent, ent->activator, ent->target4); } ent->genericValue4 = 0; } G_UseTargets (ent, ent->activator); if ( ent->noise_index ) { G_Sound( ent->activator, CHAN_AUTO, ent->noise_index ); } if ( ent->target2 && ent->target2[0] && ent->wait >= 0 ) { ent->think = trigger_cleared_fire; ent->nextthink = level.time + ent->speed; } else if ( ent->wait > 0 ) { if ( ent->painDebounceTime != level.time ) {//first ent to touch it this frame //ent->e_ThinkFunc = thinkF_multi_wait; ent->nextthink = level.time + ( ent->wait + ent->random * crandom() ) * 1000; ent->painDebounceTime = level.time; } } else if ( ent->wait < 0 ) { // we can't just remove (self) here, because this is a touch function // called while looping through area links... ent->r.contents &= ~CONTENTS_TRIGGER;//so the EntityContact trace doesn't have to be done against me ent->think = 0; ent->use = 0; //Don't remove, Icarus may barf? //ent->nextthink = level.time + FRAMETIME; //ent->think = G_FreeEntity; } if( ent->activator && ent->activator->client ) { // mark the trigger as being touched by the player ent->aimDebounceTime = level.time; } }
void scriptrunner_run (gentity_t *self) { if ( self->count != -1 ) { if ( self->count <= 0 ) { self->use = 0; self->behaviorSet[BSET_USE] = NULL; return; } else { --self->count; } } if (self->behaviorSet[BSET_USE]) { if ( self->spawnflags & 1 ) { if ( !self->activator ) { if (g_developer.integer) { Com_Printf("target_scriptrunner tried to run on invalid entity!\n"); } return; } //if ( !self->activator->sequencer || !self->activator->taskManager ) if (!trap_ICARUS_IsInitialized(self->s.number)) {//Need to be initialized through ICARUS if ( !self->activator->script_targetname || !self->activator->script_targetname[0] ) { //We don't have a script_targetname, so create a new one self->activator->script_targetname = va( "newICARUSEnt%d", numNewICARUSEnts++ ); } if ( trap_ICARUS_ValidEnt( self->activator ) ) { trap_ICARUS_InitEnt( self->activator ); } else { if (g_developer.integer) { Com_Printf("target_scriptrunner tried to run on invalid ICARUS activator!\n"); } return; } } if (g_developer.integer) { Com_Printf( "target_scriptrunner running %s on activator %s\n", self->behaviorSet[BSET_USE], self->activator->targetname ); } trap_ICARUS_RunScript( self->activator, va( "%s/%s", Q3_SCRIPT_DIR, self->behaviorSet[BSET_USE] ) ); } else { if ( g_developer.integer && self->activator ) { Com_Printf( "target_scriptrunner %s used by %s\n", self->targetname, self->activator->targetname ); } G_ActivateBehavior( self, BSET_USE ); } } if ( self->wait ) { self->nextthink = level.time + self->wait; } }
void target_level_change_use(gentity_t *self, gentity_t *other, gentity_t *activator) { G_ActivateBehavior(self,BSET_USE); trap_SendConsoleCommand(EXEC_NOW, va("map %s", self->message)); }
/* =============== NPC_ExecuteBState MCG NPC Behavior state thinking =============== */ void NPC_ExecuteBState ( gentity_t *self)//, int msec ) { bState_t bState; NPC_HandleAIFlags(); //FIXME: these next three bits could be a function call, some sort of setup/cleanup func //Lookmode must be reset every think cycle if(NPC->delayScriptTime && NPC->delayScriptTime <= level.time) { G_ActivateBehavior( NPC, BSET_DELAYED); NPC->delayScriptTime = 0; } //Clear this and let bState set it itself, so it automatically handles changing bStates... but we need a set bState wrapper func NPCInfo->combatMove = qfalse; //Execute our bState if(NPCInfo->tempBehavior) {//Overrides normal behavior until cleared bState = NPCInfo->tempBehavior; } else { if(!NPCInfo->behaviorState) NPCInfo->behaviorState = NPCInfo->defaultBehavior; bState = NPCInfo->behaviorState; } //Pick the proper bstate for us and run it NPC_RunBehavior( self->client->playerTeam, bState ); if ( NPC->enemy ) { if ( !NPC->enemy->inuse ) {//just in case bState doesn't catch this G_ClearEnemy( NPC ); } } if ( NPC->client->ps.saberLockTime && NPC->client->ps.saberLockEnemy != ENTITYNUM_NONE ) { NPC_SetLookTarget( NPC, NPC->client->ps.saberLockEnemy, level.time+1000 ); } else if ( !NPC_CheckLookTarget( NPC ) ) { if ( NPC->enemy ) { NPC_SetLookTarget( NPC, NPC->enemy->s.number, 0 ); } } if ( NPC->enemy ) { if(NPC->enemy->flags & FL_DONT_SHOOT) { ucmd.buttons &= ~BUTTON_ATTACK; ucmd.buttons &= ~BUTTON_ALT_ATTACK; } else if ( NPC->client->playerTeam != NPCTEAM_ENEMY && NPC->enemy->NPC && (NPC->enemy->NPC->surrenderTime > level.time || (NPC->enemy->NPC->scriptFlags&SCF_FORCED_MARCH)) ) {//don't shoot someone who's surrendering if you're a good guy ucmd.buttons &= ~BUTTON_ATTACK; ucmd.buttons &= ~BUTTON_ALT_ATTACK; } if(client->ps.weaponstate == WEAPON_IDLE) { client->ps.weaponstate = WEAPON_READY; } } else { if(client->ps.weaponstate == WEAPON_READY) { client->ps.weaponstate = WEAPON_IDLE; } } if(!(ucmd.buttons & BUTTON_ATTACK) && NPC->attackDebounceTime > level.time) {//We just shot but aren't still shooting, so hold the gun up for a while if(client->ps.weapon == WP_SABER ) {//One-handed NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY1,SETANIM_FLAG_NORMAL); } else if(client->ps.weapon == WP_BRYAR_PISTOL) {//Sniper pose NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); } } else if ( !NPC->enemy )//HACK! { { if( NPC->s.torsoAnim == TORSO_WEAPONREADY1 || NPC->s.torsoAnim == TORSO_WEAPONREADY3 ) {//we look ready for action, using one of the first 2 weapon, let's rest our weapon on our shoulder NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONIDLE3,SETANIM_FLAG_NORMAL); } } } NPC_CheckAttackHold(); NPC_ApplyScriptFlags(); //cliff and wall avoidance NPC_AvoidWallsAndCliffs(); // run the bot through the server like it was a real client //=== Save the ucmd for the second no-think Pmove ============================ ucmd.serverTime = level.time - 50; memcpy( &NPCInfo->last_ucmd, &ucmd, sizeof( usercmd_t ) ); if ( !NPCInfo->attackHoldTime ) { NPCInfo->last_ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK);//so we don't fire twice in one think } //============================================================================ NPC_CheckAttackScript(); NPC_KeepCurrentFacing(); if ( !NPC->next_roff_time || NPC->next_roff_time < level.time ) {//If we were following a roff, we don't do normal pmoves. ClientThink( NPC->s.number, &ucmd ); } else { NPC_ApplyRoff(); } // end of thinking cleanup NPCInfo->touchedByPlayer = NULL; NPC_CheckPlayerAim(); NPC_CheckAllClear(); }
/* =============== NPC_ExecuteBState MCG NPC Behavior state thinking =============== */ void NPC_ExecuteBState ( gentity_t *self)//, int msec ) { bState_t bState; NPC_HandleAIFlags(); //FIXME: these next three bits could be a function call, some sort of setup/cleanup func //Lookmode must be reset every think cycle if(NPC->aimDebounceTime < level.time) { NPCInfo->lookMode = LT_NONE; } if(NPC->delayScriptTime && NPC->delayScriptTime <= level.time) { G_ActivateBehavior( NPC, BSET_DELAYED); NPC->delayScriptTime = 0; } //Clear this and let bState set it itself, so it automatically handles changing bStates... but we need a set bState wrapper func NPCInfo->combatMove = qfalse; //Execute our bState if(NPCInfo->tempBehavior) {//Overrides normal behavior until cleared bState = NPCInfo->tempBehavior; } else { if(!NPCInfo->behaviorState) NPCInfo->behaviorState = NPCInfo->defaultBehavior; bState = NPCInfo->behaviorState; } //Pick the proper bstate for us and run it NPC_RunBehavior( self->client->playerTeam, bState ); //FIXME: Make these a func call if(bState != BS_FORMATION) {//So we know to re-acquire our closest squadpath point self->NPC->lastSquadPoint = -1; // NPCInfo->aiFlags |= NPCAI_OFF_PATH; } if(bState != BS_POINT_COMBAT && NPCInfo->combatPoint != -1) { //level.combatPoints[NPCInfo->combatPoint].occupied = qfalse; //NPCInfo->combatPoint = -1; } //Here we need to see what the scripted stuff told us to do //Only process snapshot if independant and in combat mode- this would pick enemies and go after needed items // ProcessSnapshot(); //Ignore my needs if I'm under script control- this would set needs for items // CheckSelf(); //Back to normal? All decisions made? //FIXME: don't walk off ledges unless we can get to our goal faster that way, or that's our goal's surface //NPCPredict(); if ( NPC->enemy ) { if ( !NPC->enemy->inuse ) {//just in case bState doesn't catch this G_ClearEnemy( NPC ); } } if ( !NPC_CheckLookTarget( NPC ) ) { if ( NPC->enemy ) { if ( NPC->client->ps.weapon != WP_IMPERIAL_BLADE && NPC->client->ps.weapon != WP_KLINGON_BLADE ) {//looking right at enemy during melee looks odd NPC_SetLookTarget( NPC, NPC->enemy->s.number, 0 ); } } } if ( NPC->enemy ) { if(NPC->enemy->flags & FL_DONT_SHOOT) { ucmd.buttons &= ~BUTTON_ATTACK; } if(client->ps.weaponstate == WEAPON_IDLE) { client->ps.weaponstate = WEAPON_READY; } } else { if(client->ps.weaponstate == WEAPON_READY) { client->ps.weaponstate = WEAPON_IDLE; } } if(!(ucmd.buttons & BUTTON_ATTACK) && NPC->attackDebounceTime > level.time) {//We just shot but aren't still shooting, so hold the gun up for a while if(client->ps.weapon == WP_PHASER ) {//One-handed NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY1,SETANIM_FLAG_NORMAL); } else if(client->ps.weapon == WP_COMPRESSION_RIFLE) {//Sniper pose NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY2,SETANIM_FLAG_NORMAL); } /*//FIXME: What's the proper solution here? else {//heavy weapon NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); } */ } else if (!NPC->enemy && bState != BS_FORMATION)//HACK! { if(client->ps.weapon != WP_TRICORDER) { if((NPC->s.torsoAnim&~ANIM_TOGGLEBIT) == TORSO_WEAPONREADY1 || (NPC->s.torsoAnim&~ANIM_TOGGLEBIT) == TORSO_WEAPONREADY2) {//we look ready for action, using one of the first 2 weapon, let's rest our weapon on our shoulder NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONIDLE1,SETANIM_FLAG_NORMAL); } } } NPC_CheckAttackHold(); NPC_ApplyScriptFlags(); //cliff and wall avoidance NPC_AvoidWallsAndCliffs(); // run the bot through the server like it was a real client //=== Save the ucmd for the second no-think Pmove ============================ ucmd.serverTime = level.time - 50; NPCInfo->last_ucmd = ucmd; if ( !NPCInfo->attackHoldTime ) { NPCInfo->last_ucmd.buttons &= ~BUTTON_ATTACK;//so we don't fire twice in one think } //============================================================================ NPC_CheckAttackScript(); NPC_KeepCurrentFacing(); if ( !NPC->next_roff_time || NPC->next_roff_time < level.time ) {//If we were following a roff, we don't do normal pmoves. ClientThink( NPC->s.number, &ucmd ); } else { NPC_ApplyRoff(); } //Had to leave this in, some legacy code must still be using s.angles //Shouldn't interfere with interpolation of angles, should it? VectorCopy( client->ps.viewangles, NPC->currentAngles ); // end of thinking cleanup NPCInfo->touchedByPlayer = NULL; NPC_CheckPlayerAim(); NPC_CheckAllClear(); /*if( ucmd.forwardmove || ucmd.rightmove ) { int i, la = -1, ta = -1; for(i = 0; i < MAX_ANIMATIONS; i++) { if((NPC->client->ps.legsAnim&~ANIM_TOGGLEBIT) == i) { la = i; } if((NPC->client->ps.torsoAnim&~ANIM_TOGGLEBIT) == i) { ta = i; } if(la != -1 && ta != -1) { break; } } if(la != -1 && ta != -1) {//FIXME: should never play same frame twice or restart an anim before finishing it gi.Printf("LegsAnim: %s(%d) TorsoAnim: %s(%d)\n", animTable[la].name, NPC->renderInfo.legsFrame, animTable[ta].name, NPC->client->renderInfo.torsoFrame); } }*/ }
//---------------------------------------------------------- void emplaced_gun_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { vec3_t fwd1, fwd2; if ( self->health <= 0 ) { // can't use a dead gun. return; } if ( self->svFlags & SVF_INACTIVE ) { return; // can't use inactive gun } if ( !activator->client ) { return; // only a client can use it. } if ( self->activator ) { // someone is already in the gun. return; } if ( other && other->client && G_IsRidingVehicle( other ) ) {//can't use eweb when on a vehicle return; } if ( activator && activator->client && G_IsRidingVehicle( activator ) ) {//can't use eweb when on a vehicle return; } // We'll just let the designers duke this one out....I mean, as to whether they even want to limit such a thing. if ( self->spawnflags & EMPLACED_FACING ) { // Let's get some direction vectors for the users AngleVectors( activator->client->ps.viewangles, fwd1, NULL, NULL ); // Get the guns direction vector AngleVectors( self->pos1, fwd2, NULL, NULL ); float dot = DotProduct( fwd1, fwd2 ); // Must be reasonably facing the way the gun points ( 90 degrees or so ), otherwise we don't allow to use it. if ( dot < 0.0f ) { return; } } // don't allow using it again for half a second if ( self->delay + 500 < level.time ) { int oldWeapon = activator->s.weapon; if ( oldWeapon == WP_SABER ) { self->alt_fire = activator->client->ps.SaberActive(); } // swap the users weapon with the emplaced gun and add the ammo the gun has to the player activator->client->ps.weapon = self->s.weapon; Add_Ammo( activator, WP_EMPLACED_GUN, self->count ); activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN ); // Allow us to point from one to the other activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. self->activator = activator; G_RemoveWeaponModels( activator ); extern void ChangeWeapon( gentity_t *ent, int newWeapon ); if ( activator->NPC ) { ChangeWeapon( activator, WP_EMPLACED_GUN ); } else if ( activator->s.number == 0 ) { // we don't want for it to draw the weapon select stuff cg.weaponSelect = WP_EMPLACED_GUN; CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.95 ); } // Since we move the activator inside of the gun, we reserve a solid spot where they were standing in order to be able to get back out without being in solid if ( self->nextTrain ) {//you never know G_FreeEntity( self->nextTrain ); } self->nextTrain = G_Spawn(); //self->nextTrain->classname = "emp_placeholder"; self->nextTrain->contents = CONTENTS_MONSTERCLIP|CONTENTS_PLAYERCLIP;//hmm... playerclip too now that we're doing it for NPCs? G_SetOrigin( self->nextTrain, activator->client->ps.origin ); VectorCopy( activator->mins, self->nextTrain->mins ); VectorCopy( activator->maxs, self->nextTrain->maxs ); gi.linkentity( self->nextTrain ); //need to inflate the activator's mins/maxs since the gunsit anim puts them outside of their bbox VectorSet( activator->mins, -24, -24, -24 ); VectorSet( activator->maxs, 24, 24, 40 ); // Move the activator into the center of the gun. For NPC's the only way the can get out of the gun is to die. VectorCopy( self->s.origin, activator->client->ps.origin ); activator->client->ps.origin[2] += 30; // move them up so they aren't standing in the floor gi.linkentity( activator ); // the gun will track which weapon we used to have self->s.weapon = oldWeapon; // Lock the player activator->client->ps.eFlags |= EF_LOCKED_TO_WEAPON; activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. self->activator = activator; self->delay = level.time; // can't disconnect from the thing for half a second // Let the gun be considered an enemy //Ugh, so much AI code seems to assume enemies are clients, maybe this shouldn't be on, but it's too late in the game to change it now without knowing what side-effects this will have self->svFlags |= SVF_NONNPC_ENEMY; self->noDamageTeam = activator->client->playerTeam; // FIXME: don't do this, we'll try and actually put the player in this beast // move the player to the center of the gun // activator->contents = 0; // VectorCopy( self->currentOrigin, activator->client->ps.origin ); SetClientViewAngle( activator, self->pos1 ); //FIXME: should really wait a bit after spawn and get this just once? self->waypoint = NAV::GetNearestNode(self); #ifdef _DEBUG if ( self->waypoint == -1 ) { gi.Printf( S_COLOR_RED"ERROR: no waypoint for emplaced_gun %s at %s\n", self->targetname, vtos(self->currentOrigin) ); } #endif G_Sound( self, G_SoundIndex( "sound/weapons/emplaced/emplaced_mount.mp3" )); if ( !(self->spawnflags&EMPLACED_PLAYERUSE) || activator->s.number == 0 ) {//player-only usescript or any usescript // Run use script G_ActivateBehavior( self, BSET_USE ); } } }
//---------------------------------------------------------- void emplaced_gun_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc ) { vec3_t org; // turn off any firing animations it may have been doing self->s.frame = self->startFrame = self->endFrame = 0; self->svFlags &= ~SVF_ANIMATING; self->health = 0; // self->s.weapon = WP_EMPLACED_GUN; // we need to be able to switch back to the old weapon self->takedamage = qfalse; self->lastEnemy = attacker; // we defer explosion so the player has time to get out if ( self->e_DieFunc ) { self->e_ThinkFunc = thinkF_emplaced_blow; self->nextthink = level.time + 3000; // don't blow for a couple of seconds return; } if ( self->activator && self->activator->client ) { if ( self->activator->NPC ) { vec3_t right; // radius damage seems to throw them, but add an extra bit to throw them away from the weapon AngleVectors( self->currentAngles, NULL, right, NULL ); VectorMA( self->activator->client->ps.velocity, 140, right, self->activator->client->ps.velocity ); self->activator->client->ps.velocity[2] = -100; // kill them self->activator->health = 0; self->activator->client->ps.stats[STAT_HEALTH] = 0; } // kill the players emplaced ammo, cheesy way to keep the gun from firing self->activator->client->ps.ammo[weaponData[WP_EMPLACED_GUN].ammoIndex] = 0; } self->e_PainFunc = painF_NULL; self->e_ThinkFunc = thinkF_NULL; if ( self->target ) { G_UseTargets( self, attacker ); } G_RadiusDamage( self->currentOrigin, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN ); // when the gun is dead, add some ugliness to it. vec3_t ugly; ugly[YAW] = 4; ugly[PITCH] = self->lastAngles[PITCH] * 0.8f + crandom() * 6; ugly[ROLL] = crandom() * 7; gi.G2API_SetBoneAnglesIndex( &self->ghoul2[self->playerModel], self->lowerLumbarBone, ugly, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 ); VectorCopy( self->currentOrigin, org ); org[2] += 20; G_PlayEffect( "emplaced/explode", org ); // create some persistent smoke by using a dynamically created fx runner gentity_t *ent = G_Spawn(); if ( ent ) { ent->delay = 200; ent->random = 100; ent->fxID = G_EffectIndex( "emplaced/dead_smoke" ); ent->e_ThinkFunc = thinkF_fx_runner_think; ent->nextthink = level.time + 50; // move up above the gun origin VectorCopy( self->currentOrigin, org ); org[2] += 35; G_SetOrigin( ent, org ); VectorCopy( org, ent->s.origin ); VectorSet( ent->s.angles, -90, 0, 0 ); // up G_SetAngles( ent, ent->s.angles ); gi.linkentity( ent ); } G_ActivateBehavior( self, BSET_DEATH ); }
void eweb_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { if ( !eweb_can_be_used( self, other, activator ) ) { return; } int oldWeapon = activator->s.weapon; if ( oldWeapon == WP_SABER ) { self->alt_fire = activator->client->ps.SaberActive(); } // swap the users weapon with the emplaced gun and add the ammo the gun has to the player activator->client->ps.weapon = self->s.weapon; Add_Ammo( activator, WP_EMPLACED_GUN, self->count ); activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN ); // Allow us to point from one to the other activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. self->activator = activator; G_RemoveWeaponModels( activator ); extern void ChangeWeapon( gentity_t *ent, int newWeapon ); if ( activator->NPC ) { ChangeWeapon( activator, WP_EMPLACED_GUN ); } else if ( activator->s.number == 0 ) { // we don't want for it to draw the weapon select stuff cg.weaponSelect = WP_EMPLACED_GUN; CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.95 ); } VectorCopy( activator->currentOrigin, self->pos4 );//keep this around so we know when to make them play the strafe anim // the gun will track which weapon we used to have self->s.weapon = oldWeapon; // Lock the player activator->client->ps.eFlags |= EF_LOCKED_TO_WEAPON; activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. self->activator = activator; self->delay = level.time; // can't disconnect from the thing for half a second // Let the gun be considered an enemy //Ugh, so much AI code seems to assume enemies are clients, maybe this shouldn't be on, but it's too late in the game to change it now without knowing what side-effects this will have self->svFlags |= SVF_NONNPC_ENEMY; self->noDamageTeam = activator->client->playerTeam; //FIXME: should really wait a bit after spawn and get this just once? self->waypoint = NAV::GetNearestNode(self); #ifdef _DEBUG if ( self->waypoint == -1 ) { gi.Printf( S_COLOR_RED"ERROR: no waypoint for emplaced_gun %s at %s\n", self->targetname, vtos(self->currentOrigin) ); } #endif G_Sound( self, G_SoundIndex( "sound/weapons/eweb/eweb_mount.mp3" )); if ( !(self->spawnflags&EMPLACED_PLAYERUSE) || activator->s.number == 0 ) {//player-only usescript or any usescript // Run use script G_ActivateBehavior( self, BSET_USE ); } }
//---------------------------------------------------------- void eweb_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc ) { vec3_t org; // turn off any firing animations it may have been doing self->s.frame = self->startFrame = self->endFrame = 0; self->svFlags &= ~(SVF_ANIMATING|SVF_PLAYER_USABLE); self->health = 0; // self->s.weapon = WP_EMPLACED_GUN; // we need to be able to switch back to the old weapon self->takedamage = qfalse; self->lastEnemy = attacker; if ( self->activator && self->activator->client ) { if ( self->activator->NPC ) { vec3_t right; // radius damage seems to throw them, but add an extra bit to throw them away from the weapon AngleVectors( self->currentAngles, NULL, right, NULL ); VectorMA( self->activator->client->ps.velocity, 140, right, self->activator->client->ps.velocity ); self->activator->client->ps.velocity[2] = -100; // kill them self->activator->health = 0; self->activator->client->ps.stats[STAT_HEALTH] = 0; } // kill the players emplaced ammo, cheesy way to keep the gun from firing self->activator->client->ps.ammo[weaponData[WP_EMPLACED_GUN].ammoIndex] = 0; } self->e_PainFunc = painF_NULL; if ( self->target ) { G_UseTargets( self, attacker ); } G_RadiusDamage( self->currentOrigin, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN ); VectorCopy( self->currentOrigin, org ); org[2] += 20; G_PlayEffect( "emplaced/explode", org ); // Turn the top of the eweb off. #define TURN_OFF 0x00000100//G2SURFACEFLAG_NODESCENDANTS gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "eweb_damage", TURN_OFF ); // create some persistent smoke by using a dynamically created fx runner gentity_t *ent = G_Spawn(); if ( ent ) { ent->delay = 200; ent->random = 100; ent->fxID = G_EffectIndex( "emplaced/dead_smoke" ); ent->e_ThinkFunc = thinkF_fx_runner_think; ent->nextthink = level.time + 50; // move up above the gun origin VectorCopy( self->currentOrigin, org ); org[2] += 35; G_SetOrigin( ent, org ); VectorCopy( org, ent->s.origin ); VectorSet( ent->s.angles, -90, 0, 0 ); // up G_SetAngles( ent, ent->s.angles ); gi.linkentity( ent ); } G_ActivateBehavior( self, BSET_DEATH ); }
/* =================== G_SpawnGEntityFromSpawnVars Spawn an entity and fill in all of the level fields from level.spawnVars[], then call the class specfic spawn function =================== */ void G_SpawnGEntityFromSpawnVars( qboolean inSubBSP ) { int i; gentity_t *ent; char *s, *value, *gametypeName; static char *gametypeNames[] = {"ffa", "holocron", "jedimaster", "duel", "powerduel", "single", "team", "siege", "ctf", "cty"}; // get the next free entity ent = G_Spawn(); for ( i = 0 ; i < level.numSpawnVars ; i++ ) { G_ParseField( level.spawnVars[i][0], level.spawnVars[i][1], ent ); } // check for "notsingle" flag if ( g_gametype.integer == GT_SINGLE_PLAYER ) { G_SpawnInt( "notsingle", "0", &i ); if ( i ) { ADJUST_AREAPORTAL(); G_FreeEntity( ent ); return; } } // check for "notteam" flag (GT_FFA, GT_DUEL, GT_SINGLE_PLAYER) if ( g_gametype.integer >= GT_TEAM ) { G_SpawnInt( "notteam", "0", &i ); if ( i ) { ADJUST_AREAPORTAL(); G_FreeEntity( ent ); return; } } else { G_SpawnInt( "notfree", "0", &i ); if ( i ) { ADJUST_AREAPORTAL(); G_FreeEntity( ent ); return; } } if( G_SpawnString( "gametype", NULL, &value ) ) { if( g_gametype.integer >= GT_FFA && g_gametype.integer < GT_MAX_GAME_TYPE ) { gametypeName = gametypeNames[g_gametype.integer]; s = strstr( value, gametypeName ); if( !s ) { ADJUST_AREAPORTAL(); G_FreeEntity( ent ); return; } } } // move editor origin to pos VectorCopy( ent->s.origin, ent->s.pos.trBase ); VectorCopy( ent->s.origin, ent->r.currentOrigin ); // if we didn't get a classname, don't bother spawning anything if ( !G_CallSpawn( ent ) ) { G_FreeEntity( ent ); } //Tag on the ICARUS scripting information only to valid recipients if ( trap_ICARUS_ValidEnt( ent ) ) { trap_ICARUS_InitEnt( ent ); if ( ent->classname && ent->classname[0] ) { if ( Q_strncmp( "NPC_", ent->classname, 4 ) != 0 ) {//Not an NPC_spawner (rww - probably don't even care for MP, but whatever) G_ActivateBehavior( ent, BSET_SPAWN ); } } } }
/*QUAKED target_kill (.5 .5 .5) (-8 -8 -8) (8 8 8) Kills the activator. */ void target_kill_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { G_ActivateBehavior(self,BSET_USE); G_Damage ( activator, NULL, NULL, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); }
/*QUAKED target_print (1 0 0) (-8 -8 -8) (8 8 8) redteam blueteam private "message" text to print "wait" don't fire off again if triggered within this many milliseconds ago If "private", only the activator gets the message. If no checks, all clients get the message. */ void Use_Target_Print (gentity_t *ent, gentity_t *other, gentity_t *activator) { if (!ent || !ent->inuse) { Com_Printf("ERROR: Bad ent in Use_Target_Print"); return; } if (ent->wait) { if (ent->genericValue14 >= level.time) { return; } ent->genericValue14 = level.time + ent->wait; } #ifndef FINAL_BUILD if (!ent || !ent->inuse) { Com_Error(ERR_DROP, "Bad ent in Use_Target_Print"); } else if (!activator || !activator->inuse) { Com_Error(ERR_DROP, "Bad activator in Use_Target_Print"); } #endif G_ActivateBehavior(ent,BSET_USE); if ( ( ent->spawnflags & 4 ) ) {//private, to one client only if (!activator || !activator->inuse) { Com_Printf("ERROR: Bad activator in Use_Target_Print"); } if ( activator && activator->client ) {//make sure there's a valid client ent to send it to if (ent->message[0] == '@' && ent->message[1] != '@') { trap_SendServerCommand( activator-g_entities, va("cps \"%s\"", ent->message )); } else { trap_SendServerCommand( activator-g_entities, va("cp \"%s\"", ent->message )); } } //NOTE: change in functionality - if there *is* no valid client ent, it won't send it to anyone at all return; } if ( ent->spawnflags & 3 ) { if ( ent->spawnflags & 1 ) { if (ent->message[0] == '@' && ent->message[1] != '@') { G_TeamCommand( TEAM_RED, va("cps \"%s\"", ent->message) ); } else { G_TeamCommand( TEAM_RED, va("cp \"%s\"", ent->message) ); } } if ( ent->spawnflags & 2 ) { if (ent->message[0] == '@' && ent->message[1] != '@') { G_TeamCommand( TEAM_BLUE, va("cps \"%s\"", ent->message) ); } else { G_TeamCommand( TEAM_BLUE, va("cp \"%s\"", ent->message) ); } } return; } if (ent->message[0] == '@' && ent->message[1] != '@') { trap_SendServerCommand( -1, va("cps \"%s\"", ent->message )); } else { trap_SendServerCommand( -1, va("cp \"%s\"", ent->message )); } }
void target_random_use(gentity_t *self, gentity_t *other, gentity_t *activator) { int t_count = 0, pick; gentity_t *t = NULL; G_ActivateBehavior(self,BSET_USE); if(self->spawnflags & 1) { self->use = 0; } while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL ) { if (t != self) { t_count++; } } if(!t_count) { return; } if(t_count == 1) { G_UseTargets (self, activator); return; } //FIXME: need a seed pick = Q_irand(1, t_count); t_count = 0; while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL ) { if (t != self) { t_count++; } else { continue; } if (t == self) { } else if(t_count == pick) { if (t->use != NULL) // check can be omitted { GlobalUse(t, self, activator); return; } } if (!self->inuse) { Com_Printf("entity was removed while using targets\n"); return; } } }
/* =============== NPC_Pain =============== */ void NPC_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod, int hitLoc ) { team_t otherTeam = TEAM_FREE; int voiceEvent = -1; 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; other = G_CheckControlledTurretEnemy(self, other, qfalse); if (!other) { return; } //MCG: Ignore damage from your own team for now if ( other->client ) { otherTeam = other->client->playerTeam; // if ( otherTeam == TEAM_DISGUISE ) // { // otherTeam = TEAM_PLAYER; // } } if ( self->client->playerTeam && other->client && otherTeam == self->client->playerTeam && (!player->client->ps.viewEntity || other->s.number != player->client->ps.viewEntity)) {//hit by a teammate if ( other != self->enemy && self != other->enemy ) {//we weren't already enemies if ( self->enemy || other->enemy || (other->s.number&&other->s.number!=player->client->ps.viewEntity) /*|| (!other->s.number&&Q_irand( 0, 3 ))*/ ) {//if one of us actually has an enemy already, it's okay, just an accident OR wasn't hit by player or someone controlled by player OR player hit ally and didn't get 25% chance of getting mad (FIXME:accumulate anger+base on diff?) //FIXME: player should have to do a certain amount of damage to ally or hit them several times to make them mad //Still run pain and flee scripts if ( self->client && self->NPC ) {//Run any pain instructions if ( self->health <= (self->max_health/3) && G_ActivateBehavior(self, BSET_FLEE) ) { } else// if( VALIDSTRING( self->behaviorSet[BSET_PAIN] ) ) { G_ActivateBehavior(self, BSET_PAIN); } } if ( damage != -1 ) {//-1 == don't play pain anim //Set our proper pain animation if ( Q_irand( 0, 1 ) ) { NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, EV_FFWARN ); } else { NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc ); } } return; } else if ( self->NPC && !other->s.number )//should be assumed, but... {//dammit, stop that! if ( self->NPC->charmedTime > level.time ) {//mindtricked return; } else if ( self->NPC->ffireCount < 3+((2-g_spskill->integer)*2) ) {//not mad enough yet //Com_Printf( "chck: %d < %d\n", self->NPC->ffireCount, 3+((2-g_spskill->integer)*2) ); if ( damage != -1 ) {//-1 == don't play pain anim //Set our proper pain animation if ( Q_irand( 0, 1 ) ) { NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, EV_FFWARN ); } else { NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc ); } } return; } else if ( G_ActivateBehavior( self, BSET_FFIRE ) ) {//we have a specific script to run, so do that instead return; } else {//okay, we're going to turn on our ally, we need to set and lock our enemy and put ourselves in a bstate that lets us attack him (and clear any flags that would stop us) self->NPC->blockedSpeechDebounceTime = 0; voiceEvent = EV_FFTURN; self->NPC->behaviorState = self->NPC->tempBehavior = self->NPC->defaultBehavior = BS_DEFAULT; other->flags &= ~FL_NOTARGET; self->svFlags &= ~(SVF_IGNORE_ENEMIES|SVF_ICARUS_FREEZE|SVF_NO_COMBAT_SOUNDS); G_SetEnemy( self, other ); self->svFlags |= SVF_LOCKEDENEMY; self->NPC->scriptFlags &= ~(SCF_DONT_FIRE|SCF_CROUCHED|SCF_WALKING|SCF_NO_COMBAT_TALK|SCF_FORCED_MARCH); self->NPC->scriptFlags |= (SCF_CHASE_ENEMIES|SCF_NO_MIND_TRICK); //NOTE: we also stop ICARUS altogether stop_icarus = qtrue; if ( !killPlayerTimer ) { killPlayerTimer = level.time + 10000; } } } } } SaveNPCGlobals(); SetNPCGlobals( self ); //Do extra bits if ( NPCInfo->ignorePain == qfalse ) { NPCInfo->confusionTime = 0;//clear any charm or confusion, regardless if ( NPC->ghoul2.size() && NPC->headBolt != -1 ) { G_StopEffect("force/confusion", NPC->playerModel, NPC->headBolt, NPC->s.number ); } if ( damage != -1 ) {//-1 == don't play pain anim //Set our proper pain animation NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, voiceEvent ); } //Check to take a new enemy if ( NPC->enemy != other && NPC != other ) {//not already mad at them //if it's an eweb or emplaced gun, get mad at the owner, not the gun NPC_CheckAttacker( other, mod ); } } //Attempt to run any pain instructions if ( self->client && self->NPC ) { //FIXME: This needs better heuristics perhaps if(self->health <= (self->max_health/3) && 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); } if (self->client && self->client->NPC_class==CLASS_BOBAFETT) { Boba_Pain( self, inflictor, damage, mod); } RestoreNPCGlobals(); }
void target_deactivate_use(gentity_t *self, gentity_t *other, gentity_t *activator) { G_ActivateBehavior(self,BSET_USE); G_SetActiveState(self->target, ACT_INACTIVE); }
void func_usable_use( gentity_t *self, gentity_t *other, gentity_t *activator ) {//Toggle on and off if ( other == activator ) {//directly used by use button trace if ( (self->spawnflags&32) ) {//only usable by NPCs if ( activator->NPC == NULL ) {//Not an NPC return; } } } G_ActivateBehavior( self, BSET_USE ); if ( self->s.eFlags & EF_SHADER_ANIM ) {//animate shader when used self->s.frame++;//inc frame if ( self->s.frame > self->endFrame ) {//wrap around self->s.frame = 0; } if ( self->target && self->target[0] ) { G_UseTargets( self, activator ); } } else if ( self->spawnflags & 8 ) {//ALWAYS_ON //Remove the ability to use the entity directly self->svFlags &= ~SVF_PLAYER_USABLE; //also remove ability to call any use func at all! self->e_UseFunc = useF_NULL; if(self->target && self->target[0]) { G_UseTargets(self, activator); } if ( self->wait ) { self->e_ThinkFunc = thinkF_func_usable_think; self->nextthink = level.time + ( self->wait * 1000 ); } return; } else if ( !self->count ) {//become solid again self->count = 1; self->activator = activator; func_wait_return_solid( self ); } else { //NOTE: MUST do this BEFORE clearing contents, or you may not open the area portal!!! if ( !(self->spawnflags&1) ) {//START_OFF doesn't effect area portals gi.AdjustAreaPortalState( self, qtrue ); } self->s.solid = 0; self->contents = 0; self->clipmask = 0; self->svFlags |= SVF_NOCLIENT; self->s.eFlags |= EF_NODRAW; self->count = 0; if(self->target && self->target[0]) { G_UseTargets(self, activator); } self->e_ThinkFunc = thinkF_NULL; self->nextthink = -1; } }
void target_play_music_use(gentity_t *self, gentity_t *other, gentity_t *activator) { G_ActivateBehavior(self,BSET_USE); trap_SetConfigstring( CS_MUSIC, self->message ); }
/*QUAKED target_score (1 0 0) (-8 -8 -8) (8 8 8) "count" number of points to add, default 1 The activator is given this many points. */ void Use_Target_Score (gentity_t *ent, gentity_t *other, gentity_t *activator) { G_ActivateBehavior(ent,BSET_USE); AddScore( activator, ent->count ); }
void misc_model_breakable_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc ) { int numChunks; float size = 0, scale; vec3_t dir, up, dis; //NOTE: Stop any scripts that are currently running (FLUSH)... ? //Turn off animation self->s.frame = self->startFrame = self->endFrame = 0; self->svFlags &= ~SVF_ANIMATING; self->health = 0; //Throw some chunks AngleVectors( self->s.apos.trBase, dir, NULL, NULL ); VectorNormalize( dir ); numChunks = random() * 6 + 20; VectorSubtract( self->absmax, self->absmin, dis ); // This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted. // Volume is length * width * height...then break that volume down based on how many chunks we have scale = sqrt( sqrt( dis[0] * dis[1] * dis[2] )) * 1.75f; if ( scale > 48 ) { size = 2; } else if ( scale > 24 ) { size = 1; } scale = scale / numChunks; if ( self->radius > 0.0f ) { // designer wants to scale number of chunks, helpful because the above scale code is far from perfect // I do this after the scale calculation because it seems that the chunk size generally seems to be very close, it's just the number of chunks is a bit weak numChunks *= self->radius; } VectorAdd( self->absmax, self->absmin, dis ); VectorScale( dis, 0.5f, dis ); CG_Chunks( self->s.number, dis, dir, self->absmin, self->absmax, 300, numChunks, self->material, self->s.modelindex3, scale ); self->e_PainFunc = painF_NULL; self->e_DieFunc = dieF_NULL; // self->e_UseFunc = useF_NULL; self->takedamage = qfalse; if ( !(self->spawnflags & 4) ) {//We don't want to stay solid self->s.solid = 0; self->contents = 0; self->clipmask = 0; gi.linkentity(self); } VectorSet(up, 0, 0, 1); if(self->target) { G_UseTargets(self, attacker); } if(inflictor->client) { VectorSubtract( self->currentOrigin, inflictor->currentOrigin, dir ); VectorNormalize( dir ); } else { VectorCopy(up, dir); } if ( !(self->spawnflags & 2048) ) // NO_EXPLOSION { // Ok, we are allowed to explode, so do it now! if(self->splashDamage > 0 && self->splashRadius > 0) {//explode vec3_t org; AddSightEvent( attacker, self->currentOrigin, 256, AEL_DISCOVERED, 100 ); AddSoundEvent( attacker, self->currentOrigin, 128, AEL_DISCOVERED ); //FIXME: specify type of explosion? (barrel, electrical, etc.) Also, maybe just use the explosion effect below since it's // a bit better? // up the origin a little for the damage check, because several models have their origin on the ground, so they don't alwasy do damage, not the optimal solution... VectorCopy( self->currentOrigin, org ); if ( self->mins[2] > -4 ) {//origin is going to be below it or very very low in the model //center the origin org[2] = self->currentOrigin[2] + self->mins[2] + (self->maxs[2] - self->mins[2])/2.0f; } G_RadiusDamage( org, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN ); if ( self->model && Q_stricmp( "models/map_objects/ships/tie_fighter.md3", self->model ) == 0 ) {//TEMP HACK for Tie Fighters- they're HUGE G_PlayEffect( "fighter_explosion2", self->currentOrigin ); G_Sound( self, G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" ) ); } else { CG_MiscModelExplosion( self->absmin, self->absmax, size, self->material ); G_Sound( self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav") ); } } else {//just break AddSightEvent( attacker, self->currentOrigin, 128, AEL_DISCOVERED ); AddSoundEvent( attacker, self->currentOrigin, 64, AEL_SUSPICIOUS ); // This is the default explosion CG_MiscModelExplosion( self->absmin, self->absmax, size, self->material ); G_Sound(self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav")); } } self->e_ThinkFunc = thinkF_NULL; self->nextthink = -1; if(self->s.modelindex2 != -1 && !(self->spawnflags & 8)) {//FIXME: modelindex doesn't get set to -1 if the damage model doesn't exist self->svFlags |= SVF_BROKEN; self->s.modelindex = self->s.modelindex2; G_ActivateBehavior( self, BSET_DEATH ); } else { G_FreeEntity( self ); } }
/* =============== 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(); }