// TODO: Consider location as well as direction when both given.
void KnockbackComponent::HandleDamage(float amount, gentity_t* source, Util::optional<Vec3> location,
                                      Util::optional<Vec3> direction, int flags, meansOfDeath_t meansOfDeath) {
	if (!(flags & DAMAGE_KNOCKBACK)) return;
	if (amount <= 0.0f) return;

	if (!direction) {
		knockbackLogger.Warn("Received damage message with knockback flag set but no direction.");
		return;
	}

	if (Math::Length(direction.value()) == 0.0f) {
		knockbackLogger.Warn("Attempt to do knockback with null vector direction.");
		return;
	}

	// TODO: Remove dependency on client.
	gclient_t *client = entity.oldEnt->client;
	assert(client);

	// Check for immunity.
	if (client->noclip) return;
	if (client->sess.spectatorState != SPECTATOR_NOT) return;

	float mass = (float)BG_Class(client->ps.stats[ STAT_CLASS ])->mass;

	if (mass <= 0.0f) {
		knockbackLogger.Warn("Attempt to do knockback against target with no mass, assuming normal mass.");
		mass = KNOCKBACK_NORMAL_MASS;
	}

	float massMod  = Math::Clamp(KNOCKBACK_NORMAL_MASS / mass, KNOCKBACK_MIN_MASSMOD, KNOCKBACK_MAX_MASSMOD);
	float strength = amount * DAMAGE_TO_KNOCKBACK * massMod;

	// Change client velocity.
	Vec3 clientVelocity = Vec3::Load(client->ps.velocity);
	clientVelocity += Math::Normalize(direction.value()) * strength;
	clientVelocity.Store(client->ps.velocity);

	// Set pmove timer so that the client can't cancel out the movement immediately.
	if (!client->ps.pm_time) {
		client->ps.pm_time = KNOCKBACK_PMOVE_TIME;
		client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
	}

	knockbackLogger.Debug("Knockback: client: %i, strength: %.1f (massMod: %.1f).",
	                      entity.oldEnt->s.number, strength, massMod);
}
// TODO: Use new vector math library for all calculations.
void ArmorComponent::HandleApplyDamageModifier(float& damage, Util::optional<Vec3> location,
Util::optional<Vec3> direction, int flags, meansOfDeath_t meansOfDeath) {
	vec3_t origin, bulletPath, bulletAngle, locationRelativeToFloor, floor, normal;

	// TODO: Remove dependency on client.
	assert(entity.oldEnt->client);

	// Use non-regional damage where appropriate.
	if (flags & DAMAGE_NO_LOCDAMAGE || !location) {
		// TODO: Move G_GetNonLocDamageMod to ArmorComponent.
		damage *= GetNonLocationalDamageMod();
		return;
	}

	// Get hit location relative to the floor beneath.
	if (g_unlagged.integer && entity.oldEnt->client->unlaggedCalc.used) {
		VectorCopy(entity.oldEnt->client->unlaggedCalc.origin, origin);
	} else {
		VectorCopy(entity.oldEnt->r.currentOrigin, origin);
	}
	BG_GetClientNormal(&entity.oldEnt->client->ps, normal);
	VectorMA(origin, entity.oldEnt->r.mins[2], normal, floor);
	VectorSubtract(location.value().Data(), floor, locationRelativeToFloor);

	// Get fraction of height where the hit landed.
	float height = entity.oldEnt->r.maxs[2] - entity.oldEnt->r.mins[2];
	if (!height) height = 1.0f;
	float hitRatio = Math::Clamp(DotProduct(normal, locationRelativeToFloor) / VectorLength(normal),
	                             0.0f, height) / height;

	// Get the yaw of the attack relative to the target's view yaw
	VectorSubtract(location.value().Data(), origin, bulletPath);
	vectoangles(bulletPath, bulletAngle);
	int hitRotation = AngleNormalize360(entity.oldEnt->client->ps.viewangles[YAW] - bulletAngle[YAW]);

	// Use regional modifier.
	// TODO: Move G_GetPointDamageMod to ArmorComponent.
	damage *= GetLocationalDamageMod(hitRotation, hitRatio);
}
Esempio n. 3
0
    /**
     * Constructs a new impl based on the locale parameters.
     */
    impl(const std::string& language,
         const util::optional<std::string>& country)
    {
        icu::Locale locale(language.c_str(),
                           country ? country->c_str() : nullptr);
        if (locale.isBogus())
            throw std::runtime_error{"failed to create locale"};

        auto status = U_ZERO_ERROR;
        sentence_iter_.reset(
            icu::BreakIterator::createSentenceInstance(locale, status));
        word_iter_.reset(
            icu::BreakIterator::createWordInstance(locale, status));
        if (!U_SUCCESS(status))
            throw std::runtime_error{"failed to create segmenter"};
    }
