static gentity_t *FireLcannonHelper( gentity_t *self, vec3_t start, vec3_t dir, int damage, int radius, int speed ) { // TODO: Tidy up this and lcannonFire gentity_t *m; int nextthink; float charge; // explode in front of player when overcharged if ( damage == LCANNON_DAMAGE ) { nextthink = level.time; } else { nextthink = level.time + 10000; } if ( self->s.generic1 == WPM_PRIMARY ) { m = G_SpawnMissile( MIS_LCANNON, self, start, dir, NULL, G_ExplodeMissile, nextthink ); // some values are set in the code m->damage = damage; m->splashDamage = damage / 2; m->splashRadius = radius; VectorScale( dir, speed, m->s.pos.trDelta ); SnapVector( m->s.pos.trDelta ); // save net bandwidth // pass the missile charge through charge = ( float )( damage - LCANNON_SECONDARY_DAMAGE ) / LCANNON_DAMAGE; m->s.torsoAnim = charge * 255; if ( m->s.torsoAnim < 0 ) { m->s.torsoAnim = 0; } } else { m = G_SpawnMissile( MIS_LCANNON2, self, start, dir, NULL, G_ExplodeMissile, nextthink ); } return m; }
static void FireHive( gentity_t *self ) { vec3_t origin; gentity_t *m; // fire from the hive tip, not the center VectorMA( muzzle, self->r.maxs[ 2 ], self->s.origin2, origin ); m = G_SpawnMissile( MIS_HIVE, self, origin, forward, self->target, HiveMissileThink, level.time + HIVE_DIR_CHANGE_PERIOD ); m->timestamp = level.time + HIVE_LIFETIME; }
static void FirebombMissileThink( gentity_t *self ) { gentity_t *neighbor, *m; int subMissileNum; vec3_t dir, upwards = { 0.0f, 0.0f, 1.0f }; // ignite alien buildables in range neighbor = NULL; while ( ( neighbor = G_IterateEntitiesWithinRadius( neighbor, self->s.origin, FIREBOMB_IGNITE_RANGE ) ) ) { if ( neighbor->s.eType == ET_BUILDABLE && neighbor->buildableTeam == TEAM_ALIENS && G_LineOfSight( self, neighbor ) ) { G_IgniteBuildable( neighbor, self->parent ); } } // set floor below on fire (assumes the firebomb lays on the floor!) G_SpawnFire( self->s.origin, upwards, self->parent ); // spam fire for ( subMissileNum = 0; subMissileNum < FIREBOMB_SUBMISSILE_COUNT; subMissileNum++ ) { dir[ 0 ] = ( rand() / ( float )RAND_MAX ) - 0.5f; dir[ 1 ] = ( rand() / ( float )RAND_MAX ) - 0.5f; dir[ 2 ] = ( rand() / ( float )RAND_MAX ) * 0.5f; VectorNormalize( dir ); // the submissile's parent is the attacker m = G_SpawnMissile( MIS_FIREBOMB_SUB, self->parent, self->s.origin, dir, NULL, G_FreeEntity, level.time + 10000 ); // randomize missile speed VectorScale( m->s.pos.trDelta, ( rand() / ( float )RAND_MAX ) + 0.5f, m->s.pos.trDelta ); } // explode G_ExplodeMissile( self ); }
void FireFirebomb( gentity_t *self ) { G_SpawnMissile( MIS_FIREBOMB, self, muzzle, forward, NULL, FirebombMissileThink, level.time + FIREBOMB_TIMER ); }
static void FireGrenade( gentity_t *self ) { G_SpawnMissile( MIS_GRENADE, self, muzzle, forward, NULL, G_ExplodeMissile, level.time + 5000 ); }
static void FireFlamer( gentity_t *self ) { G_SpawnMissile( MIS_FLAMER, self, muzzle, forward, NULL, G_FreeEntity, level.time + FLAMER_LIFETIME ); }
static void FirePrifle( gentity_t *self ) { G_SpawnMissile( MIS_PRIFLE, self, muzzle, forward, NULL, G_ExplodeMissile, level.time + 10000 ); }
static void FireBlaster( gentity_t *self ) { G_SpawnMissile( MIS_BLASTER, self, muzzle, forward, NULL, G_ExplodeMissile, level.time + 10000 ); }
static void FireLockblob( gentity_t *self ) { G_SpawnMissile( MIS_LOCKBLOB, self, muzzle, forward, NULL, G_ExplodeMissile, level.time + 15000 ); }
static void FireBounceball( gentity_t *self ) { G_SpawnMissile( MIS_BOUNCEBALL, self, muzzle, forward, NULL, G_ExplodeMissile, level.time + 3000 ); }
static void FireRocket( gentity_t *self ) { G_SpawnMissile( MIS_ROCKET, self, muzzle, forward, self->target, RocketThink, level.time + ROCKET_TURN_PERIOD )->timestamp = level.time + ROCKET_LIFETIME; }
static void FireSlowblob( gentity_t *self ) { G_SpawnMissile( MIS_SLOWBLOB, self, muzzle, forward, nullptr, G_ExplodeMissile, level.time + 15000 ); }
bool SpikerComponent::Fire() { gentity_t *self = entity.oldEnt; // Check if still resting. if (restUntil > level.time) { logger.Verbose("Spiker #%i wanted to fire but wasn't ready.", entity.oldEnt->s.number); return false; } else { logger.Verbose("Spiker #%i is firing!", entity.oldEnt->s.number); } // Play shooting animation. G_SetBuildableAnim(self, BANIM_ATTACK1, false); GetBuildableComponent().ProtectAnimation(5000); // TODO: Add a particle effect. //G_AddEvent(self, EV_ALIEN_SPIKER, DirToByte(self->s.origin2)); // Calculate total perimeter of all spike rows to allow for a more even spike distribution. // A "row" is a group of missile launch directions with a common base altitude (angle measured // from the Spiker's horizon to its zenith) which is slightly adjusted for each new missile in // the row (at most halfway to the base altitude of a neighbouring row). float totalPerimeter = 0.0f; for (int row = 0; row < MISSILEROWS; row++) { float rowAltitude = (((float)row + 0.5f) * M_PI_2) / (float)MISSILEROWS; float rowPerimeter = 2.0f * M_PI * cos(rowAltitude); totalPerimeter += rowPerimeter; } // TODO: Use new vector library. vec3_t dir, zenith, rotAxis; // As rotation axis for setting the altitude, any vector perpendicular to the zenith works. VectorCopy(self->s.origin2, zenith); PerpendicularVector(rotAxis, zenith); // Distribute and launch missiles. for (int row = 0; row < MISSILEROWS; row++) { // Set the base altitude and get the perimeter for the current row. float rowAltitude = (((float)row + 0.5f) * M_PI_2) / (float)MISSILEROWS; float rowPerimeter = 2.0f * M_PI * cos(rowAltitude); // Attempt to distribute spikes with equal expected angular distance on all rows. int spikes = (int)round(((float)MISSILES * rowPerimeter) / totalPerimeter); // Launch missiles in the current row. for (int spike = 0; spike < spikes; spike++) { float spikeAltitude = rowAltitude + (0.5f * crandom() * M_PI_2 / (float)MISSILEROWS); float spikeAzimuth = 2.0f * M_PI * (((float)spike + 0.5f * crandom()) / (float)spikes); // Set launch direction altitude. RotatePointAroundVector(dir, rotAxis, zenith, RAD2DEG(M_PI_2 - spikeAltitude)); // Set launch direction azimuth. RotatePointAroundVector(dir, zenith, dir, RAD2DEG(spikeAzimuth)); // Trace in the shooting direction and do not shoot spikes that are likely to harm // friendly entities. bool fire = SafeToShoot(Vec3::Load(dir)); logger.Debug("Spiker #%d %s: Row %d/%d: Spike %2d/%2d: " "( Alt %2.0f°, Az %3.0f° → %.2f, %.2f, %.2f )", self->s.number, fire ? "fires" : "skips", row + 1, MISSILEROWS, spike + 1, spikes, RAD2DEG(spikeAltitude), RAD2DEG(spikeAzimuth), dir[0], dir[1], dir[2]); if (!fire) { continue; } G_SpawnMissile( MIS_SPIKER, self, self->s.origin, dir, nullptr, G_FreeEntity, level.time + (int)(1000.0f * SPIKE_RANGE / (float)BG_Missile(MIS_SPIKER)->speed)); } } restUntil = level.time + COOLDOWN; RegisterSlowThinker(); return true; }