void IgnitableComponent::ConsiderSpread(int timeDelta) {
	if (!onFire) return;
	if (level.time < spreadAt) return;

	fireLogger.Notice("Trying to spread.");

	ForEntities<IgnitableComponent>([&](Entity &other, IgnitableComponent &ignitable){
		if (&other == &entity) return;

		// Don't re-ignite.
		if (ignitable.onFire) return;

		// TODO: Use LocationComponent.
		float distance = G_Distance(other.oldEnt, entity.oldEnt);

		if (distance > SPREAD_RADIUS) return;

		float distanceFrac = distance / SPREAD_RADIUS;
		float distanceMod  = 1.0f - distanceFrac;
		float spreadChance = distanceMod;

		if (random() < spreadChance) {
			if (G_LineOfSight(entity.oldEnt, other.oldEnt) && other.Ignite(fireStarter)) {
				fireLogger.Notice("Ignited a neighbour, chance to do so was %.0f%%.",
				                  spreadChance*100.0f);
			}
		}
	});

	// Don't spread again until re-ignited.
	spreadAt = INT_MAX;
}
Example #2
0
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 IgnitableComponent::ConsiderSpread(int timeDelta) {
	if (!onFire) return;

	ForEntities<IgnitableComponent>([&](Entity &other, IgnitableComponent &ignitable){
		if (&other == &entity) return;

		// TODO: Use LocationComponent.
		float chance = 1.0f - G_Distance(entity.oldEnt, other.oldEnt) / SPREAD_RADIUS;

		if (chance <= 0.0f) return; // distance > spread radius

		if (random() < chance) {
			if (G_LineOfSight(entity.oldEnt, other.oldEnt) && other.Ignite(fireStarter)) {
				fireLogger.Debug("(Re-)Ignited a neighbour (chance was %.0f%%)", chance * 100.0f);
			} else {
				fireLogger.Debug("Tried to ignite a non-ignitable or non-LOS neighbour (chance was %.0f%%)",
								 chance * 100.0f);
			}
		} else {
			fireLogger.Debug("Didn't try to ignite a neighbour (chance was %.0f%%)", chance * 100.0f);
		}
	});
}
Example #4
0
void SpikerComponent::Think(int timeDelta) {
	// Don't act if recovering from shot or disabled.
	if (!GetAlienBuildableComponent().GetBuildableComponent().Active() || level.time < restUntil) {
		lastExpectedDamage = 0.0f;
		lastSensing = false;

		return;
	}

	float expectedDamage = 0.0f;
	bool  sensing = false;

	// Calculate expected damage to decide on the best moment to shoot.
	ForEntities<HealthComponent>([&](Entity& other, HealthComponent& healthComponent) {
		if (G_Team(other.oldEnt) == TEAM_NONE)                            return;
		if (G_OnSameTeam(entity.oldEnt, other.oldEnt))                    return;
		if ((other.oldEnt->flags & FL_NOTARGET))                          return;
		if (!healthComponent.Alive())                                     return;
		if (G_Distance(entity.oldEnt, other.oldEnt) > SPIKE_RANGE)        return;
		if (other.Get<BuildableComponent>())                              return;
		if (!G_LineOfSight(entity.oldEnt, other.oldEnt))                  return;

		Vec3 dorsal    = Vec3::Load(entity.oldEnt->s.origin2);
		Vec3 toTarget  = Vec3::Load(other.oldEnt->s.origin) - Vec3::Load(entity.oldEnt->s.origin);
		Vec3 otherMins = Vec3::Load(other.oldEnt->r.mins);
		Vec3 otherMaxs = Vec3::Load(other.oldEnt->r.maxs);

		// With a straight shot, only entities in the spiker's upper hemisphere can be hit.
		// Since the spikes obey gravity, increase or decrease this radius of damage by up to
		// GRAVITY_COMPENSATION_ANGLE degrees depending on the spiker's orientation.
		if (Math::Dot(Math::Normalize(toTarget), dorsal) < gravityCompensation) return;

		// Approximate average damage the entity would receive from spikes.
		const missileAttributes_t* ma = BG_Missile(MIS_SPIKER);
		float spikeDamage  = ma->damage;
		float distance     = Math::Length(toTarget);
		float bboxDiameter = Math::Length(otherMins) + Math::Length(otherMaxs);
		float bboxEdge     = (1.0f / M_ROOT3) * bboxDiameter; // Assumes a cube.
		float hitEdge      = bboxEdge + ((1.0f / M_ROOT3) * ma->size); // Add half missile edge.
		float hitArea      = hitEdge * hitEdge; // Approximate area resulting in a hit.
		float effectArea   = 2.0f * M_PI * distance * distance; // Area of a half sphere.
		float damage       = (hitArea / effectArea) * (float)MISSILES * spikeDamage;

		// Sum up expected damage for all targets, regardless of whether they are in sense range.
		expectedDamage += damage;

		// Start sensing (frequent search for best moment to shoot) as soon as an enemy that can be
		// damaged is close enough. Note that the Spiker will shoot eventually after it started
		// sensing, and then returns to a less alert state.
		if (distance < SPIKER_SENSE_RANGE && !sensing) {
			sensing = true;

			if (!lastSensing) {
				logger.Verbose("Spiker #%i now senses an enemy and will check more frequently for "
					"the best moment to shoot.", entity.oldEnt->s.number);

				RegisterFastThinker();
			}
		}
	});

	bool senseLost = lastSensing && !sensing;

	if (sensing || senseLost) {
		bool lessDamage = (expectedDamage <= lastExpectedDamage);
		bool enoughDamage = (expectedDamage >= DAMAGE_THRESHOLD);

		if (sensing) {
			logger.Verbose("Spiker #%i senses an enemy and expects to do %.1f damage.%s%s",
				entity.oldEnt->s.number, expectedDamage, (lessDamage && !enoughDamage)
				? " This has not increased, so it's time to shoot." : "", enoughDamage ?
				" This is already enough, shoot now." : "");
		}

		if (senseLost) {
			logger.Verbose("Spiker #%i lost track of all enemies after expecting to do %.1f damage."
				" This makes the spiker angry, so it will shoot anyway.",
				entity.oldEnt->s.number, lastExpectedDamage);
		}

		// Shoot when
		// - a threshold was reached by the expected damage, implying a very close enemy,
		// - the expected damage has decreased, witnessing a recent local maximum, or
		// - whenever all viable targets have left the sense range.
		// The first trigger plays around the delay in sensing a local maximum and in having the
		// spikes travel towards their destination.
		// The last trigger guarantees that the spiker always shoots eventually after sensing.
		if (enoughDamage || (sensing && lessDamage) || senseLost) {
			Fire();
		}
	}

	lastExpectedDamage = expectedDamage;
	lastSensing = sensing;
}