static void GM_CheckFireState( void ) { if ( enemyCS4 ) {//if have a clear shot, always try return; } if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) {//if moving at all, don't do this return; } //See if we should continue to fire on their last position if ( !hitAlly4 && NPCInfo->enemyLastSeenTime > 0 ) { if ( level.time - NPCInfo->enemyLastSeenTime < 10000 ) { if ( !Q_irand( 0, 10 ) ) { //Fire on the last known position vec3_t muzzle, dir, angles; qboolean tooClose = qfalse; qboolean tooFar = qfalse; float distThreshold; float dist; CalcEntitySpot( NPC, SPOT_HEAD, muzzle ); if ( VectorCompare( impactPos4, vec3_origin ) ) {//never checked ShotEntity this frame, so must do a trace... trace_t tr; //vec3_t mins = {-2,-2,-2}, maxs = {2,2,2}; vec3_t forward, end; AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL ); VectorMA( muzzle, 8192, forward, end ); trap_Trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); VectorCopy( tr.endpos, impactPos4 ); } //see if impact would be too close to me distThreshold = 16384/*128*128*/;//default if ( NPC->s.weapon == WP_REPEATER ) { if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) { distThreshold = 65536/*256*256*/; } } dist = DistanceSquared( impactPos4, muzzle ); if ( dist < distThreshold ) {//impact would be too close to me tooClose = qtrue; } else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 ) {//we've haven't seen them in the last 5 seconds //see if it's too far from where he is distThreshold = 65536/*256*256*/;//default if ( NPC->s.weapon == WP_REPEATER ) { if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) { distThreshold = 262144/*512*512*/; } } dist = DistanceSquared( impactPos4, NPCInfo->enemyLastSeenLocation ); if ( dist > distThreshold ) {//impact would be too far from enemy tooFar = qtrue; } } if ( !tooClose && !tooFar ) {//okay too shoot at last pos VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); VectorNormalize( dir ); vectoangles( dir, angles ); NPCInfo->desiredYaw = angles[YAW]; NPCInfo->desiredPitch = angles[PITCH]; shoot4 = qtrue; faceEnemy4 = qfalse; return; } } } } }
//----------------------------------------------------- void turretG2_base_think( gentity_t *self ) //----------------------------------------------------- { qboolean turnOff = qtrue; float enemyDist; vec3_t enemyDir, org, org2; self->nextthink = level.time + level.frameTime; if ( self->health <= 0 ) {//dead if (self->spawnflags & SPF_TURRETG2_CANRESPAWN) {//can respawn if ( self->genericValue5 && self->genericValue5 < level.time ) { //we are dead, see if it's time to respawn turretG2_respawn( self ); } } return; } else if ( self->spawnflags & 1 ) {// not turned on turretG2_turnoff( self ); turretG2_aim( self ); // No target self->flags |= FL_NOTARGET; return; } else { // I'm all hot and bothered self->flags &= ~FL_NOTARGET; } if ( self->enemy ) { if ( self->enemy->health < 0 || !self->enemy->inuse ) { self->enemy = NULL; } } if ( self->last_move_time < level.time ) {//MISNOMER: used a enemy recalcing debouncer if ( turretG2_find_enemies( self ) ) {//found one turnOff = qfalse; if ( self->enemy->client ) {//hold on to clients for a min of 3 seconds self->last_move_time = level.time + 3000; } else {//hold less self->last_move_time = level.time + 500; } } } if ( self->enemy != NULL ) { if ( self->enemy->client && self->enemy->client->sess.sessionTeam == TEAM_SPECTATOR ) {//don't keep going after spectators self->enemy = NULL; } else {//FIXME: remain single-minded or look for a new enemy every now and then? // enemy is alive VectorSubtract( self->enemy->r.currentOrigin, self->r.currentOrigin, enemyDir ); enemyDist = VectorLengthSquared( enemyDir ); if ( enemyDist < self->radius * self->radius ) { // was in valid radius if ( trap_InPVS( self->r.currentOrigin, self->enemy->r.currentOrigin ) ) { // Every now and again, check to see if we can even trace to the enemy trace_t tr; if ( self->enemy->client ) { VectorCopy( self->enemy->client->renderInfo.eyePoint, org ); } else { VectorCopy( self->enemy->r.currentOrigin, org ); } VectorCopy( self->r.currentOrigin, org2 ); if ( self->spawnflags & 2 ) { org2[2] += 10; } else { org2[2] -= 10; } trap_Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT ); if ( !tr.allsolid && !tr.startsolid && tr.entityNum == self->enemy->s.number ) { turnOff = qfalse; // Can see our enemy } } } } } if ( turnOff ) { if ( self->bounceCount < level.time ) // bounceCount is used to keep the thing from ping-ponging from on to off { turretG2_turnoff( self ); } } else { // keep our enemy for a minimum of 2 seconds from now self->bounceCount = level.time + 2000 + random() * 150; } turretG2_aim( self ); if ( !turnOff ) { turretG2_head_think( self ); } }
/** * @brief 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; vec3_t maxs; if (ent->spawnflags & 1) // suspended { VectorSet(ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS); VectorSet(ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS); VectorCopy(ent->r.maxs, maxs); } else { // had to modify this so that items would spawn in shelves VectorSet(ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, 0); VectorSet(ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS); VectorCopy(ent->r.maxs, maxs); maxs[2] /= 2; } ent->r.contents = CONTENTS_TRIGGER | CONTENTS_ITEM; ent->touch = Touch_Item_Auto; ent->s.eType = ET_ITEM; ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex ent->s.otherEntityNum2 = 0; // takes modelindex2's place in signaling a dropped item // we don't use this (yet, anyway) so I'm taking it so you can specify a model for treasure items and clipboards //ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item if (ent->model) { ent->s.modelindex2 = G_ModelIndex(ent->model); } // using an item causes it to respawn ent->use = Use_Item; // moved this up so it happens for suspended items too (and made it a function) G_SetAngle(ent, ent->s.angles); if (ent->spawnflags & 1) // suspended { G_SetOrigin(ent, ent->s.origin); } else { VectorSet(dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096); trap_Trace(&tr, ent->s.origin, ent->r.mins, maxs, dest, ent->s.number, MASK_SOLID); if (tr.startsolid) { vec3_t temp; VectorCopy(ent->s.origin, temp); temp[2] -= ITEM_RADIUS; VectorSet(dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096); trap_Trace(&tr, temp, ent->r.mins, 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; } // allow to ride movers ent->s.groundEntityNum = tr.entityNum; G_SetOrigin(ent, tr.endpos); } if (ent->spawnflags & 2) // spin { ent->s.eFlags |= EF_SPINNING; } // team slaves and targeted items aren't present at start if ((ent->flags & FL_TEAMSLAVE) || ent->targetname) { ent->flags |= FL_NODRAW; //ent->s.eFlags |= EF_NODRAW; ent->r.contents = 0; return; } // health/ammo can potentially be multi-stage (multiple use) if (ent->item->giType == IT_HEALTH || ent->item->giType == IT_AMMO) { int i; // having alternate models defined in bg_misc.c for a health or ammo item specify it as "multi-stage" // - left-hand operand of comma expression has no effect // initial line: for(i=0;i<4,ent->item->world_model[i];i++) {} for (i = 0; i < 4 && ent->item->world_model[i] ; i++) { } ent->s.density = i - 1; // store number of stages in 'density' for client (most will have '1') } trap_LinkEntity(ent); }
/** * @brief Spawns an item and tosses it forward. */ gentity_t *LaunchItem(gitem_t *item, vec3_t origin, vec3_t velocity, int ownerNum) { gentity_t *dropped = G_Spawn(); trace_t tr; vec3_t vec, temp; dropped->s.eType = ET_ITEM; dropped->s.modelindex = item - bg_itemlist; // store item number in modelindex dropped->s.otherEntityNum2 = 1; // this is taking modelindex2's place for a dropped item dropped->classname = item->classname; dropped->item = item; VectorSet(dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, 0); // so items sit on the ground VectorSet(dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, 2 * ITEM_RADIUS); // so items sit on the ground dropped->r.contents = CONTENTS_TRIGGER | CONTENTS_ITEM; dropped->clipmask = CONTENTS_SOLID | CONTENTS_MISSILECLIP; // fix for items falling through grates dropped->touch = Touch_Item_Auto; trap_Trace(&tr, origin, dropped->r.mins, dropped->r.maxs, origin, ownerNum, MASK_SOLID); if (tr.startsolid) { int i; VectorSubtract(g_entities[ownerNum].s.origin, origin, temp); VectorNormalize(temp); for (i = 16; i <= 48; i += 16) { VectorScale(temp, i, vec); VectorAdd(origin, vec, origin); trap_Trace(&tr, origin, dropped->r.mins, dropped->r.maxs, origin, ownerNum, MASK_SOLID); if (!tr.startsolid) { break; } } } G_SetOrigin(dropped, origin); dropped->s.pos.trType = TR_GRAVITY; dropped->s.pos.trTime = level.time; VectorCopy(velocity, dropped->s.pos.trDelta); // set yaw to parent angles temp[PITCH] = 0; temp[YAW] = g_entities[ownerNum].s.apos.trBase[YAW]; temp[ROLL] = 0; G_SetAngle(dropped, temp); dropped->s.eFlags |= EF_BOUNCE_HALF; if (item->giType == IT_TEAM) // Special case for CTF flags { gentity_t *flag = &g_entities[g_entities[ownerNum].client->flagParent]; dropped->s.otherEntityNum = g_entities[ownerNum].client->flagParent; // store the entitynum of our original flag spawner dropped->s.density = 1; dropped->think = Team_DroppedFlagThink; dropped->nextthink = level.time + 30000; if (level.gameManager) { G_Script_ScriptEvent(level.gameManager, "trigger", flag->item->giTag == PW_REDFLAG ? "allied_object_dropped" : "axis_object_dropped"); } G_Script_ScriptEvent(flag, "trigger", "dropped"); } else // auto-remove after 30 seconds { dropped->think = G_FreeEntity; dropped->nextthink = level.time + 30000; } dropped->flags = FL_DROPPED_ITEM; trap_LinkEntity(dropped); return dropped; }
void VEH_TurretThink( Vehicle_t *pVeh, gentity_t *parent, int turretNum ) //----------------------------------------------------- { qboolean doAim = qfalse; float enemyDist, rangeSq; vec3_t enemyDir; turretStats_t *turretStats = &pVeh->m_pVehicleInfo->turret[turretNum]; vehWeaponInfo_t *vehWeapon = NULL; gentity_t *turretEnemy = NULL; int curMuzzle = 0;//? if ( !turretStats || !turretStats->iAmmoMax ) {//not a valid turret return; } if ( turretStats->passengerNum && pVeh->m_iNumPassengers >= turretStats->passengerNum ) {//the passenger that has control of this turret is on the ship VEH_TurretObeyPassengerControl( pVeh, parent, turretNum ); return; } else if ( !turretStats->bAI )//try AI {//this turret does not think on its own. return; } //okay, so it has AI, but still don't think if there's no pilot! if ( !pVeh->m_pPilot ) { return; } vehWeapon = &g_vehWeaponInfo[turretStats->iWeapon]; rangeSq = (turretStats->fAIRange*turretStats->fAIRange); curMuzzle = pVeh->turretStatus[turretNum].nextMuzzle; if ( pVeh->turretStatus[turretNum].enemyEntNum < ENTITYNUM_WORLD ) { turretEnemy = &g_entities[pVeh->turretStatus[turretNum].enemyEntNum]; if ( turretEnemy->health < 0 || !turretEnemy->inuse || turretEnemy == ((gentity_t*)pVeh->m_pPilot)//enemy became my pilot///? || turretEnemy == parent || turretEnemy->r.ownerNum == parent->s.number // a passenger? || ( turretEnemy->client && turretEnemy->client->sess.sessionTeam == TEAM_SPECTATOR ) ) {//don't keep going after spectators, pilot, self, dead people, etc. turretEnemy = NULL; pVeh->turretStatus[turretNum].enemyEntNum = ENTITYNUM_NONE; } } if ( pVeh->turretStatus[turretNum].enemyHoldTime < level.time ) { if ( VEH_TurretFindEnemies( pVeh, parent, turretStats, turretNum, curMuzzle ) ) { turretEnemy = &g_entities[pVeh->turretStatus[turretNum].enemyEntNum]; doAim = qtrue; } else if ( parent->enemy && parent->enemy->s.number < ENTITYNUM_WORLD ) { if ( g_gametype.integer < GT_TEAM || !OnSameTeam( parent->enemy, parent ) ) {//either not in a team game or the enemy isn't on the same team turretEnemy = parent->enemy; doAim = qtrue; } } if ( turretEnemy ) {//found one if ( turretEnemy->client ) {//hold on to clients for a min of 3 seconds pVeh->turretStatus[turretNum].enemyHoldTime = level.time + 3000; } else {//hold less pVeh->turretStatus[turretNum].enemyHoldTime = level.time + 500; } } } if ( turretEnemy != NULL ) { if ( turretEnemy->health > 0 ) { // enemy is alive WP_CalcVehMuzzle( parent, curMuzzle ); VectorSubtract( turretEnemy->r.currentOrigin, pVeh->m_vMuzzlePos[curMuzzle], enemyDir ); enemyDist = VectorLengthSquared( enemyDir ); if ( enemyDist < rangeSq ) { // was in valid radius if ( trap_InPVS( pVeh->m_vMuzzlePos[curMuzzle], turretEnemy->r.currentOrigin ) ) { // Every now and again, check to see if we can even trace to the enemy trace_t tr; vec3_t start, end; VectorCopy( pVeh->m_vMuzzlePos[curMuzzle], start ); VectorCopy( turretEnemy->r.currentOrigin, end ); trap_Trace( &tr, start, NULL, NULL, end, parent->s.number, MASK_SHOT ); if ( tr.entityNum == turretEnemy->s.number || (!tr.allsolid && !tr.startsolid ) ) { doAim = qtrue; // Can see our enemy } } } } } if ( doAim ) { vec3_t aimAngles; if ( VEH_TurretAim( pVeh, parent, turretEnemy, turretStats, vehWeapon, turretNum, curMuzzle, aimAngles ) ) { VEH_TurretCheckFire( pVeh, parent, /*turretEnemy,*/ turretStats, vehWeapon, turretNum, curMuzzle ); } } }
void Bullet_Fire(Gentity *ent, float spread, int damage) { Trace tr; Vec3 end; #ifdef MISSIONPACK Vec3 impactpoint, bouncedir; #endif float r; float u; Gentity *tent; Gentity *traceEnt; int i, passent; damage *= s_quadFactor; r = random() * M_PI * 2.0f; u = sin(r) * crandom() * spread * 16; r = cos(r) * crandom() * spread * 16; saddv3 (muzzle, 8192*16, forward, end); saddv3 (end, r, right, end); saddv3 (end, u, up, end); passent = ent->s.number; for(i = 0; i < 10; i++){ trap_Trace (&tr, muzzle, NULL, NULL, end, passent, MASK_SHOT); if(tr.surfaceFlags & SURF_NOIMPACT) return; traceEnt = &g_entities[ tr.entityNum ]; /* snap the endpos to integers, but nudged towards the line */ snapv3Towards(tr.endpos, muzzle); /* send bullet impact */ if(traceEnt->takedamage && traceEnt->client){ tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_FLESH); tent->s.eventParm = traceEnt->s.number; if(LogAccuracyHit(traceEnt, ent)) ent->client->accuracy_hits++; }else{ tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_WALL); tent->s.eventParm = DirToByte(tr.plane.normal); } tent->s.otherEntityNum = ent->s.number; if(traceEnt->takedamage){ #if 0 /* we still want bouncing rails, so saving for later */ if(traceEnt->client && traceEnt->client->invulnerabilityTime > level.time){ if(G_InvulnerabilityEffect(traceEnt, forward, tr.endpos, impactpoint, bouncedir)){ G_BounceProjectile(muzzle, impactpoint, bouncedir, end); copyv3(impactpoint, muzzle); /* the player can hit him/herself with the bounced rail */ passent = ENTITYNUM_NONE; }else{ copyv3(tr.endpos, muzzle); passent = traceEnt->s.number; } continue; }else{ #endif G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_MACHINEGUN); #ifdef MISSIONPACK } #endif } break; } }
/* ============= SpawnCorpse A player is respawning, so make an entity that looks just like the existing corpse to leave behind. ============= */ void SpawnCorpse( gentity_t *ent ) { gentity_t *body; int contents; vec3_t origin, dest; trace_t tr; float vDiff; VectorCopy( ent->r.currentOrigin, origin ); trap_UnlinkEntity( ent ); // if client is in a nodrop area, don't leave the body contents = trap_PointContents( origin, -1 ); if( contents & CONTENTS_NODROP ) return; body = G_Spawn( ); VectorCopy( ent->s.apos.trBase, body->s.angles ); body->s.eFlags = EF_DEAD; body->s.eType = ET_CORPSE; body->s.number = body - g_entities; body->timestamp = level.time; body->s.event = 0; body->r.contents = CONTENTS_CORPSE; body->s.clientNum = ent->client->ps.stats[ STAT_CLASS ]; body->nonSegModel = ent->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL; if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) body->classname = "humanCorpse"; else body->classname = "alienCorpse"; body->s.misc = MAX_CLIENTS; body->think = BodySink; body->nextthink = level.time + 20000; body->s.legsAnim = ent->s.legsAnim; if( !body->nonSegModel ) { switch( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { case BOTH_DEATH1: case BOTH_DEAD1: body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; break; case BOTH_DEATH2: case BOTH_DEAD2: body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; break; case BOTH_DEATH3: case BOTH_DEAD3: default: body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; break; } } else { switch( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { case NSPA_DEATH1: case NSPA_DEAD1: body->s.legsAnim = NSPA_DEAD1; break; case NSPA_DEATH2: case NSPA_DEAD2: body->s.legsAnim = NSPA_DEAD2; break; case NSPA_DEATH3: case NSPA_DEAD3: default: body->s.legsAnim = NSPA_DEAD3; break; } } body->takedamage = qfalse; body->health = ent->health = ent->client->ps.stats[ STAT_HEALTH ]; ent->health = 0; //change body dimensions BG_ClassBoundingBox( ent->client->ps.stats[ STAT_CLASS ], NULL, NULL, NULL, body->r.mins, body->r.maxs ); vDiff = body->r.mins[ 2 ] - ent->r.mins[ 2 ]; //drop down to match the *model* origins of ent and body VectorSet( dest, origin[ 0 ], origin[ 1 ], origin[ 2 ] - vDiff ); trap_Trace( &tr, origin, body->r.mins, body->r.maxs, dest, body->s.number, body->clipmask ); VectorCopy( tr.endpos, origin ); G_SetOrigin( body, origin ); VectorCopy( origin, body->s.origin ); body->s.pos.trType = TR_GRAVITY; body->s.pos.trTime = level.time; VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); trap_LinkEntity( body ); }
/* ================ G_RunMissile ================ */ void G_RunMissile( gentity_t *ent ) { vec3_t origin, groundSpot; trace_t tr; int passent; qboolean isKnockedSaber = qfalse; if (ent->neverFree && ent->s.weapon == WP_SABER && (ent->flags & FL_BOUNCE_HALF)) { isKnockedSaber = qtrue; ent->s.pos.trType = TR_GRAVITY; } // get current position BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); // if this missile bounced off an invulnerability sphere if ( ent->target_ent ) { passent = ent->target_ent->s.number; } else { // ignore interactions with the missile owner if ( (ent->r.svFlags&SVF_OWNERNOTSHARED) && (ent->s.eFlags&EF_JETPACK_ACTIVE) ) {//A vehicle missile that should be solid to its owner //I don't care about hitting my owner passent = ent->s.number; } else { passent = ent->r.ownerNum; } } // trace a line from the previous position to the current position if (d_projectileGhoul2Collision.integer) { trap_G2Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, ent->clipmask, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer ); if (tr.fraction != 1.0 && tr.entityNum < ENTITYNUM_WORLD) { gentity_t *g2Hit = &g_entities[tr.entityNum]; if (g2Hit->inuse && g2Hit->client && g2Hit->ghoul2) { //since we used G2TRFLAG_GETSURFINDEX, tr.surfaceFlags will actually contain the index of the surface on the ghoul2 model we collided with. g2Hit->client->g2LastSurfaceHit = tr.surfaceFlags; g2Hit->client->g2LastSurfaceTime = level.time; } if (g2Hit->ghoul2) { tr.surfaceFlags = 0; //clear the surface flags after, since we actually care about them in here. } } } else { trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, ent->clipmask ); } if ( tr.startsolid || tr.allsolid ) { // make sure the tr.entityNum is set to the entity we're stuck in trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, passent, ent->clipmask ); tr.fraction = 0; } else { VectorCopy( tr.endpos, ent->r.currentOrigin ); } if (ent->passThroughNum && tr.entityNum == (ent->passThroughNum-1)) { VectorCopy( origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); goto passthrough; } trap_LinkEntity( ent ); if (ent->s.weapon == G2_MODEL_PART && !ent->bounceCount) { vec3_t lowerOrg; trace_t trG; VectorCopy(ent->r.currentOrigin, lowerOrg); lowerOrg[2] -= 1; trap_Trace( &trG, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, lowerOrg, passent, ent->clipmask ); VectorCopy(trG.endpos, groundSpot); if (!trG.startsolid && !trG.allsolid && trG.entityNum == ENTITYNUM_WORLD) { ent->s.groundEntityNum = trG.entityNum; } else { ent->s.groundEntityNum = ENTITYNUM_NONE; } } if ( tr.fraction != 1) { // never explode or bounce on sky if ( tr.surfaceFlags & SURF_NOIMPACT ) { // If grapple, reset owner if (ent->parent && ent->parent->client && ent->parent->client->hook == ent) { ent->parent->client->hook = NULL; } if ((ent->s.weapon == WP_SABER && ent->isSaberEntity) || isKnockedSaber) { G_RunThink( ent ); return; } else if (ent->s.weapon != G2_MODEL_PART) { G_FreeEntity( ent ); return; } } #if 0 //will get stomped with missile impact event... if (ent->s.weapon > WP_NONE && ent->s.weapon < WP_NUM_WEAPONS && (tr.entityNum < MAX_CLIENTS || g_entities[tr.entityNum].s.eType == ET_NPC)) { //player or NPC, try making a mark on him /* gentity_t *evEnt = G_TempEntity(ent->r.currentOrigin, EV_GHOUL2_MARK); evEnt->s.owner = tr.entityNum; //the entity the mark should be placed on evEnt->s.weapon = ent->s.weapon; //the weapon used (to determine mark type) VectorCopy(ent->r.currentOrigin, evEnt->s.origin); //the point of impact //origin2 gets the predicted trajectory-based position. BG_EvaluateTrajectory( &ent->s.pos, level.time, evEnt->s.origin2 ); //If they are the same, there will be problems. if (VectorCompare(evEnt->s.origin, evEnt->s.origin2)) { evEnt->s.origin2[2] += 2; //whatever, at least it won't mess up. } */ //ok, let's try adding it to the missile ent instead (tempents bad!) G_AddEvent(ent, EV_GHOUL2_MARK, 0); //copy current pos to s.origin, and current projected traj to origin2 VectorCopy(ent->r.currentOrigin, ent->s.origin); BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->s.origin2 ); //the index for whoever we are hitting ent->s.otherEntityNum = tr.entityNum; if (VectorCompare(ent->s.origin, ent->s.origin2)) { ent->s.origin2[2] += 2.0f; //whatever, at least it won't mess up. } } #else if (ent->s.weapon > WP_NONE && ent->s.weapon < WP_NUM_WEAPONS && (tr.entityNum < MAX_CLIENTS || g_entities[tr.entityNum].s.eType == ET_NPC)) { //player or NPC, try making a mark on him //copy current pos to s.origin, and current projected traj to origin2 VectorCopy(ent->r.currentOrigin, ent->s.origin); BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->s.origin2 ); if (VectorCompare(ent->s.origin, ent->s.origin2)) { ent->s.origin2[2] += 2.0f; //whatever, at least it won't mess up. } } #endif G_MissileImpact( ent, &tr ); if (tr.entityNum == ent->s.otherEntityNum) { //if the impact event other and the trace ent match then it's ok to do the g2 mark ent->s.trickedentindex = 1; } if ( ent->s.eType != ET_MISSILE && ent->s.weapon != G2_MODEL_PART ) { return; // exploded } } passthrough: if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) ) {//stuck missiles should check some special stuff G_RunStuckMissile( ent ); return; } if (ent->s.weapon == G2_MODEL_PART) { if (ent->s.groundEntityNum == ENTITYNUM_WORLD) { ent->s.pos.trType = TR_LINEAR; VectorClear(ent->s.pos.trDelta); ent->s.pos.trTime = level.time; VectorCopy(groundSpot, ent->s.pos.trBase); VectorCopy(groundSpot, ent->r.currentOrigin); if (ent->s.apos.trType != TR_STATIONARY) { ent->s.apos.trType = TR_STATIONARY; ent->s.apos.trTime = level.time; ent->s.apos.trBase[ROLL] = 0; ent->s.apos.trBase[PITCH] = 0; } } } // check think function after bouncing G_RunThink( ent ); }
/* ================ G_RunMissile ================ */ void G_RunMissile( gentity_t *ent ) { vec3_t origin; trace_t tr; int passent; qboolean impact = qfalse; // get current position BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); // ignore interactions with the missile owner passent = ent->r.ownerNum; // general trace to see if we hit anything at all trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, ent->clipmask ); if( tr.startsolid || tr.allsolid ) { tr.fraction = 0.0f; VectorCopy( ent->r.currentOrigin, tr.endpos ); } if( tr.fraction < 1.0f ) { if( !ent->pointAgainstWorld || tr.contents & CONTENTS_BODY ) { // We hit an entity or we don't care impact = qtrue; } else { trap_Trace( &tr, ent->r.currentOrigin, NULL, NULL, origin, passent, ent->clipmask ); if( tr.fraction < 1.0f ) { // Hit the world with point trace impact = qtrue; } else { if( tr.contents & CONTENTS_BODY ) { // Hit an entity impact = qtrue; } else { trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, CONTENTS_BODY ); if( tr.fraction < 1.0f ) impact = qtrue; } } } } VectorCopy( tr.endpos, ent->r.currentOrigin ); if( impact ) { if( tr.surfaceFlags & SURF_NOIMPACT ) { // Never explode or bounce on sky G_FreeEntity( ent ); return; } G_MissileImpact( ent, &tr ); if( ent->s.eType != ET_MISSILE ) return; // exploded } ent->r.contents = CONTENTS_SOLID; //trick trap_LinkEntity into... trap_LinkEntity( ent ); ent->r.contents = 0; //...encoding bbox information // check think function after bouncing G_RunThink( ent ); }
qboolean G_PredictPlayerSlideMove( gentity_t *ent, float frametime ) { int bumpcount, numbumps; vec3_t dir; float d; int numplanes; vec3_t planes[MAX_CLIP_PLANES]; vec3_t primal_velocity, velocity, origin; vec3_t clipVelocity; int i, j, k; trace_t trace; vec3_t end; float time_left; float into; vec3_t endVelocity; vec3_t endClipVelocity; // vec3_t worldUp = { 0.0f, 0.0f, 1.0f }; numbumps = 4; VectorCopy( ent->s.pos.trDelta, primal_velocity ); VectorCopy( primal_velocity, velocity ); VectorCopy( ent->s.pos.trBase, origin ); VectorCopy( velocity, endVelocity ); time_left = frametime; numplanes = 0; for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) { // calculate position we are trying to move to VectorMA( origin, time_left, velocity, end ); // see if we can make it there trap_Trace( &trace, origin, ent->r.mins, ent->r.maxs, end, ent->s.number, ent->clipmask ); if (trace.allsolid) { // entity is completely trapped in another solid VectorClear( velocity ); VectorCopy( origin, ent->s.pos.trBase ); return qtrue; } if (trace.fraction > 0) { // actually covered some distance VectorCopy( trace.endpos, origin ); } if (trace.fraction == 1) { break; // moved the entire distance } time_left -= time_left * trace.fraction; if ( numplanes >= MAX_CLIP_PLANES ) { // this shouldn't really happen VectorClear( velocity ); VectorCopy( origin, ent->s.pos.trBase ); return qtrue; } // // if this is the same plane we hit before, nudge velocity // out along it, which fixes some epsilon issues with // non-axial planes // for ( i = 0; i < numplanes; i++ ) { if ( DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) { VectorAdd( trace.plane.normal, velocity, velocity ); break; } } if ( i < numplanes ) { continue; } VectorCopy( trace.plane.normal, planes[numplanes] ); numplanes++; // // modify velocity so it parallels all of the clip planes // // find a plane that it enters for ( i = 0; i < numplanes; i++ ) { into = DotProduct( velocity, planes[i] ); if ( into >= 0.1 ) { continue; // move doesn't interact with the plane } // slide along the plane G_PredictPlayerClipVelocity( velocity, planes[i], clipVelocity ); // slide along the plane G_PredictPlayerClipVelocity( endVelocity, planes[i], endClipVelocity ); // see if there is a second plane that the new move enters for ( j = 0; j < numplanes; j++ ) { if ( j == i ) { continue; } if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 ) { continue; // move doesn't interact with the plane } // try clipping the move to the plane G_PredictPlayerClipVelocity( clipVelocity, planes[j], clipVelocity ); G_PredictPlayerClipVelocity( endClipVelocity, planes[j], endClipVelocity ); // see if it goes back into the first clip plane if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) { continue; } // slide the original velocity along the crease CrossProduct( planes[i], planes[j], dir ); VectorNormalize( dir ); d = DotProduct( dir, velocity ); VectorScale( dir, d, clipVelocity ); CrossProduct( planes[i], planes[j], dir ); VectorNormalize( dir ); d = DotProduct( dir, endVelocity ); VectorScale( dir, d, endClipVelocity ); // see if there is a third plane the the new move enters for ( k = 0; k < numplanes; k++ ) { if ( k == i || k == j ) { continue; } if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 ) { continue; // move doesn't interact with the plane } // stop dead at a tripple plane interaction VectorClear( velocity ); VectorCopy( origin, ent->s.pos.trBase ); return qtrue; } } // if we have fixed all interactions, try another move VectorCopy( clipVelocity, velocity ); VectorCopy( endClipVelocity, endVelocity ); break; } } VectorCopy( endVelocity, velocity ); VectorCopy( origin, ent->s.pos.trBase ); return (bumpcount != 0); }
/* ============== AICast_VisibleFromPos ============== */ qboolean AICast_VisibleFromPos( vec3_t srcpos, int srcnum, vec3_t destpos, int destnum, qboolean updateVisPos ) { int i, contents_mask, passent, hitent; trace_t trace; vec3_t start, end, middle, eye; cast_state_t *cs = NULL; int srcviewheight; vec3_t destmins, destmaxs; vec3_t right, vec; qboolean inPVS; if ( g_entities[destnum].flags & FL_NOTARGET ) { return qfalse; } if ( srcnum < aicast_maxclients ) { cs = AICast_GetCastState( srcnum ); } // if ( cs && cs->bs ) { srcviewheight = cs->bs->cur_ps.viewheight; } else if ( g_entities[srcnum].client ) { srcviewheight = g_entities[srcnum].client->ps.viewheight; } else { srcviewheight = 0; } // VectorCopy( g_entities[destnum].r.mins, destmins ); VectorCopy( g_entities[destnum].r.maxs, destmaxs ); // //calculate middle of bounding box VectorAdd( destmins, destmaxs, middle ); VectorScale( middle, 0.5, middle ); VectorAdd( destpos, middle, middle ); // calculate eye position VectorCopy( srcpos, eye ); eye[2] += srcviewheight; // // set the right vector VectorSubtract( middle, eye, vec ); VectorNormalize( vec ); right[0] = vec[1]; right[1] = vec[0]; right[2] = 0; // inPVS = qfalse; // for ( i = 0; i < 5; i++ ) { if ( cs && updateVisPos ) { // if it's a grenade or something, PVS checks don't work very well //if the point is not in potential visible sight if ( i < 3 ) { // don't do PVS check for left/right checks if ( !trap_InPVS( eye, middle ) ) { continue; } else { inPVS = qtrue; } } else if ( !inPVS ) { break; // wasn't in potential view in either of the previous tests } // so don't bother doing left/right } // contents_mask = MASK_SHOT & ~CONTENTS_BODY; // we can see anything that a bullet can pass through passent = srcnum; hitent = destnum; VectorCopy( eye, start ); VectorCopy( middle, end ); //if the entity is in water, lava or slime if ( trap_PointContents( middle, destnum ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) { contents_mask |= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ); } //end if //if eye is in water, lava or slime if ( trap_PointContents( eye, srcnum ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) { if ( !( contents_mask & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) { passent = destnum; hitent = srcnum; VectorCopy( middle, start ); VectorCopy( eye, end ); } //end if contents_mask ^= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ); } //end if //trace from start to end trap_Trace( &trace, start, NULL, NULL, end, ENTITYNUM_NONE /*passent*/, contents_mask ); //if water was hit if ( trace.contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) { //if the water surface is translucent // if (trace.surface.flags & (SURF_TRANS33|SURF_TRANS66)) { //trace through the water contents_mask &= ~( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ); trap_Trace( &trace, trace.endpos, NULL, NULL, end, passent, contents_mask ); } //end if } //end if //if a full trace or the hitent was hit if ( trace.fraction >= 1 || trace.entityNum == hitent ) { return qtrue; } //check bottom and top of bounding box as well if ( i == 0 ) { middle[2] -= ( destmaxs[2] - destmins[2] ) * 0.5; } else if ( i == 1 ) { middle[2] += destmaxs[2] - destmins[2]; } else if ( i == 2 ) { // right side middle[2] -= ( destmaxs[2] - destmins[2] ) / 2.0; VectorMA( eye, destmaxs[0] - 0.5, right, eye ); } else if ( i == 3 ) { // left side VectorMA( eye, -2.0 * ( destmaxs[0] - 0.5 ), right, eye ); } } //end for return qfalse; }
/* ------------------------- NPC_BSDroid_Pain ------------------------- */ void NPC_Probe_Pain(gentity_t *self, gentity_t *attacker, int damage) { float pain_chance; gentity_t *other = attacker; int mod = gPainMOD; VectorCopy( self->NPC->lastPathAngles, self->s.angles ); if ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) // demp2 always messes them up real good { vec3_t endPos; trace_t trace; VectorSet( endPos, self->r.currentOrigin[0], self->r.currentOrigin[1], self->r.currentOrigin[2] - 128 ); trap_Trace( &trace, self->r.currentOrigin, NULL, NULL, endPos, self->s.number, MASK_SOLID ); if ( trace.fraction == 1.0f || mod == MOD_DEMP2 ) // demp2 always does this { /* if (self->client->clientInfo.headModel != 0) { vec3_t origin; VectorCopy(self->r.currentOrigin,origin); origin[2] +=50; // G_PlayEffect( "small_chunks", origin ); G_PlayEffect( "chunks/probehead", origin ); G_PlayEffect( "env/med_explode2", origin ); self->client->clientInfo.headModel = 0; self->client->moveType = MT_RUNJUMP; self->client->ps.gravity = g_gravity->value*.1; } */ if ( (mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT) && other ) { vec3_t dir; NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, dir ); VectorNormalize( dir ); VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity ); self->client->ps.velocity[2] -= 127; } //self->s.powerups |= ( 1 << PW_SHOCKED ); //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; self->client->ps.electrifyTime = level.time + 3000; self->NPC->localState = LSTATE_DROP; } } else { pain_chance = NPC_GetPainChance( self, damage ); if ( random() < pain_chance ) // Spin around in pain? { NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE); } } NPC_Pain( self, attacker, damage ); }
void weapon_railgun_fire (gentity_t *ent) { vec3_t end; trace_t trace; gentity_t *tent; gentity_t *traceEnt; int damage; int i; int hits; int unlinked; int passent; gentity_t *unlinkedEntities[MAX_RAIL_HITS]; damage = 100 * s_quadFactor; VectorMA (muzzle, 8192, forward, end); // trace only against the solids, so the railgun will go through people unlinked = 0; hits = 0; passent = ent->s.number; do { trap_Trace (&trace, muzzle, NULL, NULL, end, passent, MASK_SHOT ); if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) { break; } traceEnt = &g_entities[ trace.entityNum ]; if ( traceEnt->takedamage ) { if( LogAccuracyHit( traceEnt, ent ) ) { hits++; } G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN); } if ( trace.contents & CONTENTS_SOLID ) { break; // we hit something solid enough to stop the beam } // unlink this entity, so the next trace will go past it trap_UnlinkEntity( traceEnt ); unlinkedEntities[unlinked] = traceEnt; unlinked++; } while ( unlinked < MAX_RAIL_HITS ); // link back in any entities we unlinked for ( i = 0 ; i < unlinked ; i++ ) { trap_LinkEntity( unlinkedEntities[i] ); } // the final trace endpos will be the terminal point of the rail trail // snap the endpos to integers to save net bandwidth, but nudged towards the line SnapVectorTowards( trace.endpos, muzzle ); // send railgun beam effect tent = G_TempEntity( trace.endpos, EV_RAILTRAIL ); // set player number for custom colors on the railtrail tent->s.clientNum = ent->s.clientNum; VectorCopy( muzzle, tent->s.origin2 ); // move origin a bit to come closer to the drawn gun muzzle VectorMA( tent->s.origin2, 4, right, tent->s.origin2 ); VectorMA( tent->s.origin2, -1, up, tent->s.origin2 ); // no explosion at end if SURF_NOIMPACT, but still make the trail if ( trace.surfaceFlags & SURF_NOIMPACT ) { tent->s.eventParm = 255; // don't make the explosion at the end } else { tent->s.eventParm = DirToByte( trace.plane.normal ); } tent->s.clientNum = ent->s.clientNum; // give the shooter a reward sound if they have made two railgun hits in a row if ( hits == 0 ) { // complete miss ent->client->accurateCount = 0; } else { // check for "impressive" reward sound ent->client->accurateCount += hits; ent->client->accuracy_hits++; } }
void NPC_BSGM_Attack( void ) { //Don't do anything if we're hurt if ( NPC->painDebounceTime > level.time ) { NPC_UpdateAngles( qtrue, qtrue ); return; } #if 0 //FIXME: if killed enemy, use victory anim if ( NPC->enemy && NPC->enemy->health <= 0 && !NPC->enemy->s.number ) {//my enemy is dead if ( NPC->client->ps.torsoAnim == BOTH_STAND2TO1 ) { if ( NPC->client->ps.torsoTimer <= 500 ) { G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 ); NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsTimer += 500; NPC->client->ps.torsoTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1START ) { if ( NPC->client->ps.torsoTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STARTGESTURE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsTimer += 500; NPC->client->ps.torsoTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STARTGESTURE ) { if ( NPC->client->ps.torsoTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsTimer += 500; NPC->client->ps.torsoTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STOP ) { if ( NPC->client->ps.torsoTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsTimer = -1; NPC->client->ps.torsoTimer = -1; } } else if ( NPC->wait ) { if ( TIMER_Done( NPC, "gloatTime" ) ) { GM_StartGloat(); } else if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->r.currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared { NPCInfo->goalEntity = NPC->enemy; GM_Move(); } else {//got there GM_StartGloat(); } } NPC_FaceEnemy( qtrue ); NPC_UpdateAngles( qtrue, qtrue ); return; } #endif //If we don't have an enemy, just idle if ( NPC_CheckEnemyExt(qfalse) == qfalse || !NPC->enemy ) { NPC->enemy = NULL; NPC_BSGM_Patrol(); return; } enemyLOS4 = enemyCS4 = qfalse; move4 = qtrue; faceEnemy4 = qfalse; shoot4 = qfalse; hitAlly4 = qfalse; VectorClear( impactPos4 ); enemyDist4 = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); //if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 || // NPC->client->ps.torsoAnim == BOTH_ATTACK5 ) if (0) { shoot4 = qfalse; if ( TIMER_Done( NPC, "smackTime" ) && !NPCInfo->blockedDebounceTime ) {//time to smack //recheck enemyDist4 and InFront if ( enemyDist4 < MELEE_DIST_SQUARED && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.3f ) ) { vec3_t smackDir; VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, smackDir ); smackDir[2] += 30; VectorNormalize( smackDir ); //hurt them G_Sound( NPC->enemy, CHAN_AUTO, G_SoundIndex( "sound/weapons/galak/skewerhit.wav" ) ); G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->r.currentOrigin, (g_spskill.integer+1)*Q_irand( 5, 10), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 ) {//smackdown int knockAnim = BOTH_KNOCKDOWN1; if ( BG_CrouchAnim( NPC->enemy->client->ps.legsAnim ) ) {//knockdown from crouch knockAnim = BOTH_KNOCKDOWN4; } //throw them smackDir[2] = 1; VectorNormalize( smackDir ); G_Throw( NPC->enemy, smackDir, 50 ); NPC_SetAnim( NPC->enemy, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } else {//uppercut //throw them G_Throw( NPC->enemy, smackDir, 100 ); //make them backflip NPC_SetAnim( NPC->enemy, SETANIM_BOTH, BOTH_KNOCKDOWN5, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } //done with the damage NPCInfo->blockedDebounceTime = 1; } } } else if ( NPC->lockCount ) //already shooting laser {//sometimes use the laser beam attack, but only after he's taken down our generator shoot4 = qfalse; if ( NPC->lockCount == 1 ) {//charging up if ( TIMER_Done( NPC, "beamDelay" ) ) {//time to start the beam int laserAnim; //if ( Q_irand( 0, 1 ) ) if (1) { laserAnim = BOTH_ATTACK2; } /* else { laserAnim = BOTH_ATTACK7; } */ NPC_SetAnim( NPC, SETANIM_BOTH, laserAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer + Q_irand( 1000, 3000 ) ); //turn on beam effect NPC->lockCount = 2; G_PlayEffectID( G_EffectIndex("galak/trace_beam"), NPC->r.currentOrigin, vec3_origin ); NPC->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); if ( !NPCInfo->coverTarg ) {//for moving looping sound at end of trace NPCInfo->coverTarg = G_Spawn(); if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); NPCInfo->coverTarg->r.svFlags |= SVF_BROADCAST; NPCInfo->coverTarg->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); } } } } else {//in the actual attack now if ( NPC->client->ps.torsoTimer <= 0 ) {//attack done! NPC->lockCount = 0; G_FreeEntity( NPCInfo->coverTarg ); NPC->s.loopSound = 0; #if 0 NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_DROPWEAP2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); #endif TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer ); } else {//attack still going //do the trace and damage trace_t trace; vec3_t end, mins={-3,-3,-3}, maxs={3,3,3}; VectorMA( NPC->client->renderInfo.muzzlePoint, 1024, NPC->client->renderInfo.muzzleDir, end ); trap_Trace( &trace, NPC->client->renderInfo.muzzlePoint, mins, maxs, end, NPC->s.number, MASK_SHOT ); if ( trace.allsolid || trace.startsolid ) {//oops, in a wall if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); } } else {//clear if ( trace.fraction < 1.0f ) {//hit something gentity_t *traceEnt = &g_entities[trace.entityNum]; if ( traceEnt && traceEnt->takedamage ) {//damage it G_SoundAtLoc( trace.endpos, CHAN_AUTO, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); G_Damage( traceEnt, NPC, NPC, NPC->client->renderInfo.muzzleDir, trace.endpos, 10, 0, MOD_UNKNOWN ); } } if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, trace.endpos ); } if ( !Q_irand( 0, 5 ) ) { G_SoundAtLoc( trace.endpos, CHAN_AUTO, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); } } } } } else {//Okay, we're not in a special attack, see if we should switch weapons or start a special attack /* if ( NPC->s.weapon == WP_REPEATER && !(NPCInfo->scriptFlags & SCF_ALT_FIRE)//using rapid-fire && NPC->enemy->s.weapon == WP_SABER //enemy using saber && NPC->client && (NPC->client->ps.saberEventFlags&SEF_DEFLECTED) && !Q_irand( 0, 50 ) ) {//he's deflecting my shots, switch to the laser or the lob fire for a while TIMER_Set( NPC, "noRapid", Q_irand( 2000, 6000 ) ); NPCInfo->scriptFlags |= SCF_ALT_FIRE; NPC->alt_fire = qtrue; if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && (Q_irand( 0, 1 )||enemyDist4 < MAX_LOB_DIST_SQUARED) ) {//shield down, use laser NPC_GM_StartLaser(); } } else*/ if (// !NPC->client->ps.powerups[PW_GALAK_SHIELD] 1 //rwwFIXMEFIXME: just act like the shield is down til the effects and stuff are done && enemyDist4 < MELEE_DIST_SQUARED && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.3f ) && NPC->enemy->localAnimIndex <= 1 )//within 80 and in front {//our shield is down, and enemy within 80, if very close, use melee attack to slap away if ( TIMER_Done( NPC, "attackDelay" ) ) { //animate me int swingAnim = BOTH_ATTACK1; #if 0 if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH ) {//generator down, use random melee swingAnim = Q_irand( BOTH_ATTACK4, BOTH_ATTACK5 );//smackdown or uppercut } else {//always knock-away swingAnim = BOTH_ATTACK5;//uppercut } #endif //FIXME: swing sound NPC_SetAnim( NPC, SETANIM_BOTH, swingAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer + Q_irand( 1000, 3000 ) ); //delay the hurt until the proper point in the anim TIMER_Set( NPC, "smackTime", 600 ); NPCInfo->blockedDebounceTime = 0; //FIXME: say something? } } else if ( !NPC->lockCount && NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && TIMER_Done( NPC, "attackDelay" ) && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.3f ) && ((!Q_irand( 0, 10*(2-g_spskill.integer))&& enemyDist4 > MIN_LOB_DIST_SQUARED&& enemyDist4 < MAX_LOB_DIST_SQUARED) ||(!TIMER_Done( NPC, "noLob" )&&!TIMER_Done( NPC, "noRapid" ))) && NPC->enemy->s.weapon != WP_TURRET ) {//sometimes use the laser beam attack, but only after he's taken down our generator shoot4 = qfalse; NPC_GM_StartLaser(); } else if ( enemyDist4 < MIN_LOB_DIST_SQUARED && (NPC->enemy->s.weapon != WP_TURRET || Q_stricmp( "PAS", NPC->enemy->classname )) && TIMER_Done( NPC, "noRapid" ) )//256 {//enemy within 256 if ( (NPC->client->ps.weapon == WP_REPEATER) && (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//shooting an explosive, but enemy too close, switch to primary fire NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; NPC->alt_fire = qfalse; //FIXME: use weap raise & lower anims NPC_ChangeWeapon( WP_REPEATER ); } } else if ( (enemyDist4 > MAX_LOB_DIST_SQUARED || (NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ))) && TIMER_Done( NPC, "noLob" ) )//448 {//enemy more than 448 away and we are ready to try lob fire again if ( (NPC->client->ps.weapon == WP_REPEATER) && !(NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//enemy far enough away to use lobby explosives NPCInfo->scriptFlags |= SCF_ALT_FIRE; NPC->alt_fire = qtrue; //FIXME: use weap raise & lower anims NPC_ChangeWeapon( WP_REPEATER ); } } } //can we see our target? if ( NPC_ClearLOS4( NPC->enemy ) ) { NPCInfo->enemyLastSeenTime = level.time;//used here for aim debouncing, not always a clear LOS enemyLOS4 = qtrue; if ( NPC->client->ps.weapon == WP_NONE ) { enemyCS4 = qfalse;//not true, but should stop us from firing NPC_AimAdjust( -1 );//adjust aim worse longer we have no weapon } else {//can we shoot our target? if ( ((NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist4 < MIN_LOB_DIST_SQUARED )//256 { enemyCS4 = qfalse;//not true, but should stop us from firing hitAlly4 = qtrue;//us! //FIXME: if too close, run away! } else { int hit = NPC_ShotEntity( NPC->enemy, impactPos4 ); gentity_t *hitEnt = &g_entities[hit]; if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) || ( hitEnt && hitEnt->takedamage ) ) {//can hit enemy or will hit glass or other breakable, so shoot anyway enemyCS4 = qtrue; NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); } else {//Hmm, have to get around this bastard NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam ) {//would hit an ally, don't fire!!! hitAlly4 = qtrue; } else {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire } } } } } else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) ) { int hit; gentity_t *hitEnt; if ( TIMER_Done( NPC, "talkDebounce" ) && !Q_irand( 0, 10 ) ) { if ( NPCInfo->enemyCheckDebounceTime < 8 ) { int speech = -1; switch( NPCInfo->enemyCheckDebounceTime ) { case 0: case 1: case 2: speech = EV_CHASE1 + NPCInfo->enemyCheckDebounceTime; break; case 3: case 4: case 5: speech = EV_COVER1 + NPCInfo->enemyCheckDebounceTime-3; break; case 6: case 7: speech = EV_ESCAPING1 + NPCInfo->enemyCheckDebounceTime-6; break; } NPCInfo->enemyCheckDebounceTime++; if ( speech != -1 ) { G_AddVoiceEvent( NPC, speech, Q_irand( 3000, 5000 ) ); TIMER_Set( NPC, "talkDebounce", Q_irand( 5000, 7000 ) ); } } } NPCInfo->enemyLastSeenTime = level.time; hit = NPC_ShotEntity( NPC->enemy, impactPos4 ); hitEnt = &g_entities[hit]; if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) || ( hitEnt && hitEnt->takedamage ) ) {//can hit enemy or will hit glass or other breakable, so shoot anyway enemyCS4 = qtrue; } else { faceEnemy4 = qtrue; NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy } } if ( enemyLOS4 ) { faceEnemy4 = qtrue; } else { if ( !NPCInfo->goalEntity ) { NPCInfo->goalEntity = NPC->enemy; } if ( NPCInfo->goalEntity == NPC->enemy ) {//for now, always chase the enemy move4 = qtrue; } } if ( enemyCS4 ) { shoot4 = qtrue; //NPCInfo->enemyCheckDebounceTime = level.time;//actually used here as a last actual LOS } else { if ( !NPCInfo->goalEntity ) { NPCInfo->goalEntity = NPC->enemy; } if ( NPCInfo->goalEntity == NPC->enemy ) {//for now, always chase the enemy move4 = qtrue; } } //Check for movement to take care of GM_CheckMoveState(); //See if we should override shooting decision with any special considerations GM_CheckFireState(); if ( NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE) && shoot4 && TIMER_Done( NPC, "attackDelay" ) ) { vec3_t muzzle; vec3_t angles; vec3_t target; vec3_t velocity = {0,0,0}; vec3_t mins = {-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE}, maxs = {REPEATER_ALT_SIZE,REPEATER_ALT_SIZE,REPEATER_ALT_SIZE}; qboolean clearshot; CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); VectorCopy( NPC->enemy->r.currentOrigin, target ); target[0] += flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); target[1] += flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); target[2] += flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); //Find the desired angles clearshot = WP_LobFire( NPC, muzzle, target, mins, maxs, MASK_SHOT|CONTENTS_LIGHTSABER, velocity, qtrue, NPC->s.number, NPC->enemy->s.number, 300, 1100, 1500, qtrue ); if ( VectorCompare( vec3_origin, velocity ) || (!clearshot&&enemyLOS4&&enemyCS4) ) {//no clear lob shot and no lob shot that will hit something breakable if ( enemyLOS4 && enemyCS4 && TIMER_Done( NPC, "noRapid" ) ) {//have a clear straight shot, so switch to primary NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; NPC->alt_fire = qfalse; NPC_ChangeWeapon( WP_REPEATER ); //keep this weap for a bit TIMER_Set( NPC, "noLob", Q_irand( 500, 1000 ) ); } else { shoot4 = qfalse; } } else { vectoangles( velocity, angles ); NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); VectorCopy( velocity, NPC->client->hiddenDir ); NPC->client->hiddenDist = VectorNormalize ( NPC->client->hiddenDir ); } } else if ( faceEnemy4 ) {//face the enemy NPC_FaceEnemy( qtrue ); } if ( !TIMER_Done( NPC, "standTime" ) ) { move4 = qfalse; } if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) {//not supposed to chase my enemies if ( NPCInfo->goalEntity == NPC->enemy ) {//goal is my entity, so don't move move4 = qfalse; } } if ( move4 && !NPC->lockCount ) {//move toward goal if ( NPCInfo->goalEntity /*&& NPC->client->ps.legsAnim != BOTH_ALERT1 && NPC->client->ps.legsAnim != BOTH_ATTACK2 && NPC->client->ps.legsAnim != BOTH_ATTACK4 && NPC->client->ps.legsAnim != BOTH_ATTACK5 && NPC->client->ps.legsAnim != BOTH_ATTACK7*/ ) { move4 = GM_Move(); } else { move4 = qfalse; } } if ( !TIMER_Done( NPC, "flee" ) ) {//running away faceEnemy4 = qfalse; } //FIXME: check scf_face_move_dir here? if ( !faceEnemy4 ) {//we want to face in the dir we're running if ( !move4 ) {//if we haven't moved, we should look in the direction we last looked? VectorCopy( NPC->client->ps.viewangles, NPCInfo->lastPathAngles ); } if ( move4 ) {//don't run away and shoot NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; NPCInfo->desiredPitch = 0; shoot4 = qfalse; } } NPC_UpdateAngles( qtrue, qtrue ); if ( NPCInfo->scriptFlags & SCF_DONT_FIRE ) { shoot4 = qfalse; } if ( NPC->enemy && NPC->enemy->enemy ) { if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER ) {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH) shoot4 = qfalse; } } //FIXME: don't shoot right away! if ( shoot4 ) {//try to shoot if it's time if ( TIMER_Done( NPC, "attackDelay" ) ) { if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here { WeaponThink( qtrue ); } } } //also: if ( NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ) ) {//crush turrets if ( G_BoundsOverlap( NPC->r.absmin, NPC->r.absmax, NPC->enemy->r.absmin, NPC->enemy->r.absmax ) ) {//have to do this test because placed turrets are not solid to NPCs (so they don't obstruct navigation) //if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) if (0) { NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->r.currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN ); } else { G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->r.currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); } } } else if ( NPCInfo->touchedByPlayer != NULL && NPCInfo->touchedByPlayer == NPC->enemy ) {//touched enemy //if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) if (0) {//zap him! vec3_t smackDir; //animate me #if 0 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK6, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); #endif TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer ); TIMER_Set( NPC, "standTime", NPC->client->ps.legsTimer ); //FIXME: debounce this? NPCInfo->touchedByPlayer = NULL; //FIXME: some shield effect? NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, smackDir ); smackDir[2] += 30; VectorNormalize( smackDir ); G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->r.currentOrigin, (g_spskill.integer+1)*Q_irand( 5, 10), DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN ); //throw them G_Throw( NPC->enemy, smackDir, 100 ); //NPC->enemy->s.powerups |= ( 1 << PW_SHOCKED ); if ( NPC->enemy->client ) { // NPC->enemy->client->ps.powerups[PW_SHOCKED] = level.time + 1000; NPC->enemy->client->ps.electrifyTime = level.time + 1000; } //stop any attacks ucmd.buttons = 0; } } if ( NPCInfo->movementSpeech < 3 && NPCInfo->blockedSpeechDebounceTime <= level.time ) { if ( NPC->enemy && NPC->enemy->health > 0 && NPC->enemy->painDebounceTime > level.time ) { if ( NPC->enemy->health < 50 && NPCInfo->movementSpeech == 2 ) { G_AddVoiceEvent( NPC, EV_ANGER2, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 3; } else if ( NPC->enemy->health < 75 && NPCInfo->movementSpeech == 1 ) { G_AddVoiceEvent( NPC, EV_ANGER1, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 2; } else if ( NPC->enemy->health < 100 && NPCInfo->movementSpeech == 0 ) { G_AddVoiceEvent( NPC, EV_ANGER3, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 1; } } } }
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 ); } 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; } // FIXME: Where's the best place for this? if ( ent->item->giTag == PW_BERSERKER ) { trap_SendServerCommand( ( ent - g_entities ), va( "srwc %i", WP_PUNCHY ) ); other->client->pers.cmd.weapon = WP_PUNCHY; other->client->ps.weapon = WP_PUNCHY; } return RESPAWN_POWERUP; }
/** * @brief Move a point towards empty space (away from map geometry). * @param normal Optional direction to move towards. */ void MoveTowardsRoom( vec3_t origin ) { static const vec3_t vecs[ 162 ] = { {0.000000, 0.000000, -1.000000}, {0.723607, -0.525725, -0.447220}, {-0.276388, -0.850649, -0.447220}, {-0.894426, 0.000000, -0.447216}, {-0.276388, 0.850649, -0.447220}, {0.723607, 0.525725, -0.447220}, {0.276388, -0.850649, 0.447220}, {-0.723607, -0.525725, 0.447220}, {-0.723607, 0.525725, 0.447220}, {0.276388, 0.850649, 0.447220}, {0.894426, 0.000000, 0.447216}, {0.000000, 0.000000, 1.000000}, {-0.232822, -0.716563, -0.657519}, {-0.162456, -0.499995, -0.850654}, {-0.077607, -0.238853, -0.967950}, {0.203181, -0.147618, -0.967950}, {0.425323, -0.309011, -0.850654}, {0.609547, -0.442856, -0.657519}, {0.531941, -0.681712, -0.502302}, {0.262869, -0.809012, -0.525738}, {-0.029639, -0.864184, -0.502302}, {0.812729, 0.295238, -0.502301}, {0.850648, 0.000000, -0.525736}, {0.812729, -0.295238, -0.502301}, {0.203181, 0.147618, -0.967950}, {0.425323, 0.309011, -0.850654}, {0.609547, 0.442856, -0.657519}, {-0.753442, 0.000000, -0.657515}, {-0.525730, 0.000000, -0.850652}, {-0.251147, 0.000000, -0.967949}, {-0.483971, -0.716565, -0.502302}, {-0.688189, -0.499997, -0.525736}, {-0.831051, -0.238853, -0.502299}, {-0.232822, 0.716563, -0.657519}, {-0.162456, 0.499995, -0.850654}, {-0.077607, 0.238853, -0.967950}, {-0.831051, 0.238853, -0.502299}, {-0.688189, 0.499997, -0.525736}, {-0.483971, 0.716565, -0.502302}, {-0.029639, 0.864184, -0.502302}, {0.262869, 0.809012, -0.525738}, {0.531941, 0.681712, -0.502302}, {0.956626, -0.147618, 0.251149}, {0.951058, -0.309013, -0.000000}, {0.860698, -0.442858, -0.251151}, {0.860698, 0.442858, -0.251151}, {0.951058, 0.309013, 0.000000}, {0.956626, 0.147618, 0.251149}, {0.155215, -0.955422, 0.251152}, {0.000000, -1.000000, -0.000000}, {-0.155215, -0.955422, -0.251152}, {0.687159, -0.681715, -0.251152}, {0.587786, -0.809017, 0.000000}, {0.436007, -0.864188, 0.251152}, {-0.860698, -0.442858, 0.251151}, {-0.951058, -0.309013, -0.000000}, {-0.956626, -0.147618, -0.251149}, {-0.436007, -0.864188, -0.251152}, {-0.587786, -0.809017, 0.000000}, {-0.687159, -0.681715, 0.251152}, {-0.687159, 0.681715, 0.251152}, {-0.587786, 0.809017, -0.000000}, {-0.436007, 0.864188, -0.251152}, {-0.956626, 0.147618, -0.251149}, {-0.951058, 0.309013, 0.000000}, {-0.860698, 0.442858, 0.251151}, {0.436007, 0.864188, 0.251152}, {0.587786, 0.809017, -0.000000}, {0.687159, 0.681715, -0.251152}, {-0.155215, 0.955422, -0.251152}, {0.000000, 1.000000, 0.000000}, {0.155215, 0.955422, 0.251152}, {0.831051, -0.238853, 0.502299}, {0.688189, -0.499997, 0.525736}, {0.483971, -0.716565, 0.502302}, {0.029639, -0.864184, 0.502302}, {-0.262869, -0.809012, 0.525738}, {-0.531941, -0.681712, 0.502302}, {-0.812729, -0.295238, 0.502301}, {-0.850648, 0.000000, 0.525736}, {-0.812729, 0.295238, 0.502301}, {-0.531941, 0.681712, 0.502302}, {-0.262869, 0.809012, 0.525738}, {0.029639, 0.864184, 0.502302}, {0.483971, 0.716565, 0.502302}, {0.688189, 0.499997, 0.525736}, {0.831051, 0.238853, 0.502299}, {0.077607, -0.238853, 0.967950}, {0.162456, -0.499995, 0.850654}, {0.232822, -0.716563, 0.657519}, {0.753442, 0.000000, 0.657515}, {0.525730, 0.000000, 0.850652}, {0.251147, 0.000000, 0.967949}, {-0.203181, -0.147618, 0.967950}, {-0.425323, -0.309011, 0.850654}, {-0.609547, -0.442856, 0.657519}, {-0.203181, 0.147618, 0.967950}, {-0.425323, 0.309011, 0.850654}, {-0.609547, 0.442856, 0.657519}, {0.077607, 0.238853, 0.967950}, {0.162456, 0.499995, 0.850654}, {0.232822, 0.716563, 0.657519}, {0.052790, -0.688185, -0.723612}, {0.138199, -0.425321, -0.894429}, {0.361805, -0.587779, -0.723611}, {0.670817, 0.162457, -0.723611}, {0.670818, -0.162458, -0.723610}, {0.447211, -0.000001, -0.894428}, {-0.638195, -0.262864, -0.723609}, {-0.361801, -0.262864, -0.894428}, {-0.447211, -0.525729, -0.723610}, {-0.447211, 0.525727, -0.723612}, {-0.361801, 0.262863, -0.894429}, {-0.638195, 0.262863, -0.723609}, {0.361803, 0.587779, -0.723612}, {0.138197, 0.425321, -0.894429}, {0.052789, 0.688186, -0.723611}, {1.000000, 0.000000, 0.000000}, {0.947213, -0.162458, -0.276396}, {0.947213, 0.162458, -0.276396}, {0.309017, -0.951056, 0.000000}, {0.138199, -0.951055, -0.276398}, {0.447216, -0.850648, -0.276398}, {-0.809018, -0.587783, 0.000000}, {-0.861803, -0.425324, -0.276396}, {-0.670819, -0.688191, -0.276397}, {-0.809018, 0.587783, -0.000000}, {-0.670819, 0.688191, -0.276397}, {-0.861803, 0.425324, -0.276396}, {0.309017, 0.951056, -0.000000}, {0.447216, 0.850648, -0.276398}, {0.138199, 0.951055, -0.276398}, {0.670820, -0.688190, 0.276396}, {0.809019, -0.587783, -0.000002}, {0.861804, -0.425323, 0.276394}, {-0.447216, -0.850648, 0.276397}, {-0.309017, -0.951056, -0.000001}, {-0.138199, -0.951055, 0.276397}, {-0.947213, 0.162458, 0.276396}, {-1.000000, -0.000000, 0.000001}, {-0.947213, -0.162458, 0.276397}, {-0.138199, 0.951055, 0.276397}, {-0.309016, 0.951057, -0.000000}, {-0.447215, 0.850649, 0.276397}, {0.861804, 0.425322, 0.276396}, {0.809019, 0.587782, 0.000000}, {0.670821, 0.688189, 0.276397}, {0.361800, -0.262863, 0.894429}, {0.447209, -0.525728, 0.723612}, {0.638194, -0.262864, 0.723610}, {-0.138197, -0.425319, 0.894430}, {-0.361804, -0.587778, 0.723612}, {-0.052790, -0.688185, 0.723612}, {-0.447210, 0.000000, 0.894429}, {-0.670817, 0.162457, 0.723611}, {-0.670817, -0.162457, 0.723611}, {-0.138197, 0.425319, 0.894430}, {-0.052790, 0.688185, 0.723612}, {-0.361804, 0.587778, 0.723612}, {0.361800, 0.262863, 0.894429}, {0.638194, 0.262864, 0.723610}, {0.447209, 0.525728, 0.723612} }; const int numvecs = sizeof( vecs ) / sizeof( vecs[ 0 ] ); int i; vec3_t accumulator, end; trace_t tr; VectorClear( accumulator ); for ( i = 0; i < numvecs; i++ ) { VectorMA( origin, 500, vecs[ i ], end ); trap_Trace( &tr, origin, nullptr, nullptr, end, 0, MASK_SOLID, 0 ); VectorAdd( accumulator, tr.endpos, accumulator ); } VectorScale( accumulator, 1.0 / numvecs, accumulator ); VectorCopy( accumulator, origin ); }
/* ================ 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; VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS ); VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS ); 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; if ( ent->spawnflags & 1 ) { // suspended G_SetOrigin( ent, ent->s.origin ); } else { // drop to floor 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; } // 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; } if ( ent->item->giType == IT_HOLDABLE ) { if ( ( ent->item->giTag == HI_BAMBAM ) && ( g_gametype.integer != GT_CTF ) ) { return; } else if ( ( ent->item->giTag == HI_BOOMIES ) && ( ( g_gametype.integer != GT_CTF ) && ( g_gametype.integer != GT_BALLOON ) ) ) { return; } } trap_LinkEntity (ent); }
/** * @brief Perform an approximate trace to find a taggable entity. * @param team Team the caller belongs to. * @param refreshTagged Refresh all already tagged entities's tags and exclude these entities from further consideration. */ gentity_t *TagTrace( const vec3_t begin, const vec3_t end, int skip, int mask, team_t team, bool refreshTagged ) { tagtrace_ent_t list[ MAX_GENTITIES ]; int i, count = 0; gentity_t *ent, *reticleEnt = nullptr; vec3_t seg, delta; float dot; VectorSubtract( end, begin, seg ); // Do a trace for bounding boxes under the reticle first, they are prefered { trace_t tr; trap_Trace( &tr, begin, nullptr, nullptr, end, skip, mask, 0 ); if ( EntityTaggable( tr.entityNum, team, true ) ) { reticleEnt = g_entities + tr.entityNum; if ( !refreshTagged || !CheckRefreshTag( reticleEnt, team ) ) return reticleEnt; } } for( i = 0; i < level.num_entities; i++ ) { ent = g_entities + i; if( ent == reticleEnt ) continue; if( !ent->inuse ) continue; if( !EntityTaggable( i, team, true ) ) continue; VectorSubtract( ent->r.currentOrigin, begin, delta ); dot = DotProduct( seg, delta ) / VectorLength( seg ) / VectorLength( delta ); if( dot < 0.9 ) continue; if( !trap_InPVS( ent->r.currentOrigin, begin ) ) continue; // LOS { trace_t tr; trap_Trace( &tr, begin, nullptr, nullptr, ent->r.currentOrigin, skip, mask, 0 ); if( tr.entityNum != i ) continue; } if( refreshTagged && CheckRefreshTag( ent, team ) ) continue; list[ count ].ent = ent; list[ count++ ].dot = dot; } if( !count ) return nullptr; qsort( list, count, sizeof( tagtrace_ent_t ), TagTrace_EntCmp ); return list[ 0 ].ent; }
void weapon_railgun_fire(Gentity *ent) { Vec3 end; #ifdef MISSIONPACK Vec3 impactpoint, bouncedir; #endif Trace trace; Gentity *tent; Gentity *traceEnt; int damage; int i; int hits; int unlinked; int passent; Gentity *unlinkedEntities[MAX_RAIL_HITS]; damage = 100 * s_quadFactor; saddv3 (muzzle, 8192, forward, end); /* trace only against the solids, so the railgun will go through people */ unlinked = 0; hits = 0; passent = ent->s.number; do { trap_Trace (&trace, muzzle, NULL, NULL, end, passent, MASK_SHOT); if(trace.entityNum >= ENTITYNUM_MAX_NORMAL) break; traceEnt = &g_entities[ trace.entityNum ]; if(traceEnt->takedamage){ if(LogAccuracyHit(traceEnt, ent)) hits++; G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN); } if(trace.contents & CONTENTS_SOLID) break; /* we hit something solid enough to stop the beam */ /* unlink this entity, so the next trace will go past it */ trap_UnlinkEntity(traceEnt); unlinkedEntities[unlinked] = traceEnt; unlinked++; } while(unlinked < MAX_RAIL_HITS); /* link back in any entities we unlinked */ for(i = 0; i < unlinked; i++) trap_LinkEntity(unlinkedEntities[i]); /* the final trace endpos will be the terminal point of the rail trail */ /* snap the endpos to integers to save net bandwidth, but nudged towards the line */ snapv3Towards(trace.endpos, muzzle); /* send railgun beam effect */ tent = G_TempEntity(trace.endpos, EV_RAILTRAIL); /* set player number for custom colors on the railtrail */ tent->s.clientNum = ent->s.clientNum; copyv3(muzzle, tent->s.origin2); /* move origin a bit to come closer to the drawn gun muzzle */ saddv3(tent->s.origin2, 4, right, tent->s.origin2); saddv3(tent->s.origin2, -1, up, tent->s.origin2); /* no explosion at end if SURF_NOIMPACT, but still make the trail */ if(trace.surfaceFlags & SURF_NOIMPACT) tent->s.eventParm = 255; /* don't make the explosion at the end */ else tent->s.eventParm = DirToByte(trace.plane.normal); tent->s.clientNum = ent->s.clientNum; /* give the shooter a reward sound if they have made two railgun hits in a row */ if(hits == 0) /* complete miss */ ent->client->accurateCount = 0; else{ /* check for "impressive" reward sound */ ent->client->accurateCount += hits; if(ent->client->accurateCount >= 2){ ent->client->accurateCount -= 2; ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++; /* add the sprite over the player's head */ ent->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP); ent->client->ps.eFlags |= EF_AWARD_IMPRESSIVE; ent->client->rewardTime = level.time + REWARD_SPRITE_TIME; } ent->client->accuracy_hits++; } }
qboolean IsLegShot( gentity_t *targ, vec3_t dir, vec3_t point, int mod ) { float height; float theight; gentity_t *leg; if (!(targ->client)) return qfalse; if (targ->health <= 0) return qfalse; if(!point) { return qfalse; } if(!IsHeadShotWeapon(mod)) { return qfalse; } leg = G_BuildLeg( targ ); if( leg ) { gentity_t *traceEnt; vec3_t start, end; trace_t tr; // trace another shot see if we hit the legs VectorCopy( point, start ); VectorMA( start, 64, dir, end ); trap_Trace( &tr, start, NULL, NULL, end, targ->s.number, MASK_SHOT ); traceEnt = &g_entities[ tr.entityNum ]; if( g_debugBullets.integer >= 3 ) { // show hit player head bb gentity_t *tent; vec3_t b1, b2; VectorCopy( leg->r.currentOrigin, b1 ); VectorCopy( leg->r.currentOrigin, b2 ); VectorAdd( b1, leg->r.mins, b1 ); VectorAdd( b2, leg->r.maxs, b2 ); tent = G_TempEntity( b1, EV_RAILTRAIL ); VectorCopy( b2, tent->s.origin2 ); tent->s.dmgFlags = 1; // show headshot trace // end the headshot trace at the head box if it hits if( tr.fraction != 1 ) { VectorMA( start, (tr.fraction * 64), dir, end ); } tent = G_TempEntity( start, EV_RAILTRAIL ); VectorCopy( end, tent->s.origin2 ); tent->s.dmgFlags = 0; } G_FreeEntity( leg ); if( traceEnt == leg ) { return qtrue; } } else { height = point[2] - targ->r.absmin[2]; theight = targ->r.absmax[2] - targ->r.absmin[2]; if(height < (theight * 0.4f)) { return qtrue; } } return qfalse; }
/** * @brief Drop weapon. */ void G_DropWeapon(gentity_t *ent, weapon_t weapon) { vec3_t angles, velocity, org, offset, mins, maxs; gclient_t *client = ent->client; gentity_t *ent2; gitem_t *item; trace_t tr; if (!IS_VALID_WEAPON(weapon)) { return; } item = BG_FindItemForWeapon(weapon); VectorCopy(client->ps.viewangles, angles); // clamp pitch if (angles[PITCH] < -30) { angles[PITCH] = -30; } else if (angles[PITCH] > 30) { angles[PITCH] = 30; } AngleVectors(angles, velocity, NULL, NULL); VectorScale(velocity, 64, offset); offset[2] += client->ps.viewheight / 2.f; VectorScale(velocity, 75, velocity); velocity[2] += 50 + random() * 35; VectorAdd(client->ps.origin, offset, org); VectorSet(mins, -ITEM_RADIUS, -ITEM_RADIUS, 0); VectorSet(maxs, ITEM_RADIUS, ITEM_RADIUS, 2 * ITEM_RADIUS); trap_Trace(&tr, client->ps.origin, mins, maxs, org, ent->s.number, MASK_SOLID); VectorCopy(tr.endpos, org); ent2 = LaunchItem(item, org, velocity, client->ps.clientNum); COM_BitClear(client->ps.weapons, weapon); // FIXME: do a switch if (weapon == WP_KAR98) { COM_BitClear(client->ps.weapons, WP_GPG40); } else if (weapon == WP_CARBINE) { COM_BitClear(client->ps.weapons, WP_M7); } else if (weapon == WP_FG42) { COM_BitClear(client->ps.weapons, WP_FG42SCOPE); } else if (weapon == WP_K43) { COM_BitClear(client->ps.weapons, WP_K43_SCOPE); } else if (weapon == WP_GARAND) { COM_BitClear(client->ps.weapons, WP_GARAND_SCOPE); } else if (weapon == WP_MORTAR) { COM_BitClear(client->ps.weapons, WP_MORTAR_SET); } else if (weapon == WP_MORTAR2) { COM_BitClear(client->ps.weapons, WP_MORTAR2_SET); } else if (weapon == WP_MOBILE_MG42) { COM_BitClear(client->ps.weapons, WP_MOBILE_MG42_SET); } else if (weapon == WP_MOBILE_BROWNING) { COM_BitClear(client->ps.weapons, WP_MOBILE_BROWNING_SET); } // Clear out empty weapon, change to next best weapon G_AddEvent(ent, EV_WEAPONSWITCHED, 0); if (weapon == client->ps.weapon) { client->ps.weapon = 0; } if (IS_MORTAR_WEAPON_SET(weapon)) { ent2->count = client->ps.ammo[BG_FindAmmoForWeapon(weapon)] + client->ps.ammoclip[BG_FindClipForWeapon(weapon)]; } else { ent2->count = client->ps.ammoclip[BG_FindClipForWeapon(weapon)]; } if (weapon == WP_KAR98 || weapon == WP_CARBINE) { ent2->delay = client->ps.ammo[BG_FindAmmoForWeapon(weaponTable[weapon].weapAlts)]; } else { ent2->delay = 0; } // ent2->item->quantity = client->ps.ammoclip[BG_FindClipForWeapon(weapon)]; // um, modifying an item is not a good idea client->ps.ammoclip[BG_FindClipForWeapon(weapon)] = 0; #ifdef FEATURE_OMNIBOT Bot_Event_RemoveWeapon(client->ps.clientNum, Bot_WeaponGameToBot(weapon)); #endif }
/* void CalcEntitySpot ( gentity_t *ent, spot_t spot, vec3_t point ) Added: Uses shootAngles if a NPC has them */ void CalcEntitySpot ( const gentity_t *ent, const spot_t spot, vec3_t point ) { vec3_t forward, up, right; vec3_t start, end; trace_t tr; if ( !ent ) { return; } switch ( spot ) { case SPOT_ORIGIN: if(VectorCompare(ent->r.currentOrigin, vec3_origin)) {//brush VectorSubtract(ent->r.absmax, ent->r.absmin, point);//size VectorMA(ent->r.absmin, 0.5, point, point); } else { VectorCopy ( ent->r.currentOrigin, point ); } break; case SPOT_CHEST: case SPOT_HEAD: if ( ent->client && VectorLengthSquared( ent->client->renderInfo.eyePoint ) /*&& (ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD)*/ ) {//Actual tag_head eyespot! //FIXME: Stasis aliens may have a problem here... VectorCopy( ent->client->renderInfo.eyePoint, point ); if ( ent->client->NPC_class == CLASS_ATST ) {//adjust up some point[2] += 28;//magic number :) } if ( ent->NPC ) {//always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards point[0] = ent->r.currentOrigin[0]; point[1] = ent->r.currentOrigin[1]; } } else { VectorCopy ( ent->r.currentOrigin, point ); if ( ent->client ) { point[2] += ent->client->ps.viewheight; } } if ( spot == SPOT_CHEST && ent->client ) { if ( ent->client->NPC_class != CLASS_ATST ) {//adjust up some point[2] -= ent->r.maxs[2]*0.2f; } } break; case SPOT_HEAD_LEAN: if ( ent->client && VectorLengthSquared( ent->client->renderInfo.eyePoint ) /*&& (ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD*/ ) {//Actual tag_head eyespot! //FIXME: Stasis aliens may have a problem here... VectorCopy( ent->client->renderInfo.eyePoint, point ); if ( ent->client->NPC_class == CLASS_ATST ) {//adjust up some point[2] += 28;//magic number :) } if ( ent->NPC ) {//always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards point[0] = ent->r.currentOrigin[0]; point[1] = ent->r.currentOrigin[1]; } //NOTE: automatically takes leaning into account! } else { VectorCopy ( ent->r.currentOrigin, point ); if ( ent->client ) { point[2] += ent->client->ps.viewheight; } } break; case SPOT_LEGS: VectorCopy ( ent->r.currentOrigin, point ); point[2] += (ent->r.mins[2] * 0.5); break; case SPOT_WEAPON: if( ent->NPC && !VectorCompare( ent->NPC->shootAngles, vec3_origin ) && !VectorCompare( ent->NPC->shootAngles, ent->client->ps.viewangles )) { AngleVectors( ent->NPC->shootAngles, forward, right, up ); } else { AngleVectors( ent->client->ps.viewangles, forward, right, up ); } CalcMuzzlePoint( (gentity_t*)ent, forward, right, up, point ); //NOTE: automatically takes leaning into account! break; case SPOT_GROUND: // if entity is on the ground, just use it's absmin if ( ent->s.groundEntityNum != -1 ) { VectorCopy( ent->r.currentOrigin, point ); point[2] = ent->r.absmin[2]; break; } // if it is reasonably close to the ground, give the point underneath of it VectorCopy( ent->r.currentOrigin, start ); start[2] = ent->r.absmin[2]; VectorCopy( start, end ); end[2] -= 64; trap_Trace( &tr, start, ent->r.mins, ent->r.maxs, end, ent->s.number, MASK_PLAYERSOLID ); if ( tr.fraction < 1.0 ) { VectorCopy( tr.endpos, point); break; } // otherwise just use the origin VectorCopy( ent->r.currentOrigin, point ); break; default: VectorCopy ( ent->r.currentOrigin, point ); break; } }
//----------------------------------------------------- static qboolean VEH_TurretFindEnemies( Vehicle_t *pVeh, gentity_t *parent, turretStats_t *turretStats, int turretNum, int curMuzzle ) //----------------------------------------------------- { qboolean found = qfalse; int i, count; float bestDist = turretStats->fAIRange * turretStats->fAIRange; float enemyDist; vec3_t enemyDir, org, org2; qboolean foundClient = qfalse; gentity_t *entity_list[MAX_GENTITIES], *target, *bestTarget = NULL; WP_CalcVehMuzzle( parent, curMuzzle ); VectorCopy( pVeh->m_vMuzzlePos[curMuzzle], org2 ); count = G_RadiusList( org2, turretStats->fAIRange, parent, qtrue, entity_list ); for ( i = 0; i < count; i++ ) { trace_t tr; target = entity_list[i]; if ( target == parent || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET )) { continue; } 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&&parent->targetname&&Q_stricmp(target->NPC_targetname,parent->targetname)!=0) )//not in invicible bbrush, but can only be broken by an NPC that is not me { if ( target->s.weapon == WP_TURRET && target->classname && Q_strncmp( "misc_turret", target->classname, 11 ) == 0 ) {//these guys we want to shoot at } else { continue; } } //else: we will shoot at bbrushes! } else if ( target->client->sess.sessionTeam == TEAM_SPECTATOR ) { continue; } if ( target == ((gentity_t*)pVeh->m_pPilot) || target->r.ownerNum == parent->s.number ) {//don't get angry at my pilot or passengers? continue; } if ( parent->client && parent->client->sess.sessionTeam ) { if ( target->client ) { if ( target->client->sess.sessionTeam == parent->client->sess.sessionTeam ) { // A bot/client/NPC we don't want to shoot continue; } } else if ( target->teamnodmg == parent->client->sess.sessionTeam ) {//some other entity that's allied with us continue; } } if ( !trap_InPVS( org2, target->r.currentOrigin )) { continue; } VectorCopy( target->r.currentOrigin, org ); trap_Trace( &tr, org2, NULL, NULL, org, parent->s.number, MASK_SHOT ); if ( tr.entityNum == target->s.number || (!tr.allsolid && !tr.startsolid && tr.fraction == 1.0 ) ) { // Only acquire if have a clear shot, Is it in range and closer than our best? VectorSubtract( target->r.currentOrigin, org2, enemyDir ); enemyDist = VectorLengthSquared( enemyDir ); if ( enemyDist < bestDist || (target->client && !foundClient))// all things equal, keep current { bestTarget = target; bestDist = enemyDist; found = qtrue; if ( target->client ) {//prefer clients over non-clients foundClient = qtrue; } } } } if ( found ) { pVeh->turretStatus[turretNum].enemyEntNum = bestTarget->s.number; } return found; }
qboolean CanDamage (gentity_t *targ, vec3_t origin) { vec3_t dest; trace_t tr; vec3_t midpoint; vec3_t offsetmins = { -16.f, -16.f, -16.f }; vec3_t offsetmaxs = { 16.f, 16.f, 16.f }; // use the midpoint of the bounds instead of the origin, because // bmodels may have their origin is 0,0,0 // Gordon: well, um, just check then... if(targ->r.currentOrigin[0] || targ->r.currentOrigin[1] || targ->r.currentOrigin[2]) { VectorCopy( targ->r.currentOrigin, midpoint ); if( targ->s.eType == ET_MOVER ) { midpoint[2] += 32; } } else { VectorAdd (targ->r.absmin, targ->r.absmax, midpoint); VectorScale (midpoint, 0.5, midpoint); } // G_RailTrail( origin, dest ); trap_Trace ( &tr, origin, vec3_origin, vec3_origin, midpoint, ENTITYNUM_NONE, MASK_CAN_DAMAGE ); if (tr.fraction == 1.0) return qtrue; if(&g_entities[tr.entityNum] == targ) return qtrue; if( targ->client ) { VectorCopy( targ->client->ps.mins, offsetmins ); VectorCopy( targ->client->ps.maxs, offsetmaxs ); } // this should probably check in the plane of projection, // rather than in world coordinate VectorCopy (midpoint, dest); dest[0] += offsetmaxs[0]; dest[1] += offsetmaxs[1]; dest[2] += offsetmaxs[2]; trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_CAN_DAMAGE ); if( tr.fraction == 1 || &g_entities[tr.entityNum] == targ ) { return qtrue; } VectorCopy (midpoint, dest); dest[0] += offsetmaxs[0]; dest[1] += offsetmins[1]; dest[2] += offsetmaxs[2]; trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_CAN_DAMAGE ); if( tr.fraction == 1 || &g_entities[tr.entityNum] == targ ) { return qtrue; } VectorCopy (midpoint, dest); dest[0] += offsetmins[0]; dest[1] += offsetmaxs[1]; dest[2] += offsetmaxs[2]; trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_CAN_DAMAGE ); if( tr.fraction == 1 || &g_entities[tr.entityNum] == targ ) { return qtrue; } VectorCopy (midpoint, dest); dest[0] += offsetmins[0]; dest[1] += offsetmins[1]; dest[2] += offsetmaxs[2]; trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_CAN_DAMAGE ); if( tr.fraction == 1 || &g_entities[tr.entityNum] == targ ) { return qtrue; } // ========================= VectorCopy (midpoint, dest); dest[0] += offsetmaxs[0]; dest[1] += offsetmaxs[1]; dest[2] += offsetmins[2]; trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_CAN_DAMAGE ); if( tr.fraction == 1 || &g_entities[tr.entityNum] == targ ) { return qtrue; } VectorCopy (midpoint, dest); dest[0] += offsetmaxs[0]; dest[1] += offsetmins[1]; dest[2] += offsetmins[2]; trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_CAN_DAMAGE ); if( tr.fraction == 1 || &g_entities[tr.entityNum] == targ ) { return qtrue; } VectorCopy (midpoint, dest); dest[0] += offsetmins[0]; dest[1] += offsetmaxs[1]; dest[2] += offsetmins[2]; trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_CAN_DAMAGE ); if( tr.fraction == 1 || &g_entities[tr.entityNum] == targ ) { return qtrue; } VectorCopy (midpoint, dest); dest[0] += offsetmins[0]; dest[1] += offsetmins[2]; dest[2] += offsetmins[2]; trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_CAN_DAMAGE ); if( tr.fraction == 1 || &g_entities[tr.entityNum] == targ ) { return qtrue; } return qfalse; }
//----------------------------------------------------- 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 ) { G_SetEnemy( self, bestTarget ); if ( VALIDSTRING( self->target2 )) { G_UseTargets2( self, self, self->target2 ); } } return found; }
/* ============ etpro_RadiusDamage mutation of G_RadiusDamage which lets us selectively damage only clients or only non clients ============ */ qboolean etpro_RadiusDamage( vec3_t origin, gentity_t *inflictor, gentity_t *attacker, float damage, float radius, gentity_t *ignore, int mod, RadiusScope scope ) { float points, dist; gentity_t *ent; int entityList[MAX_GENTITIES]; int numListedEntities; vec3_t mins, maxs; vec3_t v; vec3_t dir; int i, e; qboolean hitClient = qfalse; float boxradius; vec3_t dest; trace_t tr; vec3_t midpoint; int flags = DAMAGE_RADIUS; if( mod == MOD_SATCHEL || mod == MOD_LANDMINE ) { flags |= DAMAGE_HALF_KNOCKBACK; } if( radius < 1 ) { radius = 1; } boxradius = 1.41421356 * radius; // radius * sqrt(2) for bounding box enlargement -- // bounding box was checking against radius / sqrt(2) if collision is along box plane for( i = 0 ; i < 3 ; i++ ) { mins[i] = origin[i] - boxradius; maxs[i] = origin[i] + boxradius; } numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( e = 0 ; e < numListedEntities ; e++ ) { ent = &g_entities[entityList[ e ]]; if( ent == ignore ) { continue; } if( !ent->takedamage && ( !ent->dmgparent || !ent->dmgparent->takedamage )) { continue; } switch (scope) { default: case RADIUS_SCOPE_ANY: break; case RADIUS_SCOPE_CLIENTS: if (!ent->client && ent->s.eType != ET_CORPSE ) continue; break; case RADIUS_SCOPE_NOCLIENTS: if (ent->client) continue; break; } if( ent->waterlevel == 3 && mod == MOD_POISON_GAS) { continue; } G_AdjustedDamageVec( ent, origin, v ); dist = VectorLength( v ); if ( dist >= radius ) { continue; } points = damage * ( 1.0 - dist / radius ); if( CanDamage( ent, origin ) ) { if( ent->dmgparent ) { ent = ent->dmgparent; } if( AccuracyHit( ent, attacker ) ) { hitClient = qtrue; } VectorSubtract (ent->r.currentOrigin, origin, dir); // push the center of mass higher than the origin so players // get knocked into the air more dir[2] += 24; G_Damage( ent, inflictor, attacker, dir, origin, (int)points, flags, mod ); } else { VectorAdd( ent->r.absmin, ent->r.absmax, midpoint ); VectorScale( midpoint, 0.5, midpoint ); VectorCopy( midpoint, dest ); trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); if( tr.fraction < 1.0 ) { VectorSubtract( dest, origin, dest ); dist = VectorLength( dest ); if( dist < radius * 0.2f ) { // closer than 1/4 dist if( ent->dmgparent ) { ent = ent->dmgparent; } if( AccuracyHit( ent, attacker ) ) { hitClient = qtrue; } VectorSubtract (ent->r.currentOrigin, origin, dir); dir[2] += 24; G_Damage( ent, inflictor, attacker, dir, origin, (int)(points*0.1f), flags, mod ); } } } } return hitClient; }
gentity_t *SpawnObelisk( vec3_t origin, int team, int spawnflags) { trace_t tr; vec3_t dest; gentity_t *ent; ent = G_Spawn(); VectorCopy( origin, ent->s.origin ); VectorCopy( origin, ent->s.pos.trBase ); VectorCopy( origin, ent->r.currentOrigin ); VectorSet( ent->r.mins, -15, -15, 0 ); VectorSet( ent->r.maxs, 15, 15, 87 ); ent->s.eType = ET_GENERAL; ent->flags = FL_NO_KNOCKBACK; if( g_gametype.integer == GT_OBELISK ) { ent->r.contents = CONTENTS_SOLID; ent->takedamage = qtrue; ent->health = g_obeliskHealth.integer; ent->die = ObeliskDie; ent->pain = ObeliskPain; ent->think = ObeliskRegen; ent->nextthink = level.time + g_obeliskRegenPeriod.integer * 1000; } if( g_gametype.integer == GT_HARVESTER ) { ent->r.contents = CONTENTS_TRIGGER; ent->touch = ObeliskTouch; } if ( spawnflags & 1 ) { // suspended G_SetOrigin( ent, ent->s.origin ); } else { // mappers like to put them exactly on the floor, but being coplanar // will sometimes show up as starting in solid, so lif it up one pixel ent->s.origin[2] += 1; // drop to floor 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 ) { ent->s.origin[2] -= 1; G_Printf( "SpawnObelisk: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin) ); ent->s.groundEntityNum = ENTITYNUM_NONE; G_SetOrigin( ent, ent->s.origin ); } else { // allow to ride movers ent->s.groundEntityNum = tr.entityNum; G_SetOrigin( ent, tr.endpos ); } } ent->spawnflags = team; trap_LinkEntity( ent ); return ent; }
/* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame on fast clients. If "g_synchronousClients 1" is set, this will be called exactly once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real( gentity_t *ent ) { gclient_t *client; pmove_t pm; int oldEventSequence; int msec; usercmd_t *ucmd; client = ent->client; // don't think if the client is not yet connected (and thus not yet spawned in) if( client->pers.connected != CON_CONNECTED ) return; // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; // sanity check the command time to prevent speedup cheating if( ucmd->serverTime > level.time + 200 ) { ucmd->serverTime = level.time + 200; // G_Printf("serverTime <<<<<\n" ); } if( ucmd->serverTime < level.time - 1000 ) { ucmd->serverTime = level.time - 1000; // G_Printf("serverTime >>>>>\n" ); } msec = ucmd->serverTime - client->ps.commandTime; // following others may result in bad times, but we still want // to check for follow toggles if( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) return; if( msec > 200 ) msec = 200; if( pmove_msec.integer < 8 ) trap_Cvar_Set( "pmove_msec", "8" ); else if( pmove_msec.integer > 33 ) trap_Cvar_Set( "pmove_msec", "33" ); if( pmove_fixed.integer || client->pers.pmoveFixed ) { ucmd->serverTime = ( ( ucmd->serverTime + pmove_msec.integer - 1 ) / pmove_msec.integer ) * pmove_msec.integer; //if (ucmd->serverTime - client->ps.commandTime <= 0) // return; } // // check for exiting intermission // if( level.intermissiontime ) { ClientIntermissionThink( client ); return; } // spectators don't do much if( client->sess.sessionTeam == TEAM_SPECTATOR ) { if( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) return; SpectatorThink( ent, ucmd ); return; } G_UpdatePTRConnection( client ); // check for inactivity timer, but never drop the local client of a non-dedicated server if( !ClientInactivityTimer( client ) ) return; if( client->noclip ) client->ps.pm_type = PM_NOCLIP; else if( client->ps.stats[ STAT_HEALTH ] <= 0 ) client->ps.pm_type = PM_DEAD; else if( client->ps.stats[ STAT_STATE ] & SS_INFESTING || client->ps.stats[ STAT_STATE ] & SS_HOVELING ) client->ps.pm_type = PM_FREEZE; else if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED || client->ps.stats[ STAT_STATE ] & SS_GRABBED ) client->ps.pm_type = PM_GRABBED; else if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) client->ps.pm_type = PM_JETPACK; else client->ps.pm_type = PM_NORMAL; if( client->ps.stats[ STAT_STATE ] & SS_GRABBED && client->grabExpiryTime < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_GRABBED; if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED && client->lastLockTime + 5000 < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_BLOBLOCKED; if( client->ps.stats[ STAT_STATE ] & SS_SLOWLOCKED && client->lastLockTime + ABUILDER_BLOB_TIME < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_SLOWLOCKED; client->ps.stats[ STAT_BOOSTTIME ] = level.time - client->lastBoostedTime; if( client->ps.stats[ STAT_STATE ] & SS_BOOSTED && client->lastBoostedTime + BOOST_TIME < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTED; if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED && client->lastPoisonCloudedTime + LEVEL1_PCLOUD_TIME < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED; if( client->ps.stats[ STAT_STATE ] & SS_POISONED && client->lastPoisonTime + ALIEN_POISON_TIME < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; client->ps.gravity = g_gravity.value; if( BG_InventoryContainsUpgrade( UP_MEDKIT, client->ps.stats ) && BG_UpgradeIsActive( UP_MEDKIT, client->ps.stats ) ) { //if currently using a medkit or have no need for a medkit now if( client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE || ( client->ps.stats[ STAT_HEALTH ] == client->ps.stats[ STAT_MAX_HEALTH ] && !( client->ps.stats[ STAT_STATE ] & SS_POISONED ) ) ) { BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); } else if( client->ps.stats[ STAT_HEALTH ] > 0 ) { //remove anti toxin BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); BG_RemoveUpgradeFromInventory( UP_MEDKIT, client->ps.stats ); client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; client->poisonImmunityTime = level.time + MEDKIT_POISON_IMMUNITY_TIME; client->ps.stats[ STAT_STATE ] |= SS_MEDKIT_ACTIVE; client->lastMedKitTime = level.time; client->medKitHealthToRestore = client->ps.stats[ STAT_MAX_HEALTH ] - client->ps.stats[ STAT_HEALTH ]; client->medKitIncrementTime = level.time + ( MEDKIT_STARTUP_TIME / MEDKIT_STARTUP_SPEED ); G_AddEvent( ent, EV_MEDKIT_USED, 0 ); } } if( BG_InventoryContainsUpgrade( UP_GRENADE, client->ps.stats ) && BG_UpgradeIsActive( UP_GRENADE, client->ps.stats ) ) { int lastWeapon = ent->s.weapon; //remove grenade BG_DeactivateUpgrade( UP_GRENADE, client->ps.stats ); BG_RemoveUpgradeFromInventory( UP_GRENADE, client->ps.stats ); //M-M-M-M-MONSTER HACK ent->s.weapon = WP_GRENADE; FireWeapon( ent ); ent->s.weapon = lastWeapon; } // set speed client->ps.speed = g_speed.value * BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] ); if( client->lastCreepSlowTime + CREEP_TIMEOUT < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_CREEPSLOWED; //randomly disable the jet pack if damaged if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) { if( ent->lastDamageTime + JETPACK_DISABLE_TIME > level.time ) { if( random( ) > JETPACK_DISABLE_CHANCE ) client->ps.pm_type = PM_NORMAL; } //switch jetpack off if no reactor if( !level.reactorPresent ) BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats ); } // set up for pmove oldEventSequence = client->ps.eventSequence; memset( &pm, 0, sizeof( pm ) ); if( !( ucmd->buttons & BUTTON_TALK ) ) //&& client->ps.weaponTime <= 0 ) //TA: erk more server load { switch( client->ps.weapon ) { case WP_ALEVEL0: if( client->ps.weaponTime <= 0 ) pm.autoWeaponHit[ client->ps.weapon ] = CheckVenomAttack( ent ); break; case WP_ALEVEL1: case WP_ALEVEL1_UPG: CheckGrabAttack( ent ); break; case WP_ALEVEL3: case WP_ALEVEL3_UPG: if( client->ps.weaponTime <= 0 ) pm.autoWeaponHit[ client->ps.weapon ] = CheckPounceAttack( ent ); break; default: break; } } if( ent->flags & FL_FORCE_GESTURE ) { ent->flags &= ~FL_FORCE_GESTURE; ent->client->pers.cmd.buttons |= BUTTON_GESTURE; } pm.ps = &client->ps; pm.cmd = *ucmd; if( pm.ps->pm_type == PM_DEAD ) pm.tracemask = MASK_PLAYERSOLID; // & ~CONTENTS_BODY; if( pm.ps->stats[ STAT_STATE ] & SS_INFESTING || pm.ps->stats[ STAT_STATE ] & SS_HOVELING ) pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; else pm.tracemask = MASK_PLAYERSOLID; pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; pm.noFootsteps = (qboolean)0; pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; pm.pmove_msec = pmove_msec.integer; VectorCopy( client->ps.origin, client->oldOrigin ); // moved from after Pmove -- potentially the cause of // future triggering bugs if( !ent->client->noclip ) G_TouchTriggers( ent ); Pmove( &pm ); // save results of pmove if( ent->client->ps.eventSequence != oldEventSequence ) ent->eventTime = level.time; if( g_smoothClients.integer ) BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); else BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); SendPendingPredictableEvents( &ent->client->ps ); if( !( ent->client->ps.eFlags & EF_FIRING ) ) client->fireHeld = qfalse; // for grapple if( !( ent->client->ps.eFlags & EF_FIRING2 ) ) client->fire2Held = qfalse; // use the snapped origin for linking so it matches client predicted versions VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); VectorCopy( pm.mins, ent->r.mins ); VectorCopy( pm.maxs, ent->r.maxs ); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; // execute client events ClientEvents( ent, oldEventSequence ); // link entity now, after any personal teleporters have been used trap_LinkEntity( ent ); // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); VectorCopy( ent->client->ps.origin, ent->s.origin ); // touch other objects ClientImpacts( ent, &pm ); // save results of triggers and client events if( ent->client->ps.eventSequence != oldEventSequence ) ent->eventTime = level.time; // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons |= client->buttons & ~client->oldbuttons; if( ( client->buttons & BUTTON_GETFLAG ) && !( client->oldbuttons & BUTTON_GETFLAG ) && client->ps.stats[ STAT_HEALTH ] > 0 ) { trace_t trace; vec3_t view, point; gentity_t *traceEnt; if( client->ps.stats[ STAT_STATE ] & SS_HOVELING ) { gentity_t *hovel = client->hovel; //only let the player out if there is room if( !AHovel_Blocked( hovel, ent, qtrue ) ) { //prevent lerping client->ps.eFlags ^= EF_TELEPORT_BIT; client->ps.eFlags &= ~EF_NODRAW; //client leaves hovel client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING; //hovel is empty G_setBuildableAnim( hovel, BANIM_ATTACK2, qfalse ); hovel->active = qfalse; } else { //exit is blocked G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL_BLOCKED ); } } else { #define USE_OBJECT_RANGE 64 int entityList[ MAX_GENTITIES ]; vec3_t range = { USE_OBJECT_RANGE, USE_OBJECT_RANGE, USE_OBJECT_RANGE }; vec3_t mins, maxs; int i, num; //TA: look for object infront of player AngleVectors( client->ps.viewangles, view, NULL, NULL ); VectorMA( client->ps.origin, USE_OBJECT_RANGE, view, point ); trap_Trace( &trace, client->ps.origin, NULL, NULL, point, ent->s.number, MASK_SHOT ); traceEnt = &g_entities[ trace.entityNum ]; if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use ) traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context else { //no entity in front of player - do a small area search VectorAdd( client->ps.origin, range, maxs ); VectorSubtract( client->ps.origin, range, mins ); num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { traceEnt = &g_entities[ entityList[ i ] ]; if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use ) { traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context break; } } if( i == num && client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { if( BG_UpgradeClassAvailable( &client->ps ) ) { //no nearby objects and alien - show class menu G_TriggerMenu( ent->client->ps.clientNum, MN_A_INFEST ); } else { //flash frags G_AddEvent( ent, EV_ALIEN_EVOLVE_FAILED, 0 ); } } } } } // check for respawning if( client->ps.stats[ STAT_HEALTH ] <= 0 ) { // wait for the attack button to be pressed if( level.time > client->respawnTime ) { // forcerespawn is to prevent users from waiting out powerups if( g_forcerespawn.integer > 0 && ( level.time - client->respawnTime ) > 0 ) { respawn( ent ); return; } // pressing attack or use is the normal respawn method if( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { respawn( ent ); } } return; } if( level.framenum > client->retriggerArmouryMenu && client->retriggerArmouryMenu ) { G_TriggerMenu( client->ps.clientNum, MN_H_ARMOURY ); client->retriggerArmouryMenu = 0; } // Give clients some credit periodically if( ent->client->lastKillTime + FREEKILL_PERIOD < level.time ) { if( g_suddenDeathTime.integer && ( level.time - level.startTime >= g_suddenDeathTime.integer * 60000 ) ) { //gotta love logic like this eh? } else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) G_AddCreditToClient( ent->client, FREEKILL_ALIEN, qtrue ); else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) G_AddCreditToClient( ent->client, FREEKILL_HUMAN, qtrue ); ent->client->lastKillTime = level.time; } // perform once-a-second actions ClientTimerActions( ent, msec ); if( ent->suicideTime > 0 && ent->suicideTime < level.time ) { ent->flags &= ~FL_GODMODE; ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0; player_die( ent, ent, ent, 100000, MOD_SUICIDE ); ent->suicideTime = 0; } }
/** * @brief Run item. */ 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 || ent->s.pos.trType == TR_GRAVITY_PAUSED) // check think function { G_RunThink(ent); return; } if (ent->s.pos.trType == TR_LINEAR && (!ent->clipmask && !ent->r.contents)) { // check think function G_RunThink(ent); return; } // get current position BG_EvaluateTrajectory(&ent->s.pos, level.time, origin, qfalse, ent->s.effect2Time); // trace a line from the previous position to the current position if (ent->clipmask) { mask = ent->clipmask; } else { mask = MASK_SOLID; } trap_Trace(&tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->r.ownerNum, mask); if (ent->isProp && ent->takedamage) { G_RunItemProp(ent, origin); } 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_ReturnFlag(ent); } else { G_FreeEntity(ent); } return; } G_BounceItem(ent, &tr); }
void NPC_BSGM_Default( void ) { if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) { WeaponThink( qtrue ); } if ( NPC->client->ps.stats[STAT_ARMOR] <= 0 ) {//armor gone // if ( !NPCInfo->investigateDebounceTime ) if (0) {//start regenerating the armor NPC_SetSurfaceOnOff( NPC, "torso_shield", TURN_OFF ); NPC->flags &= ~FL_SHIELDED;//no more reflections VectorSet( NPC->r.mins, -20, -20, -24 ); VectorSet( NPC->r.maxs, 20, 20, 64 ); NPC->client->ps.crouchheight = NPC->client->ps.standheight = 64; if ( NPC->locationDamage[HL_GENERIC1] < GENERATOR_HEALTH ) {//still have the generator bolt-on if ( NPCInfo->investigateCount < 12 ) { NPCInfo->investigateCount++; } NPCInfo->investigateDebounceTime = level.time + (NPCInfo->investigateCount * 5000); } } else if ( NPCInfo->investigateDebounceTime < level.time ) {//armor regenerated, turn shield back on //do a trace and make sure we can turn this back on? trace_t tr; trap_Trace( &tr, NPC->r.currentOrigin, shieldMins, shieldMaxs, NPC->r.currentOrigin, NPC->s.number, NPC->clipmask ); if ( !tr.startsolid ) { VectorCopy( shieldMins, NPC->r.mins ); VectorCopy( shieldMaxs, NPC->r.maxs ); NPC->client->ps.crouchheight = NPC->client->ps.standheight = shieldMaxs[2]; NPC->client->ps.stats[STAT_ARMOR] = GALAK_SHIELD_HEALTH; NPCInfo->investigateDebounceTime = 0; NPC->flags |= FL_SHIELDED;//reflect normal shots // NPC->fx_time = level.time; NPC_SetSurfaceOnOff( NPC, "torso_shield", TURN_ON ); } } } /* if ( NPC->client->ps.stats[STAT_ARMOR] > 0 ) {//armor present NPC->client->ps.powerups[PW_GALAK_SHIELD] = Q3_INFINITE;//temp, for effect NPC_SetSurfaceOnOff( NPC, "torso_shield", TURN_ON ); } else { NPC_SetSurfaceOnOff( NPC, "torso_shield", TURN_OFF ); } */ //rwwFIXMEFIXME: Allow this stuff, and again, going to have to let the client know about it. //Maybe a surface-off bitflag of some sort in the entity state? if( !NPC->enemy ) {//don't have an enemy, look for one NPC_BSGM_Patrol(); } else //if ( NPC->enemy ) {//have an enemy NPC_BSGM_Attack(); } }