// Copyright (C) 1999-2000 Id Software, Inc. // #include "g_local.h" /* Items are any object that a player can touch to gain some effect. Pickup will return the number of seconds until they should respawn. all items should pop when dropped in lava or slime Respawnable items don't actually go away when picked up, they are just made invisible and untouchable. This allows them to ride movers and respawn apropriately. */ #define RESPAWN_ARMOR 25 #define RESPAWN_HEALTH 35 #define RESPAWN_AMMO 40 #define RESPAWN_HOLDABLE 60 #define RESPAWN_MEGAHEALTH 35//120 #define RESPAWN_POWERUP 120 extern gentity_t *droppedRedFlag; extern gentity_t *droppedBlueFlag; //====================================================================== #define MAX_MEDPACK_HEAL_AMOUNT 100 #define MAX_SENTRY_DISTANCE 256 #define SHIELD_HEALTH 250 #define SHIELD_HEALTH_DEC 10 // 25 seconds #define MAX_SHIELD_HEIGHT 254 #define MAX_SHIELD_HALFWIDTH 255 #define SHIELD_HALFTHICKNESS 4 #define SHIELD_PLACEDIST 64 static qhandle_t shieldAttachSound=0; static qhandle_t shieldActivateSound=0; static qhandle_t shieldDeactivateSound=0; static qhandle_t shieldDamageSound=0; void ShieldRemove(gentity_t *self) { self->think = G_FreeEntity; self->nextthink = level.time + 100; // Play raising sound... G_AddEvent(self, EV_GENERAL_SOUND, shieldActivateSound); return; } // Count down the health of the shield. void ShieldThink(gentity_t *self) { self->s.trickedentindex = 0; self->health -= SHIELD_HEALTH_DEC; self->nextthink = level.time + 1000; if (self->health <= 0) { ShieldRemove(self); } return; } // The shield was damaged to below zero health. void ShieldDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod) { // Play damaging sound... G_AddEvent(self, EV_GENERAL_SOUND, shieldDamageSound); ShieldRemove(self); } // The shield had damage done to it. Make it flicker. void ShieldPain(gentity_t *self, gentity_t *attacker, int damage) { // Set the itemplaceholder flag to indicate the the shield drawing that the shield pain should be drawn. self->think = ShieldThink; self->nextthink = level.time + 400; // Play damaging sound... G_AddEvent(self, EV_GENERAL_SOUND, shieldDamageSound); self->s.trickedentindex = 1; return; } // Try to turn the shield back on after a delay. void ShieldGoSolid(gentity_t *self) { trace_t tr; // see if we're valid self->health--; if (self->health <= 0) { ShieldRemove(self); return; } trap_Trace (&tr, self->r.currentOrigin, self->r.mins, self->r.maxs, self->r.currentOrigin, self->s.number, CONTENTS_BODY ); if(tr.startsolid) { // gah, we can't activate yet self->nextthink = level.time + 200; self->think = ShieldGoSolid; trap_LinkEntity(self); } else { // get hard... huh-huh... self->s.eFlags &= ~EF_NODRAW; self->r.contents = CONTENTS_SOLID; self->nextthink = level.time + 1000; self->think = ShieldThink; self->takedamage = qtrue; trap_LinkEntity(self); // Play raising sound... G_AddEvent(self, EV_GENERAL_SOUND, shieldActivateSound); } return; } // Turn the shield off to allow a friend to pass through. void ShieldGoNotSolid(gentity_t *self) { // make the shield non-solid very briefly self->r.contents = 0; self->s.eFlags |= EF_NODRAW; // nextthink needs to have a large enough interval to avoid excess accumulation of Activate messages self->nextthink = level.time + 200; self->think = ShieldGoSolid; self->takedamage = qfalse; trap_LinkEntity(self); // Play raising sound... G_AddEvent(self, EV_GENERAL_SOUND, shieldDeactivateSound); } // Somebody (a player) has touched the shield. See if it is a "friend". void ShieldTouch(gentity_t *self, gentity_t *other, trace_t *trace) { if (g_gametype.integer >= GT_TEAM) { // let teammates through // compare the parent's team to the "other's" team if (self->parent && ( self->parent->client) && (other->client)) { if (OnSameTeam(self->parent, other)) { ShieldGoNotSolid(self); } } } else {//let the person who dropped the shield through if (self->parent && self->parent->s.number == other->s.number) { ShieldGoNotSolid(self); } } } // After a short delay, create the shield by expanding in all directions. void CreateShield(gentity_t *ent) { trace_t tr; vec3_t mins, maxs, end, posTraceEnd, negTraceEnd, start; int height, posWidth, negWidth, halfWidth = 0; qboolean xaxis; int paramData = 0; static int shieldID; // trace upward to find height of shield VectorCopy(ent->r.currentOrigin, end); end[2] += MAX_SHIELD_HEIGHT; trap_Trace (&tr, ent->r.currentOrigin, NULL, NULL, end, ent->s.number, MASK_SHOT ); height = (int)(MAX_SHIELD_HEIGHT * tr.fraction); // use angles to find the proper axis along which to align the shield VectorSet(mins, -SHIELD_HALFTHICKNESS, -SHIELD_HALFTHICKNESS, 0); VectorSet(maxs, SHIELD_HALFTHICKNESS, SHIELD_HALFTHICKNESS, height); VectorCopy(ent->r.currentOrigin, posTraceEnd); VectorCopy(ent->r.currentOrigin, negTraceEnd); if ((int)(ent->s.angles[YAW]) == 0) // shield runs along y-axis { posTraceEnd[1]+=MAX_SHIELD_HALFWIDTH; negTraceEnd[1]-=MAX_SHIELD_HALFWIDTH; xaxis = qfalse; } else // shield runs along x-axis { posTraceEnd[0]+=MAX_SHIELD_HALFWIDTH; negTraceEnd[0]-=MAX_SHIELD_HALFWIDTH; xaxis = qtrue; } // trace horizontally to find extend of shield // positive trace VectorCopy(ent->r.currentOrigin, start); start[2] += (height>>1); trap_Trace (&tr, start, 0, 0, posTraceEnd, ent->s.number, MASK_SHOT ); posWidth = MAX_SHIELD_HALFWIDTH * tr.fraction; // negative trace trap_Trace (&tr, start, 0, 0, negTraceEnd, ent->s.number, MASK_SHOT ); negWidth = MAX_SHIELD_HALFWIDTH * tr.fraction; // kef -- monkey with dimensions and place origin in center halfWidth = (posWidth + negWidth)>>1; if (xaxis) { ent->r.currentOrigin[0] = ent->r.currentOrigin[0] - negWidth + halfWidth; } else { ent->r.currentOrigin[1] = ent->r.currentOrigin[1] - negWidth + halfWidth; } ent->r.currentOrigin[2] += (height>>1); // set entity's mins and maxs to new values, make it solid, and link it if (xaxis) { VectorSet(ent->r.mins, -halfWidth, -SHIELD_HALFTHICKNESS, -(height>>1)); VectorSet(ent->r.maxs, halfWidth, SHIELD_HALFTHICKNESS, height>>1); } else { VectorSet(ent->r.mins, -SHIELD_HALFTHICKNESS, -halfWidth, -(height>>1)); VectorSet(ent->r.maxs, SHIELD_HALFTHICKNESS, halfWidth, height); } ent->clipmask = MASK_SHOT; // Information for shield rendering. // xaxis - 1 bit // height - 0-254 8 bits // posWidth - 0-255 8 bits // negWidth - 0 - 255 8 bits paramData = (xaxis << 24) | (height << 16) | (posWidth << 8) | (negWidth); ent->s.time2 = paramData; ent->health = ceil(SHIELD_HEALTH*1); ent->s.time = ent->health;//??? ent->pain = ShieldPain; ent->die = ShieldDie; ent->touch = ShieldTouch; // see if we're valid trap_Trace (&tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, ent->s.number, CONTENTS_BODY ); if (tr.startsolid) { // Something in the way! // make the shield non-solid very briefly ent->r.contents = 0; ent->s.eFlags |= EF_NODRAW; // nextthink needs to have a large enough interval to avoid excess accumulation of Activate messages ent->nextthink = level.time + 200; ent->think = ShieldGoSolid; ent->takedamage = qfalse; trap_LinkEntity(ent); } else { // Get solid. ent->r.contents = CONTENTS_PLAYERCLIP|CONTENTS_SHOTCLIP;//CONTENTS_SOLID; ent->nextthink = level.time; ent->think = ShieldThink; ent->takedamage = qtrue; trap_LinkEntity(ent); // Play raising sound... G_AddEvent(ent, EV_GENERAL_SOUND, shieldActivateSound); } ShieldGoSolid(ent); return; } qboolean PlaceShield(gentity_t *playerent) { static const gitem_t *shieldItem = NULL; gentity_t *shield = NULL; trace_t tr; vec3_t fwd, pos, dest, mins = {-4,-4, 0}, maxs = {4,4,4}; if (shieldAttachSound==0) { shieldAttachSound = G_SoundIndex("sound/weapons/detpack/stick.wav"); shieldActivateSound = G_SoundIndex("sound/movers/doors/forcefield_on.wav"); shieldDeactivateSound = G_SoundIndex("sound/movers/doors/forcefield_off.wav"); shieldDamageSound = G_SoundIndex("sound/effects/bumpfield.wav"); shieldItem = BG_FindItemForHoldable(HI_SHIELD); } // can we place this in front of us? AngleVectors (playerent->client->ps.viewangles, fwd, NULL, NULL); fwd[2] = 0; VectorMA(playerent->client->ps.origin, SHIELD_PLACEDIST, fwd, dest); trap_Trace (&tr, playerent->client->ps.origin, mins, maxs, dest, playerent->s.number, MASK_SHOT ); if (tr.fraction > 0.9) {//room in front VectorCopy(tr.endpos, pos); // drop to floor VectorSet( dest, pos[0], pos[1], pos[2] - 4096 ); trap_Trace( &tr, pos, mins, maxs, dest, playerent->s.number, MASK_SOLID ); if ( !tr.startsolid && !tr.allsolid ) { // got enough room so place the portable shield shield = G_Spawn(); // Figure out what direction the shield is facing. if (fabs(fwd[0]) > fabs(fwd[1])) { // shield is north/south, facing east. shield->s.angles[YAW] = 0; } else { // shield is along the east/west axis, facing north shield->s.angles[YAW] = 90; } shield->think = CreateShield; shield->nextthink = level.time + 500; // power up after .5 seconds shield->parent = playerent; // Set team number. shield->s.otherEntityNum2 = playerent->client->sess.sessionTeam; shield->s.eType = ET_SPECIAL; shield->s.modelindex = HI_SHIELD; // this'll be used in CG_Useable() for rendering. shield->classname = shieldItem->classname; shield->r.contents = CONTENTS_TRIGGER; shield->touch = 0; // using an item causes it to respawn shield->use = 0; //Use_Item; // allow to ride movers shield->s.groundEntityNum = tr.entityNum; G_SetOrigin( shield, tr.endpos ); shield->s.eFlags &= ~EF_NODRAW; shield->r.svFlags &= ~SVF_NOCLIENT; trap_LinkEntity (shield); shield->s.owner = playerent->s.number; shield->s.shouldtarget = qtrue; if (g_gametype.integer >= GT_TEAM) { shield->s.teamowner = playerent->client->sess.sessionTeam; } else { shield->s.teamowner = 16; } // Play placing sound... G_AddEvent(shield, EV_GENERAL_SOUND, shieldAttachSound); return qtrue; } } // no room return qfalse; } void ItemUse_Binoculars(gentity_t *ent) { if (!ent || !ent->client) { return; } if (ent->client->ps.weaponstate != WEAPON_READY) { //So we can't fool it and reactivate while switching to the saber or something. return; } if (ent->client->ps.weapon == WP_SABER) { //No. return; } if (ent->client->ps.zoomMode == 0) // not zoomed or currently zoomed with the disruptor { ent->client->ps.zoomMode = 2; ent->client->ps.zoomLocked = qfalse; ent->client->ps.zoomFov = 40.0f; } else if (ent->client->ps.zoomMode == 2) { ent->client->ps.zoomMode = 0; ent->client->ps.zoomTime = level.time; } } void ItemUse_Shield(gentity_t *ent) { PlaceShield(ent); } //-------------------------- // PERSONAL ASSAULT SENTRY //-------------------------- #define PAS_DAMAGE 2 void SentryTouch(gentity_t *ent, gentity_t *other, trace_t *trace) { return; } //---------------------------------------------------------------- void pas_fire( gentity_t *ent ) //---------------------------------------------------------------- { vec3_t fwd, myOrg, enOrg; VectorCopy(ent->r.currentOrigin, myOrg); myOrg[2] += 24; VectorCopy(ent->enemy->client->ps.origin, enOrg); enOrg[2] += 24; VectorSubtract(enOrg, myOrg, fwd); VectorNormalize(fwd); myOrg[0] += fwd[0]*16; myOrg[1] += fwd[1]*16; myOrg[2] += fwd[2]*16; WP_FireTurretMissile(&g_entities[ent->boltpoint3], myOrg, fwd, qfalse, 10, 2300, MOD_SENTRY, ent ); G_RunObject(ent); } void G_SetEnemy( gentity_t *self, gentity_t *enemy ) { if (!enemy->client) { return; } self->enemy = enemy; } #define TURRET_RADIUS 800 //----------------------------------------------------- static qboolean pas_find_enemies( gentity_t *self ) //----------------------------------------------------- { qboolean found = qfalse; int count, i; float bestDist = TURRET_RADIUS*TURRET_RADIUS; float enemyDist; vec3_t enemyDir, org, org2; gentity_t *entity_list[MAX_GENTITIES], *target; trace_t tr; 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 ) { G_Sound(self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/ping.wav" )); self->painDebounceTime = level.time + 1000; } } VectorCopy(self->s.pos.trBase, org2); count = G_RadiusList( org2, TURRET_RADIUS, self, qtrue, entity_list ); for ( i = 0; i < count; i++ ) { target = entity_list[i]; if ( !target->client ) { continue; } if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET )) { continue; } if ( self->noDamageTeam && target->client->sess.sessionTeam == self->noDamageTeam ) { continue; } if (self->boltpoint3 == target->s.number) { continue; } if ( !trap_InPVS( org2, target->r.currentOrigin )) { continue; } if ( target->client ) { VectorCopy( target->client->ps.origin, org ); } else { VectorCopy( target->r.currentOrigin, org ); } 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 )// all things equal, keep current { if ( self->attackDebounceTime + 100 < 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 + 900 + random() * 200; } G_SetEnemy( self, target ); bestDist = enemyDist; found = qtrue; } } } return found; } //--------------------------------- void pas_adjust_enemy( gentity_t *ent ) //--------------------------------- { trace_t tr; qboolean keep = qtrue; if ( ent->enemy->health <= 0 ) { keep = qfalse; } else { vec3_t org, org2; VectorCopy(ent->s.pos.trBase, org2); if ( ent->enemy->client ) { VectorCopy( ent->enemy->client->ps.origin, org ); org[2] -= 15; } else { VectorCopy( ent->enemy->r.currentOrigin, org ); } trap_Trace( &tr, org2, NULL, NULL, org, ent->s.number, MASK_SHOT ); if ( tr.allsolid || tr.startsolid || tr.fraction < 0.9f || tr.entityNum == ent->s.number ) { if (tr.entityNum != ent->enemy->s.number) { // trace failed keep = qfalse; } } } if ( keep ) { //ent->bounceCount = level.time + 500 + random() * 150; } else if ( ent->bounceCount < level.time && ent->enemy ) // don't ping pong on and off { ent->enemy = NULL; // shut-down sound G_Sound( ent, CHAN_BODY, G_SoundIndex( "sound/chars/turret/shutdown.wav" )); ent->bounceCount = level.time + 500 + random() * 150; // make turret play ping sound for 5 seconds ent->aimDebounceTime = level.time + 5000; } } #define TURRET_DEATH_DELAY 2000 void turret_die(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod); void sentryExpire(gentity_t *self) { turret_die(self, self, self, 1000, MOD_UNKNOWN); } //--------------------------------- void pas_think( gentity_t *ent ) //--------------------------------- { qboolean moved; float diffYaw, diffPitch; vec3_t enemyDir, org; vec3_t frontAngles, backAngles; vec3_t desiredAngles; if (!g_entities[ent->boltpoint3].inuse || !g_entities[ent->boltpoint3].client || g_entities[ent->boltpoint3].client->sess.sessionTeam != ent->boltpoint2) { ent->think = G_FreeEntity; ent->nextthink = level.time; return; } G_RunObject(ent); if ( !ent->damage ) { ent->damage = 1; ent->nextthink = level.time + FRAMETIME; return; } ent->nextthink = level.time + FRAMETIME; if ( ent->enemy ) { // make sure that the enemy is still valid pas_adjust_enemy( ent ); } if (ent->enemy) { if (!ent->enemy->client) { ent->enemy = NULL; } else if (ent->enemy->s.number == ent->s.number) { ent->enemy = NULL; } else if (ent->enemy->health < 1) { ent->enemy = NULL; } } if ( !ent->enemy ) { pas_find_enemies( ent ); } if (ent->enemy) { ent->s.bolt2 = ent->enemy->s.number; } else { ent->s.bolt2 = ENTITYNUM_NONE; } moved = qfalse; diffYaw = 0.0f; diffPitch = 0.0f; ent->speed = AngleNormalize360( ent->speed ); ent->random = AngleNormalize360( ent->random ); if ( ent->enemy ) { // ...then we'll calculate what new aim adjustments we should attempt to make this frame // Aim at enemy if ( ent->enemy->client ) { VectorCopy( ent->enemy->client->ps.origin, org ); } else { VectorCopy( ent->enemy->r.currentOrigin, org ); } VectorSubtract( org, ent->r.currentOrigin, enemyDir ); vectoangles( enemyDir, desiredAngles ); diffYaw = AngleSubtract( ent->speed, desiredAngles[YAW] ); diffPitch = AngleSubtract( ent->random, desiredAngles[PITCH] ); } else { // no enemy, so make us slowly sweep back and forth as if searching for a new one diffYaw = sin( level.time * 0.0001f + ent->count ) * 2.0f; } if ( fabs(diffYaw) > 0.25f ) { moved = qtrue; if ( fabs(diffYaw) > 10.0f ) { // cap max speed ent->speed += (diffYaw > 0.0f) ? -10.0f : 10.0f; } else { // small enough ent->speed -= diffYaw; } } if ( fabs(diffPitch) > 0.25f ) { moved = qtrue; if ( fabs(diffPitch) > 4.0f ) { // cap max speed ent->random += (diffPitch > 0.0f) ? -4.0f : 4.0f; } else { // small enough ent->random -= diffPitch; } } // the bone axes are messed up, so hence some dumbness here VectorSet( frontAngles, -ent->random, 0.0f, 0.0f ); VectorSet( backAngles, 0.0f, 0.0f, ent->speed ); if ( moved ) { //ent->s.loopSound = G_SoundIndex( "sound/chars/turret/move.wav" ); } else { ent->s.loopSound = 0; } if ( ent->enemy && ent->attackDebounceTime < level.time ) { ent->count--; if ( ent->count ) { pas_fire( ent ); ent->s.fireflag = 1; ent->attackDebounceTime = level.time + 100; } else { //ent->nextthink = 0; G_Sound( ent, CHAN_BODY, G_SoundIndex( "sound/chars/turret/shutdown.wav" )); ent->s.bolt2 = ENTITYNUM_NONE; ent->s.fireflag = 2; ent->think = sentryExpire; ent->nextthink = level.time + TURRET_DEATH_DELAY; } } else { ent->s.fireflag = 0; } } //------------------------------------------------------------------------------------------------------------ void turret_die(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod) //------------------------------------------------------------------------------------------------------------ { // Turn off the thinking of the base & use it's targets self->think = 0;//NULL; self->use = 0;//NULL; if ( self->target ) { G_UseTargets( self, attacker ); } if (!g_entities[self->boltpoint3].inuse || !g_entities[self->boltpoint3].client) { G_FreeEntity(self); return; } // clear my data self->die = 0;//NULL; self->takedamage = qfalse; self->health = 0; // hack the effect angle so that explode death can orient the effect properly VectorSet( self->s.angles, 0, 0, 1 ); G_PlayEffect(EFFECT_EXPLOSION, self->s.pos.trBase, self->s.angles); G_RadiusDamage(self->s.pos.trBase, &g_entities[self->boltpoint3], 30, 256, self, MOD_UNKNOWN); g_entities[self->boltpoint3].client->ps.fd.sentryDeployed = qfalse; //ExplodeDeath( self ); G_FreeEntity( self ); } #define TURRET_AMMO_COUNT 40 //--------------------------------- void SP_PAS( gentity_t *base ) //--------------------------------- { if ( base->count == 0 ) { // give ammo base->count = TURRET_AMMO_COUNT; } base->s.bolt1 = 1; //This is a sort of hack to indicate that this model needs special turret things done to it base->s.bolt2 = ENTITYNUM_NONE; //store our current enemy index base->damage = 0; // start animation flag VectorSet( base->r.mins, -8, -8, 0 ); VectorSet( base->r.maxs, 8, 8, 24 ); G_RunObject(base); base->think = pas_think; base->nextthink = level.time + FRAMETIME; if ( !base->health ) { base->health = 50; } base->takedamage = qtrue; base->die = turret_die; G_Sound( base, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" )); } //------------------------------------------------------------------------ void ItemUse_Sentry( gentity_t *ent ) //------------------------------------------------------------------------ { vec3_t fwd, fwdorg; vec3_t yawonly; vec3_t mins, maxs; gentity_t *sentry; if (!ent || !ent->client) { return; } VectorSet( mins, -8, -8, 0 ); VectorSet( maxs, 8, 8, 24 ); yawonly[ROLL] = 0; yawonly[PITCH] = 0; yawonly[YAW] = ent->client->ps.viewangles[YAW]; AngleVectors(yawonly, fwd, NULL, NULL); fwdorg[0] = ent->client->ps.origin[0] + fwd[0]*64; fwdorg[1] = ent->client->ps.origin[1] + fwd[1]*64; fwdorg[2] = ent->client->ps.origin[2] + fwd[2]*64; sentry = G_Spawn(); sentry->classname = "sentryGun"; sentry->s.modelindex = G_ModelIndex("models/items/psgun.glm"); //replace ASAP sentry->s.g2radius = 30.0f; sentry->s.modelGhoul2 = 1; G_SetOrigin(sentry, fwdorg); sentry->parent = ent; sentry->r.contents = CONTENTS_SOLID; sentry->s.solid = 2; sentry->clipmask = MASK_SOLID; VectorCopy(mins, sentry->r.mins); VectorCopy(maxs, sentry->r.maxs); sentry->boltpoint3 = ent->s.number; sentry->boltpoint2 = ent->client->sess.sessionTeam; //so we can remove ourself if our owner changes teams sentry->r.absmin[0] = sentry->s.pos.trBase[0] + sentry->r.mins[0]; sentry->r.absmin[1] = sentry->s.pos.trBase[1] + sentry->r.mins[1]; sentry->r.absmin[2] = sentry->s.pos.trBase[2] + sentry->r.mins[2]; sentry->r.absmax[0] = sentry->s.pos.trBase[0] + sentry->r.maxs[0]; sentry->r.absmax[1] = sentry->s.pos.trBase[1] + sentry->r.maxs[1]; sentry->r.absmax[2] = sentry->s.pos.trBase[2] + sentry->r.maxs[2]; sentry->s.eType = ET_GENERAL; sentry->s.pos.trType = TR_GRAVITY;//STATIONARY; sentry->s.pos.trTime = level.time; sentry->touch = SentryTouch; sentry->nextthink = level.time; sentry->boltpoint4 = ENTITYNUM_NONE; //boltpoint4 used as enemy index sentry->bolt_Head = 1000; sentry->noDamageTeam = ent->client->sess.sessionTeam; ent->client->ps.fd.sentryDeployed = qtrue; trap_LinkEntity(sentry); sentry->s.owner = ent->s.number; sentry->s.shouldtarget = qtrue; if (g_gametype.integer >= GT_TEAM) { sentry->s.teamowner = ent->client->sess.sessionTeam; } else { sentry->s.teamowner = 16; } SP_PAS( sentry ); } void ItemUse_Seeker(gentity_t *ent) { ent->client->ps.eFlags |= EF_SEEKERDRONE; ent->client->ps.droneExistTime = level.time + 30000; ent->client->ps.droneFireTime = level.time + 1500; } void ItemUse_MedPack(gentity_t *ent) { if (!ent || !ent->client) { return; } if (ent->health >= ent->client->ps.stats[STAT_MAX_HEALTH]) { return; } ent->health += MAX_MEDPACK_HEAL_AMOUNT; if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH]) { ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; } } int Pickup_Powerup( gentity_t *ent, gentity_t *other ) { int quantity; int i; gclient_t *client; if ( !other->client->ps.powerups[ent->item->giTag] ) { // round timing to seconds to make multiple powerup timers // count in sync other->client->ps.powerups[ent->item->giTag] = level.time - ( level.time % 1000 ); G_LogWeaponPowerup(other->s.number, ent->item->giTag); } if ( ent->count ) { quantity = ent->count; } else { quantity = ent->item->quantity; } other->client->ps.powerups[ent->item->giTag] += quantity * 1000; // give any nearby players a "denied" anti-reward for ( i = 0 ; i < level.maxclients ; i++ ) { vec3_t delta; float len; vec3_t forward; trace_t tr; client = &level.clients[i]; if ( client == other->client ) { continue; } if ( client->pers.connected == CON_DISCONNECTED ) { continue; } if ( client->ps.stats[STAT_HEALTH] <= 0 ) { continue; } // if same team in team game, no sound // cannot use OnSameTeam as it expects to g_entities, not clients if ( g_gametype.integer >= GT_TEAM && other->client->sess.sessionTeam == client->sess.sessionTeam ) { continue; } // if too far away, no sound VectorSubtract( ent->s.pos.trBase, client->ps.origin, delta ); len = VectorNormalize( delta ); if ( len > 192 ) { continue; } // if not facing, no sound AngleVectors( client->ps.viewangles, forward, NULL, NULL ); if ( DotProduct( delta, forward ) < 0.4 ) { continue; } // if not line of sight, no sound trap_Trace( &tr, client->ps.origin, NULL, NULL, ent->s.pos.trBase, ENTITYNUM_NONE, CONTENTS_SOLID ); if ( tr.fraction != 1.0 ) { continue; } // anti-reward client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_DENIEDREWARD; } return RESPAWN_POWERUP; } //====================================================================== int Pickup_Holdable( gentity_t *ent, gentity_t *other ) { other->client->ps.stats[STAT_HOLDABLE_ITEM] = ent->item - bg_itemlist; other->client->ps.stats[STAT_HOLDABLE_ITEMS] |= (1 << ent->item->giTag); G_LogWeaponItem(other->s.number, ent->item->giTag); return RESPAWN_HOLDABLE; } //====================================================================== void Add_Ammo (gentity_t *ent, int weapon, int count) { ent->client->ps.ammo[weapon] += count; if ( ent->client->ps.ammo[weapon] > ammoData[weapon].max ) { ent->client->ps.ammo[weapon] = ammoData[weapon].max; } } int Pickup_Ammo (gentity_t *ent, gentity_t *other) { int quantity; if ( ent->count ) { quantity = ent->count; } else { quantity = ent->item->quantity; } Add_Ammo (other, ent->item->giTag, quantity); return RESPAWN_AMMO; } //====================================================================== int Pickup_Weapon (gentity_t *ent, gentity_t *other) { int quantity; if ( ent->count < 0 ) { quantity = 0; // None for you, sir! } else { if ( ent->count ) { quantity = ent->count; } else { quantity = ent->item->quantity; } // dropped items and teamplay weapons always have full ammo if ( ! (ent->flags & FL_DROPPED_ITEM) && g_gametype.integer != GT_TEAM ) { // respawning rules // drop the quantity if the already have over the minimum if ( other->client->ps.ammo[weaponData[ ent->item->giTag ].ammoIndex] < quantity ) { quantity = quantity - other->client->ps.ammo[weaponData[ ent->item->giTag ].ammoIndex]; } else { quantity = 1; // only add a single shot } } } // add the weapon other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag ); Add_Ammo( other, weaponData[ent->item->giTag].ammoIndex, quantity ); G_LogWeaponPickup(other->s.number, ent->item->giTag); // team deathmatch has slow weapon respawns if ( g_gametype.integer == GT_TEAM ) { return g_weaponTeamRespawn.integer; } return g_weaponRespawn.integer; } //====================================================================== int Pickup_Health (gentity_t *ent, gentity_t *other) { int max; int quantity; // small and mega healths will go over the max if ( ent->item->quantity != 5 && ent->item->quantity != 100 ) { max = other->client->ps.stats[STAT_MAX_HEALTH]; } else { max = other->client->ps.stats[STAT_MAX_HEALTH] * 2; } if ( ent->count ) { quantity = ent->count; } else { quantity = ent->item->quantity; } other->health += quantity; if (other->health > max ) { other->health = max; } other->client->ps.stats[STAT_HEALTH] = other->health; if ( ent->item->quantity == 100 ) { // mega health respawns slow return RESPAWN_MEGAHEALTH; } return RESPAWN_HEALTH; } //====================================================================== int Pickup_Armor( gentity_t *ent, gentity_t *other ) { other->client->ps.stats[STAT_ARMOR] += ent->item->quantity; if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH]/* * 2*/ ) { other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH]/* * 2*/; } return RESPAWN_ARMOR; } //====================================================================== /* =============== RespawnItem =============== */ void RespawnItem( gentity_t *ent ) { // randomly select from teamed entities if (ent->team) { gentity_t *master; int count; int choice; if ( !ent->teammaster ) { G_Error( "RespawnItem: bad teammaster"); } master = ent->teammaster; for (count = 0, ent = master; ent; ent = ent->teamchain, count++) ; choice = rand() % count; for (count = 0, ent = master; count < choice; ent = ent->teamchain, count++) ; } ent->r.contents = CONTENTS_TRIGGER; //ent->s.eFlags &= ~EF_NODRAW; ent->s.eFlags &= ~(EF_NODRAW | EF_ITEMPLACEHOLDER); ent->r.svFlags &= ~SVF_NOCLIENT; trap_LinkEntity (ent); if ( ent->item->giType == IT_POWERUP ) { // play powerup spawn sound to all clients gentity_t *te; // if the powerup respawn sound should Not be global if (ent->speed) { te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND ); } else { te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND ); } te->s.eventParm = G_SoundIndex( "sound/items/respawn1" ); te->r.svFlags |= SVF_BROADCAST; } // play the normal respawn sound only to nearby clients G_AddEvent( ent, EV_ITEM_RESPAWN, 0 ); ent->nextthink = 0; } /* =============== Touch_Item =============== */ void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) { int respawn; qboolean predict; if (ent->s.eFlags & EF_ITEMPLACEHOLDER) { return; } if (ent->s.eFlags & EF_NODRAW) { return; } if (ent->item->giType == IT_WEAPON && ent->s.powerups && ent->s.powerups < level.time) { ent->s.generic1 = 0; ent->s.powerups = 0; } if (!other->client) return; if (other->health < 1) return; // dead people can't pickup if (ent->item->giType == IT_POWERUP && (ent->item->giTag == PW_FORCE_ENLIGHTENED_LIGHT || ent->item->giTag == PW_FORCE_ENLIGHTENED_DARK)) { if (ent->item->giTag == PW_FORCE_ENLIGHTENED_LIGHT) { if (other->client->ps.fd.forceSide != FORCE_LIGHTSIDE) { return; } } else { if (other->client->ps.fd.forceSide != FORCE_DARKSIDE) { return; } } } // the same pickup rules are used for client side and server side if ( !BG_CanItemBeGrabbed( g_gametype.integer, &ent->s, &other->client->ps ) ) { return; } G_LogPrintf( "Item: %i %s\n", other->s.number, ent->item->classname ); predict = other->client->pers.predictItemPickup; // call the item-specific pickup function switch( ent->item->giType ) { case IT_WEAPON: respawn = Pickup_Weapon(ent, other); // predict = qfalse; predict = qtrue; break; case IT_AMMO: respawn = Pickup_Ammo(ent, other); // predict = qfalse; predict = qtrue; break; case IT_ARMOR: respawn = Pickup_Armor(ent, other); // predict = qfalse; predict = qtrue; break; case IT_HEALTH: respawn = Pickup_Health(ent, other); // predict = qfalse; predict = qtrue; break; case IT_POWERUP: respawn = Pickup_Powerup(ent, other); predict = qfalse; // predict = qtrue; break; case IT_TEAM: respawn = Pickup_Team(ent, other); break; case IT_HOLDABLE: respawn = Pickup_Holdable(ent, other); break; default: return; } if ( !respawn ) { return; } // play the normal pickup sound if (predict) { if (other->client) { BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, ent->s.number, &other->client->ps); } else { G_AddPredictableEvent( other, EV_ITEM_PICKUP, ent->s.number ); } } else { G_AddEvent( other, EV_ITEM_PICKUP, ent->s.number ); } // powerup pickups are global broadcasts if ( ent->item->giType == IT_POWERUP || ent->item->giType == IT_TEAM) { // if we want the global sound to play if (!ent->speed) { gentity_t *te; te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP ); te->s.eventParm = ent->s.modelindex; te->r.svFlags |= SVF_BROADCAST; } else { gentity_t *te; te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP ); te->s.eventParm = ent->s.modelindex; // only send this temp entity to a single client te->r.svFlags |= SVF_SINGLECLIENT; te->r.singleClient = other->s.number; } } // fire item targets G_UseTargets (ent, other); // wait of -1 will not respawn if ( ent->wait == -1 ) { ent->r.svFlags |= SVF_NOCLIENT; ent->s.eFlags |= EF_NODRAW; ent->r.contents = 0; ent->unlinkAfterEvent = qtrue; return; } // non zero wait overrides respawn time if ( ent->wait ) { respawn = ent->wait; } // random can be used to vary the respawn time if ( ent->random ) { respawn += crandom() * ent->random; if ( respawn < 1 ) { respawn = 1; } } // dropped items will not respawn if ( ent->flags & FL_DROPPED_ITEM ) { ent->freeAfterEvent = qtrue; } // picked up items still stay around, they just don't // draw anything. This allows respawnable items // to be placed on movers. if (!(ent->flags & FL_DROPPED_ITEM) && (ent->item->giType==IT_WEAPON || ent->item->giType==IT_POWERUP)) { ent->s.eFlags |= EF_ITEMPLACEHOLDER; ent->s.eFlags &= ~EF_NODRAW; } else { ent->s.eFlags |= EF_NODRAW; ent->r.svFlags |= SVF_NOCLIENT; } ent->r.contents = 0; // ZOID // A negative respawn times means to never respawn this item (but don't // delete it). This is used by items that are respawned by third party // events such as ctf flags if ( respawn <= 0 ) { ent->nextthink = 0; ent->think = 0; } else { ent->nextthink = level.time + respawn * 1000; ent->think = RespawnItem; } trap_LinkEntity( ent ); } //====================================================================== /* ================ LaunchItem Spawns an item and tosses it forward ================ */ gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity ) { gentity_t *dropped; dropped = G_Spawn(); dropped->s.eType = ET_ITEM; dropped->s.modelindex = item - bg_itemlist; // store item number in modelindex dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item dropped->classname = item->classname; dropped->item = item; VectorSet (dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS); VectorSet (dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS); dropped->r.contents = CONTENTS_TRIGGER; dropped->touch = Touch_Item; G_SetOrigin( dropped, origin ); dropped->s.pos.trType = TR_GRAVITY; dropped->s.pos.trTime = level.time; VectorCopy( velocity, dropped->s.pos.trDelta ); dropped->s.eFlags |= EF_BOUNCE_HALF; if ((g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY) && item->giType == IT_TEAM) { // Special case for CTF flags dropped->think = Team_DroppedFlagThink; dropped->nextthink = level.time + 30000; Team_CheckDroppedItem( dropped ); //rww - so bots know if (strcmp(dropped->classname, "team_CTF_redflag") == 0) { droppedRedFlag = dropped; } else if (strcmp(dropped->classname, "team_CTF_blueflag") == 0) { droppedBlueFlag = dropped; } } else { // auto-remove after 30 seconds dropped->think = G_FreeEntity; dropped->nextthink = level.time + 30000; } dropped->flags = FL_DROPPED_ITEM; if (item->giType == IT_WEAPON || item->giType == IT_POWERUP) { dropped->s.eFlags |= EF_DROPPEDWEAPON; } vectoangles(velocity, dropped->s.angles); dropped->s.angles[PITCH] = 0; if (item->giTag == WP_TRIP_MINE || item->giTag == WP_DET_PACK) { dropped->s.angles[PITCH] = -90; } if (item->giTag != WP_BOWCASTER && item->giTag != WP_DET_PACK && item->giTag != WP_THERMAL) { dropped->s.angles[ROLL] = -90; } trap_LinkEntity (dropped); return dropped; } /* ================ Drop_Item Spawns an item and tosses it forward ================ */ gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle ) { vec3_t velocity; vec3_t angles; VectorCopy( ent->s.apos.trBase, angles ); angles[YAW] += angle; angles[PITCH] = 0; // always forward AngleVectors( angles, velocity, NULL, NULL ); VectorScale( velocity, 150, velocity ); velocity[2] += 200 + crandom() * 50; return LaunchItem( item, ent->s.pos.trBase, velocity ); } /* ================ Use_Item Respawn the item ================ */ void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator ) { RespawnItem( ent ); } //====================================================================== /* ================ FinishSpawningItem Traces down to find where an item should rest, instead of letting them free fall from their spawn points ================ */ void FinishSpawningItem( gentity_t *ent ) { trace_t tr; vec3_t dest; // gitem_t *item; // VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS ); // VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS ); if (ent->item->giType == IT_WEAPON && g_weaponDisable.integer && (g_weaponDisable.integer & (1 << ent->item->giTag))) { if (g_gametype.integer != GT_JEDIMASTER) { G_FreeEntity( ent ); return; } } if (g_gametype.integer != GT_JEDIMASTER) { if (HasSetSaberOnly()) { if (ent->item->giType == IT_AMMO) { G_FreeEntity( ent ); return; } if (ent->item->giType == IT_HOLDABLE) { if (ent->item->giTag == HI_SEEKER || ent->item->giTag == HI_SHIELD || ent->item->giTag == HI_SENTRY_GUN) { G_FreeEntity( ent ); return; } } } } if (g_gametype.integer != GT_CTF && g_gametype.integer != GT_CTY && ent->item->giType == IT_TEAM) { int killMe = 0; switch (ent->item->giTag) { case PW_REDFLAG: killMe = 1; break; case PW_BLUEFLAG: killMe = 1; break; case PW_NEUTRALFLAG: killMe = 1; break; default: break; } if (killMe) { G_FreeEntity( ent ); return; } } VectorSet (ent->r.mins, -8, -8, -0); VectorSet (ent->r.maxs, 8, 8, 16); ent->s.eType = ET_ITEM; ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item ent->r.contents = CONTENTS_TRIGGER; ent->touch = Touch_Item; // useing an item causes it to respawn ent->use = Use_Item; // create a Ghoul2 model if the world model is a glm /* item = &bg_itemlist[ ent->s.modelindex ]; if (!stricmp(&item->world_model[0][strlen(item->world_model[0]) - 4], ".glm")) { trap_G2API_InitGhoul2Model(&ent->s, item->world_model[0], G_ModelIndex(item->world_model[0] ), 0, 0, 0, 0); ent->s.radius = 60; } */ if ( ent->spawnflags & 1 ) { // suspended G_SetOrigin( ent, ent->s.origin ); } else { // drop to floor //if it is directly even with the floor it will return startsolid, so raise up by 0.1 //and temporarily subtract 0.1 from the z maxs so that going up doesn't push into the ceiling ent->s.origin[2] += 0.1; ent->r.maxs[2] -= 0.1; VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 ); trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID ); if ( tr.startsolid ) { G_Printf ("FinishSpawningItem: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); G_FreeEntity( ent ); return; } //add the 0.1 back after the trace ent->r.maxs[2] += 0.1; // allow to ride movers ent->s.groundEntityNum = tr.entityNum; G_SetOrigin( ent, tr.endpos ); } // team slaves and targeted items aren't present at start if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) { ent->s.eFlags |= EF_NODRAW; ent->r.contents = 0; return; } // powerups don't spawn in for a while /* if ( ent->item->giType == IT_POWERUP ) { float respawn; respawn = 45 + crandom() * 15; ent->s.eFlags |= EF_NODRAW; ent->r.contents = 0; ent->nextthink = level.time + respawn * 1000; ent->think = RespawnItem; return; } */ trap_LinkEntity (ent); } qboolean itemRegistered[MAX_ITEMS]; /* ================== G_CheckTeamItems ================== */ void G_CheckTeamItems( void ) { // Set up team stuff Team_InitGame(); if( g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY ) { gitem_t *item; // check for the two flags item = BG_FindItem( "Red Flag" ); if ( !item || !itemRegistered[ item - bg_itemlist ] ) { G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_redflag in map" ); } item = BG_FindItem( "Blue Flag" ); if ( !item || !itemRegistered[ item - bg_itemlist ] ) { G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_blueflag in map" ); } } } /* ============== ClearRegisteredItems ============== */ void ClearRegisteredItems( void ) { memset( itemRegistered, 0, sizeof( itemRegistered ) ); // players always start with the base weapon RegisterItem( BG_FindItemForWeapon( WP_BRYAR_PISTOL ) ); RegisterItem( BG_FindItemForWeapon( WP_STUN_BATON ) ); } /* =============== RegisterItem The item will be added to the precache list =============== */ void RegisterItem( gitem_t *item ) { if ( !item ) { G_Error( "RegisterItem: NULL" ); } itemRegistered[ item - bg_itemlist ] = qtrue; } /* =============== SaveRegisteredItems Write the needed items to a config string so the client will know which ones to precache =============== */ void SaveRegisteredItems( void ) { char string[MAX_ITEMS+1]; int i; int count; count = 0; for ( i = 0 ; i < bg_numItems ; i++ ) { if ( itemRegistered[i] ) { count++; string[i] = '1'; } else { string[i] = '0'; } } string[ bg_numItems ] = 0; G_Printf( "%i items registered\n", count ); trap_SetConfigstring(CS_ITEMS, string); } /* ============ G_ItemDisabled ============ */ int G_ItemDisabled( gitem_t *item ) { char name[128]; Com_sprintf(name, sizeof(name), "disable_%s", item->classname); return trap_Cvar_VariableIntegerValue( name ); } /* ============ G_SpawnItem Sets the clipping size and plants the object on the floor. Items can't be immediately dropped to floor, because they might be on an entity that hasn't spawned yet. ============ */ void G_SpawnItem (gentity_t *ent, gitem_t *item) { G_SpawnFloat( "random", "0", &ent->random ); G_SpawnFloat( "wait", "0", &ent->wait ); RegisterItem( item ); if ( G_ItemDisabled(item) ) return; ent->item = item; // some movers spawn on the second frame, so delay item // spawns until the third frame so they can ride trains ent->nextthink = level.time + FRAMETIME * 2; ent->think = FinishSpawningItem; ent->physicsBounce = 0.50; // items are bouncy if ( item->giType == IT_POWERUP ) { G_SoundIndex( "sound/items/respawn1" ); G_SpawnFloat( "noglobalsound", "0", &ent->speed); } } /* ================ G_BounceItem ================ */ void G_BounceItem( gentity_t *ent, trace_t *trace ) { vec3_t velocity; float dot; int hitTime; // reflect the velocity on the trace plane hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction; BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity ); dot = DotProduct( velocity, trace->plane.normal ); VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta ); // cut the velocity to keep from bouncing forever VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta ); // check for stop if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) { trace->endpos[2] += 1.0; // make sure it is off ground SnapVector( trace->endpos ); G_SetOrigin( ent, trace->endpos ); ent->s.groundEntityNum = trace->entityNum; return; } VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin); VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); ent->s.pos.trTime = level.time; } /* ================ G_RunItem ================ */ void G_RunItem( gentity_t *ent ) { vec3_t origin; trace_t tr; int contents; int mask; // if groundentity has been set to -1, it may have been pushed off an edge if ( ent->s.groundEntityNum == -1 ) { if ( ent->s.pos.trType != TR_GRAVITY ) { ent->s.pos.trType = TR_GRAVITY; ent->s.pos.trTime = level.time; } } if ( ent->s.pos.trType == TR_STATIONARY ) { // check think function G_RunThink( ent ); return; } // get current position BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); // trace a line from the previous position to the current position if ( ent->clipmask ) { mask = ent->clipmask; } else { mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID; } trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->r.ownerNum, mask ); VectorCopy( tr.endpos, ent->r.currentOrigin ); if ( tr.startsolid ) { tr.fraction = 0; } trap_LinkEntity( ent ); // FIXME: avoid this for stationary? // check think function G_RunThink( ent ); if ( tr.fraction == 1 ) { return; } // if it is in a nodrop volume, remove it contents = trap_PointContents( ent->r.currentOrigin, -1 ); if ( contents & CONTENTS_NODROP ) { if (ent->item && ent->item->giType == IT_TEAM) { Team_FreeEntity(ent); } else { G_FreeEntity( ent ); } return; } G_BounceItem( ent, &tr ); }