// 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); }
float ResourceStorageComponent::GetStoredFraction() { // TODO: Add TeamComponent and/or Utility::Team. team_t team = entity.oldEnt->buildableTeam; if (!level.team[team].acquiredBuildPoints) return 1.0f; // The stored fraction is equal to the acquired fraction. float storedFraction = acquiredBuildPoints / level.team[team].acquiredBuildPoints; if (storedFraction < 0.0f || storedFraction > 1.0f + LINE_DISTANCE_EPSILON) { resourceStorageLogger.Warn( "A resource storage stores an invalid fraction of all build points: %.1f", storedFraction ); } return storedFraction; }
void SpawnerComponent::Think(int timeDelta) { BuildableComponent *buildableComponent = entity.Get<BuildableComponent>(); if (buildableComponent && !buildableComponent->Active()) return; Entity* blocker = GetBlocker(); if (blocker) { if (!blocker->oldEnt) { logger.Warn("Spawn blocking entity has oldEnt == nullptr"); return; } // Suicide if blocked by the map. if (blocker->oldEnt->s.number == ENTITYNUM_WORLD || blocker->oldEnt->s.eType == entityType_t::ET_MOVER) { Entities::Kill(entity, nullptr, MOD_SUICIDE); } // Free a blocking corpse. else if (blocker->oldEnt->s.eType == entityType_t::ET_CORPSE) { G_FreeEntity(blocker->oldEnt); } else if (Entities::OnSameTeam(entity, *blocker)) { // Suicide if blocked by own main buildable. if (blocker->Get<MainBuildableComponent>()) { Entities::Kill(entity, nullptr, MOD_SUICIDE); } // Kill a friendly blocking buildable. else if (blocker->Get<BuildableComponent>()) { Entities::Kill(*blocker, nullptr, MOD_SUICIDE); // Play an animation so it's clear what destroyed the buildable. G_SetBuildableAnim(entity.oldEnt, BANIM_SPAWN1, true); } // Do periodic damage to a friendly client. // TODO: Externalize constants. else if (blocker->Get<ClientComponent>() && g_antiSpawnBlock.integer) { blockTime += timeDelta; if (blockTime > BLOCKER_GRACE_PERIOD && blockTime - timeDelta <= BLOCKER_GRACE_PERIOD) { WarnBlocker(*blocker, false); } if (blockTime > BLOCKER_GRACE_PERIOD + BLOCKER_WARN_PERIOD) { if (blockTime - timeDelta <= BLOCKER_GRACE_PERIOD + BLOCKER_WARN_PERIOD) { WarnBlocker(*blocker, true); } blocker->Damage( BLOCKER_DAMAGE * ((float)timeDelta / 1000.0f), entity.oldEnt, {}, {}, DAMAGE_PURE, MOD_TRIGGER_HURT ); } } } } else if (g_antiSpawnBlock.integer) { blockTime = Math::Clamp(blockTime - timeDelta, 0, BLOCKER_GRACE_PERIOD); } }