// 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); }
/** * 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); } }