void HealthComponent::HandleDamage(float amount, gentity_t* source, Util::optional<Vec3> location,
Util::optional<Vec3> direction, int flags, meansOfDeath_t meansOfDeath) {
	if (health <= 0.0f) return;
	if (amount <= 0.0f) return;

	gclient_t *client = entity.oldEnt->client;

	// Check for immunity.
	if (entity.oldEnt->flags & FL_GODMODE) return;
	if (client) {
		if (client->noclip) return;
		if (client->sess.spectatorState != SPECTATOR_NOT) return;
	}

	// Set source to world if missing.
	if (!source) source = &g_entities[ENTITYNUM_WORLD];

	// Don't handle ET_MOVER w/o die or pain function.
	// TODO: Handle mover special casing in a dedicated component.
	if (entity.oldEnt->s.eType == entityType_t::ET_MOVER && !(entity.oldEnt->die || entity.oldEnt->pain)) {
		// Special case for ET_MOVER with act function in initial position.
		if ((entity.oldEnt->moverState == MOVER_POS1 || entity.oldEnt->moverState == ROTATOR_POS1)
		    && entity.oldEnt->act) {
			entity.oldEnt->act(entity.oldEnt, source, source);
		}

		return;
	}

	// Check for protection.
	if (!(flags & DAMAGE_NO_PROTECTION)) {
		// Check for protection from friendly damage.
		if (entity.oldEnt != source && G_OnSameTeam(entity.oldEnt, source)) {
			// Check if friendly fire has been disabled.
			if (!g_friendlyFire.integer) return;

			// Never do friendly damage on movement attacks.
			switch (meansOfDeath) {
				case MOD_LEVEL3_POUNCE:
				case MOD_LEVEL4_TRAMPLE:
					return;

				default:
					break;
			}

			// If dretchpunt is enabled and this is a dretch, do dretchpunt instead of damage.
			// TODO: Add a message for pushing.
			if (g_dretchPunt.integer && client && client->ps.stats[STAT_CLASS] == PCL_ALIEN_LEVEL0)
			{
				vec3_t dir, push;

				VectorSubtract(entity.oldEnt->r.currentOrigin, source->r.currentOrigin, dir);
				VectorNormalizeFast(dir);
				VectorScale(dir, (amount * 10.0f), push);
				push[ 2 ] = 64.0f;

				VectorAdd( client->ps.velocity, push, client->ps.velocity );

				return;
			}
		}

		// Check for protection from friendly buildable damage. Never protect from building actions.
		// TODO: Use DAMAGE_NO_PROTECTION flag instead of listing means of death here.
		if (entity.oldEnt->s.eType == entityType_t::ET_BUILDABLE && source->client &&
		    meansOfDeath != MOD_DECONSTRUCT && meansOfDeath != MOD_SUICIDE &&
		    meansOfDeath != MOD_REPLACE) {
			if (G_OnSameTeam(entity.oldEnt, source) && !g_friendlyBuildableFire.integer) {
				return;
			}
		}
	}

	float take = amount;

	// Apply damage modifiers.
	if (!(flags & DAMAGE_PURE)) {
		entity.ApplyDamageModifier(take, location, direction, flags, meansOfDeath);
	}

	// Update combat timers.
	// TODO: Add a message to update combat timers.
	if (client && source->client && entity.oldEnt != source) {
		client->lastCombatTime = entity.oldEnt->client->lastCombatTime = level.time;
	}

	if (client) {
		// Save damage w/o armor modifier.
		client->damage_received += (int)(amount + 0.5f);

		// Save damage direction.
		if (direction) {
			VectorCopy(direction.value().Data(), client->damage_from);
			client->damage_fromWorld = false;
		} else {
			VectorCopy(entity.oldEnt->r.currentOrigin, client->damage_from);
			client->damage_fromWorld = true;
		}

		// Drain jetpack fuel.
		// TODO: Have another component handle jetpack fuel drain.
		client->ps.stats[STAT_FUEL] = std::max(0, client->ps.stats[STAT_FUEL] -
		                                       (int)(amount + 0.5f) * JETPACK_FUEL_PER_DMG);

		// If boosted poison every attack.
		// TODO: Add a poison message and a PoisonableComponent.
		if (source->client && (source->client->ps.stats[STAT_STATE] & SS_BOOSTED) &&
		    client->pers.team == TEAM_HUMANS && client->poisonImmunityTime < level.time) {
			switch (meansOfDeath) {
				case MOD_POISON:
				case MOD_LEVEL2_ZAP:
					break;

				default:
					client->ps.stats[STAT_STATE] |= SS_POISONED;
					client->lastPoisonTime   = level.time;
					client->lastPoisonClient = source;
					break;
			}
		}
	}

	healthLogger.Notice("Taking damage: %3.1f (%3.1f → %3.1f)", take, health, health - take);

	// Do the damage.
	health -= take;

	// Update team overlay info.
	if (client) client->pers.infoChangeTime = level.time;

	// TODO: Move lastDamageTime to HealthComponent.
	entity.oldEnt->lastDamageTime = level.time;

	// HACK: gentity_t.nextRegenTime only affects alien clients.
	// TODO: Catch damage message in a new RegenerationComponent.
	entity.oldEnt->nextRegenTime = level.time + ALIEN_CLIENT_REGEN_WAIT;

	// Handle non-self damage.
	if (entity.oldEnt != source) {
		float loss = take;

		if (health < 0.0f) loss += health;

		// TODO: Use ClientComponent.
		if (source->client) {
			// Add to the attacker's account on the target.
			// TODO: Move damage account array to HealthComponent.
			entity.oldEnt->credits[source->client->ps.clientNum].value += loss;
			entity.oldEnt->credits[source->client->ps.clientNum].time = level.time;
			entity.oldEnt->credits[source->client->ps.clientNum].team = (team_t)source->client->pers.team;
		}
	}

	// Handle death.
	// TODO: Send a Die/Pain message and handle details where appropriate.
	if (health <= 0) {
		healthLogger.Notice("Dying with %.1f health.", health);

		// Disable knockback.
		if (client) entity.oldEnt->flags |= FL_NO_KNOCKBACK;

		// Call legacy die function.
		if (entity.oldEnt->die) entity.oldEnt->die(entity.oldEnt, source, source, meansOfDeath);

		// Send die message.
		entity.Die(source, meansOfDeath);

		// Trigger ON_DIE event.
		if(!client) G_EventFireEntity(entity.oldEnt, source, ON_DIE);
	} else if (entity.oldEnt->pain) {
		entity.oldEnt->pain(entity.oldEnt, source, (int)std::ceil(take));
	}

	if (entity.oldEnt != source && source->client) {
		bool lethal = (health <= 0);
		CombatFeedback::HitNotify(source, entity.oldEnt, location, take, meansOfDeath, lethal);
	}
}