// TODO: Check if we can just check for HealthComponent. static AIValue_t goalDead( gentity_t *self, const AIValue_t* ) { bool dead = false; botTarget_t *goal = &self->botMind->goal; if ( !BotTargetIsEntity( *goal ) ) { dead = true; } else if ( BotGetTargetTeam( *goal ) == TEAM_NONE ) { dead = true; } else if ( !G_Alive( self->botMind->goal.ent ) ) { dead = true; } else if ( goal->ent->client && goal->ent->client->sess.spectatorState != SPECTATOR_NOT ) { dead = true; } else if ( goal->ent->s.eType == entityType_t::ET_BUILDABLE && goal->ent->buildableTeam == self->client->pers.team && !goal->ent->powered ) { dead = true; } return AIBoxInt( dead ); }
/* ================ Target tracking for the hive missile. ================ */ static void HiveMissileThink( gentity_t *self ) { vec3_t dir; trace_t tr; gentity_t *ent; int i; float d, nearest; if ( level.time > self->timestamp ) // swarm lifetime exceeded { VectorCopy( self->r.currentOrigin, self->s.pos.trBase ); self->s.pos.trType = TR_STATIONARY; self->s.pos.trTime = level.time; self->think = G_ExplodeMissile; self->nextthink = level.time + 50; self->parent->active = false; //allow the parent to start again return; } nearest = DistanceSquared( self->r.currentOrigin, self->target->r.currentOrigin ); //find the closest human for ( i = 0; i < MAX_CLIENTS; i++ ) { ent = &g_entities[ i ]; if ( !ent->inuse ) continue; if ( ent->flags & FL_NOTARGET ) continue; if ( ent->client && G_Alive( ent ) && ent->client->pers.team == TEAM_HUMANS && nearest > ( d = DistanceSquared( ent->r.currentOrigin, self->r.currentOrigin ) ) ) { trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs, ent->r.currentOrigin, self->r.ownerNum, self->clipmask, 0 ); if ( tr.entityNum != ENTITYNUM_WORLD ) { nearest = d; self->target = ent; } } } VectorSubtract( self->target->r.currentOrigin, self->r.currentOrigin, dir ); VectorNormalize( dir ); //change direction towards the player VectorScale( dir, HIVE_SPEED, self->s.pos.trDelta ); SnapVector( self->s.pos.trDelta ); // save net bandwidth VectorCopy( self->r.currentOrigin, self->s.pos.trBase ); self->s.pos.trTime = level.time; self->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD; }
static void FindZapChainTargets( zap_t *zap ) { gentity_t *ent = zap->targets[ 0 ]; // the source int entityList[ MAX_GENTITIES ]; vec3_t range; vec3_t mins, maxs; int i, num; gentity_t *enemy; trace_t tr; float distance; VectorSet(range, LEVEL2_AREAZAP_CHAIN_RANGE, LEVEL2_AREAZAP_CHAIN_RANGE, LEVEL2_AREAZAP_CHAIN_RANGE); VectorAdd( ent->s.origin, range, maxs ); VectorSubtract( ent->s.origin, range, mins ); num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for ( i = 0; i < num; i++ ) { enemy = &g_entities[ entityList[ i ] ]; // don't chain to self; noclippers can be listed, don't chain to them either if ( enemy == ent || ( enemy->client && enemy->client->noclip ) ) { continue; } distance = Distance( ent->s.origin, enemy->s.origin ); if ( ( ( enemy->client && enemy->client->pers.team == TEAM_HUMANS ) || ( enemy->s.eType == ET_BUILDABLE && BG_Buildable( enemy->s.modelindex )->team == TEAM_HUMANS ) ) && G_Alive( enemy ) && distance <= LEVEL2_AREAZAP_CHAIN_RANGE ) { // world-LOS check: trace against the world, ignoring other BODY entities trap_Trace( &tr, ent->s.origin, nullptr, nullptr, enemy->s.origin, ent->s.number, CONTENTS_SOLID, 0 ); if ( tr.entityNum == ENTITYNUM_NONE ) { zap->targets[ zap->numTargets ] = enemy; zap->distances[ zap->numTargets ] = distance; if ( ++zap->numTargets >= LEVEL2_AREAZAP_MAX_TARGETS ) { return; } } } } }
void G_ChargeAttack( gentity_t *self, gentity_t *victim ) { int damage; int i; vec3_t forward; if ( !self->client || self->client->ps.stats[ STAT_MISC ] <= 0 || !( self->client->ps.stats[ STAT_STATE ] & SS_CHARGING ) || self->client->ps.weaponTime ) { return; } if ( !G_Alive( victim ) ) { return; } VectorSubtract( victim->s.origin, self->s.origin, forward ); VectorNormalize( forward ); // For buildables, track the last MAX_TRAMPLE_BUILDABLES_TRACKED buildables // hit, and do not do damage if the current buildable is in that list // in order to prevent dancing over stuff to kill it very quickly if ( !victim->client ) { for ( i = 0; i < MAX_TRAMPLE_BUILDABLES_TRACKED; i++ ) { if ( self->client->trampleBuildablesHit[ i ] == victim - g_entities ) { return; } } self->client->trampleBuildablesHit[ self->client->trampleBuildablesHitPos++ % MAX_TRAMPLE_BUILDABLES_TRACKED ] = victim - g_entities; } damage = LEVEL4_TRAMPLE_DMG * self->client->ps.stats[ STAT_MISC ] / LEVEL4_TRAMPLE_DURATION; victim->entity->Damage((float)damage, self, Vec3::Load(victim->s.origin), Vec3::Load(forward), DAMAGE_NO_LOCDAMAGE, MOD_LEVEL4_TRAMPLE); SendMeleeHitEvent( self, victim, nullptr ); self->client->ps.weaponTime += LEVEL4_TRAMPLE_REPEAT; }
bool G_CheckPounceAttack( gentity_t *self ) { trace_t tr; gentity_t *traceEnt; int damage, timeMax, pounceRange, payload; if ( self->client->pmext.pouncePayload <= 0 ) { return false; } // In case the goon lands on his target, he gets one shot after landing payload = self->client->pmext.pouncePayload; if ( !( self->client->ps.pm_flags & PMF_CHARGE ) ) { self->client->pmext.pouncePayload = 0; } // Calculate muzzle point AngleVectors( self->client->ps.viewangles, forward, right, up ); G_CalcMuzzlePoint( self, forward, right, up, muzzle ); // Trace from muzzle to see what we hit pounceRange = self->client->ps.weapon == WP_ALEVEL3 ? LEVEL3_POUNCE_RANGE : LEVEL3_POUNCE_UPG_RANGE; G_WideTrace( &tr, self, pounceRange, LEVEL3_POUNCE_WIDTH, LEVEL3_POUNCE_WIDTH, &traceEnt ); if ( !G_Alive( traceEnt ) ) { return false; } timeMax = self->client->ps.weapon == WP_ALEVEL3 ? LEVEL3_POUNCE_TIME : LEVEL3_POUNCE_TIME_UPG; damage = payload * LEVEL3_POUNCE_DMG / timeMax; self->client->pmext.pouncePayload = 0; traceEnt->entity->Damage((float)damage, self, Vec3::Load(tr.endpos), Vec3::Load(forward), DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE); SendMeleeHitEvent( self, traceEnt, &tr ); return true; }
static void FirePainsaw( gentity_t *self ) { trace_t tr; gentity_t *target; G_WideTrace( &tr, self, PAINSAW_RANGE, PAINSAW_WIDTH, PAINSAW_HEIGHT, &target ); if ( !G_Alive( target ) ) { return; } // not really a "ranged" weapon, but this is still the right call SendRangedHitEvent( self, target, &tr ); target->entity->Damage((float)PAINSAW_DAMAGE, self, Vec3::Load(tr.endpos), Vec3::Load(forward), 0, (meansOfDeath_t)MOD_PAINSAW); }
static gentity_t *FireMelee( gentity_t *self, float range, float width, float height, int damage, meansOfDeath_t mod ) { trace_t tr; gentity_t *traceEnt; G_WideTrace( &tr, self, range, width, height, &traceEnt ); if ( !G_Alive( traceEnt ) ) { return nullptr; } traceEnt->entity->Damage((float)damage, self, Vec3::Load(tr.endpos), Vec3::Load(forward), 0, (meansOfDeath_t)mod); SendMeleeHitEvent( self, traceEnt, &tr ); return traceEnt; }
bool G_CheckVenomAttack( gentity_t *self ) { trace_t tr; gentity_t *traceEnt; if ( self->client->ps.weaponTime ) { return false; } // Calculate muzzle point AngleVectors( self->client->ps.viewangles, forward, right, up ); G_CalcMuzzlePoint( self, forward, right, up, muzzle ); G_WideTrace( &tr, self, LEVEL0_BITE_RANGE, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH, &traceEnt ); if ( !G_Alive( traceEnt ) || G_OnSameTeam( self, traceEnt ) ) { return false; } // only allow bites to work against buildables in construction if ( traceEnt->s.eType == ET_BUILDABLE && traceEnt->spawned ) { return false; } traceEnt->entity->Damage((float)LEVEL0_BITE_DMG, self, Vec3::Load(tr.endpos), Vec3::Load(forward), 0, (meansOfDeath_t)MOD_LEVEL0_BITE); SendMeleeHitEvent( self, traceEnt, &tr ); self->client->ps.weaponTime += LEVEL0_BITE_REPEAT; return true; }
static void MissileImpact( gentity_t *ent, trace_t *trace ) { int dirAsByte, impactFlags; const missileAttributes_t *ma = BG_Missile( ent->s.modelindex ); gentity_t *hitEnt = &g_entities[ trace->entityNum ]; gentity_t *attacker = &g_entities[ ent->r.ownerNum ]; // Returns whether damage and hit effects should be done and played. std::function<int(gentity_t*, trace_t*, gentity_t*)> impactFunc; // Check for bounce. if ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) && !HasComponents<HealthComponent>(*hitEnt->entity) ) { BounceMissile( ent, trace ); if ( !( ent->s.eFlags & EF_NO_BOUNCE_SOUND ) ) { G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 ); } return; } // Call missile specific impact functions. switch( ent->s.modelindex ) { case MIS_GRENADE: impactFunc = ImpactGrenade; break; case MIS_FIREBOMB: impactFunc = ImpactGrenade; break; case MIS_FLAMER: impactFunc = ImpactFlamer; break; case MIS_FIREBOMB_SUB: impactFunc = ImpactFirebombSub; break; case MIS_LOCKBLOB: impactFunc = ImpactLockblock; break; case MIS_SLOWBLOB: impactFunc = ImpactSlowblob; break; case MIS_HIVE: impactFunc = ImpactHive; break; default: impactFunc = DefaultImpactFunc; break; } impactFlags = impactFunc( ent, trace, hitEnt ); // Deal impact damage. if ( !( impactFlags & MIF_NO_DAMAGE ) ) { if ( ent->damage && G_Alive( hitEnt ) ) { vec3_t dir; BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, dir ); if ( VectorNormalize( dir ) == 0 ) { dir[ 2 ] = 1; // stepped on a grenade } int dflags = 0; if ( !ma->doLocationalDamage ) dflags |= DAMAGE_NO_LOCDAMAGE; if ( ma->doKnockback ) dflags |= DAMAGE_KNOCKBACK; hitEnt->entity->Damage(ent->damage * MissileTimeDmgMod(ent), attacker, Vec3::Load(trace->endpos), Vec3::Load(dir), dflags, (meansOfDeath_t)ent->methodOfDeath); } // splash damage (doesn't apply to person directly hit) if ( ent->splashDamage ) { G_RadiusDamage( trace->endpos, ent->parent, ent->splashDamage * MissileTimeSplashDmgMod( ent ), ent->splashRadius, hitEnt, ( ma->doKnockback ? DAMAGE_KNOCKBACK : 0 ), ent->splashMethodOfDeath ); } } // Play hit effects and remove the missile. if ( !( impactFlags & MIF_NO_EFFECT ) ) { // Use either the trajectory direction or the surface normal for the hit event. if ( ma->impactFlightDirection ) { vec3_t trajDir; BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, trajDir ); VectorNormalize( trajDir ); dirAsByte = DirToByte( trajDir ); } else { dirAsByte = DirToByte( trace->plane.normal ); } // Add hit event. if ( HasComponents<HealthComponent>(*hitEnt->entity) ) { G_AddEvent( ent, EV_MISSILE_HIT_ENTITY, dirAsByte ); ent->s.otherEntityNum = hitEnt->s.number; } else if ( trace->surfaceFlags & SURF_METAL ) { G_AddEvent( ent, EV_MISSILE_HIT_METAL, dirAsByte ); } else { G_AddEvent( ent, EV_MISSILE_HIT_ENVIRONMENT, dirAsByte ); } ent->freeAfterEvent = true; // HACK: Change over to a general entity at the point of impact. ent->s.eType = ET_GENERAL; // Prevent map models from appearing at impact point. ent->s.modelindex = 0; // Save net bandwith. G_SnapVectorTowards( trace->endpos, ent->s.pos.trBase ); G_SetOrigin( ent, trace->endpos ); trap_LinkEntity( ent ); } // If no impact happened, check if we should continue or free ourselves. else if ( !( impactFlags & MIF_NO_FREE ) ) { G_FreeEntity( ent ); } }
bool G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage, float radius, gentity_t *ignore, int dflags, int mod, team_t testHit ) { 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; bool hitSomething = false; if ( radius < 1 ) { radius = 1; } for ( i = 0; i < 3; i++ ) { mins[ i ] = origin[ i ] - radius; maxs[ i ] = origin[ i ] + radius; } numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for ( e = 0; e < numListedEntities; e++ ) { ent = &g_entities[ entityList[ e ] ]; if ( ent == ignore ) { continue; } // find the distance from the edge of the bounding box for ( i = 0; i < 3; i++ ) { if ( origin[ i ] < ent->r.absmin[ i ] ) { v[ i ] = ent->r.absmin[ i ] - origin[ i ]; } else if ( origin[ i ] > ent->r.absmax[ i ] ) { v[ i ] = origin[ i ] - ent->r.absmax[ i ]; } else { v[ i ] = 0; } } dist = VectorLength( v ); if ( dist >= radius ) { continue; } points = damage * ( 1.0 - dist / radius ); if ( G_CanDamage( ent, origin ) ) { if ( testHit == TEAM_NONE ) { 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; VectorNormalize( dir ); hitSomething = ent->entity->Damage(points, attacker, Vec3::Load(origin), Vec3::Load(dir), (DAMAGE_NO_LOCDAMAGE | dflags), (meansOfDeath_t)mod); } else if ( G_Team( ent ) == testHit && G_Alive( ent ) ) { return true; } } } return hitSomething; }