void AlienBuildableComponent::HandleDie(gentity_t* killer, meansOfDeath_t meansOfDeath) { entity.oldEnt->powered = false; // Warn if in main base and there's an overmind. gentity_t *om; if ((om = G_ActiveOvermind()) && om != entity.oldEnt && level.time > om->warnTimer && G_InsideBase(entity.oldEnt, true) && G_IsWarnableMOD(meansOfDeath)) { om->warnTimer = level.time + ATTACKWARN_NEARBY_PERIOD; G_BroadcastEvent(EV_WARN_ATTACK, 0, TEAM_ALIENS); Beacon::NewArea(BCT_DEFEND, entity.oldEnt->s.origin, entity.oldEnt->buildableTeam); } // Set blast timer. int blastDelay = 0; if (entity.oldEnt->spawned && GetBuildableComponent().GetHealthComponent().Health() / GetBuildableComponent().GetHealthComponent().MaxHealth() > -1.0f) { blastDelay += GetBlastDelay(); } alienBuildableLogger.Debug("Alien buildable dies, will blast in %i ms.", blastDelay); GetBuildableComponent().SetState(BuildableComponent::PRE_BLAST); GetBuildableComponent().REGISTER_THINKER(Blast, ThinkingComponent::SCHEDULER_BEFORE, blastDelay); }
void AlienBuildableComponent::HandleDamage(float amount, gentity_t* source, Util::optional<Vec3> location, Util::optional<Vec3> direction, int flags, meansOfDeath_t meansOfDeath) { if (GetBuildableComponent().GetState() != BuildableComponent::CONSTRUCTED) return; // TODO: Move animation code to BuildableComponent. G_SetBuildableAnim(entity.oldEnt, BANIM_PAIN1, false); }
// TODO: Move this to the client side. void AlienBuildableComponent::CreepRecede(int timeDelta) { alienBuildableLogger.Debug("Starting creep recede."); G_AddEvent(entity.oldEnt, EV_BUILD_DESTROY, 0); if (entity.oldEnt->spawned) { entity.oldEnt->s.time = -level.time; } else { entity.oldEnt->s.time = -(level.time - (int)( (float)CREEP_SCALEDOWN_TIME * (1.0f - ((float)(level.time - entity.oldEnt->creationTime) / (float)BG_Buildable(entity.oldEnt->s.modelindex)->buildTime))) ); } // Remove buildable when done. GetBuildableComponent().REGISTER_THINKER(Remove, ThinkingComponent::SCHEDULER_AFTER, CREEP_SCALEDOWN_TIME); GetBuildableComponent().GetThinkingComponent().UnregisterActiveThinker(); }
void AlienBuildableComponent::Blast(int timeDelta) { float splashDamage = (float)entity.oldEnt->splashDamage; float splashRadius = (float)entity.oldEnt->splashRadius; meansOfDeath_t splashMOD = (meansOfDeath_t)entity.oldEnt->splashMethodOfDeath; // Damage close humans. Utility::AntiHumanRadiusDamage(entity, splashDamage, splashRadius, splashMOD); // Reward attackers. G_RewardAttackers(entity.oldEnt); // Stop collisions, add blast event and update buildable state. entity.oldEnt->r.contents = 0; trap_LinkEntity(entity.oldEnt); G_AddEvent(entity.oldEnt, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte(entity.oldEnt->s.origin2)); GetBuildableComponent().SetState(BuildableComponent::POST_BLAST); // Makes entity invisible. // Start creep recede with a brief delay. GetBuildableComponent().REGISTER_THINKER(CreepRecede, ThinkingComponent::SCHEDULER_AVERAGE, 500); GetBuildableComponent().GetThinkingComponent().UnregisterActiveThinker(); }
AlienBuildableComponent::AlienBuildableComponent(Entity& entity, BuildableComponent& r_BuildableComponent, IgnitableComponent& r_IgnitableComponent) : AlienBuildableComponentBase(entity, r_BuildableComponent, r_IgnitableComponent) { GetBuildableComponent().REGISTER_THINKER(Think, ThinkingComponent::SCHEDULER_AVERAGE, 500); }
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; }