This commit is contained in:
Gaël committed 2026-10-19 20:59:15 +02:00
commit 87bff164f1
416 files changed
+279512

No files matched your search

+156
View File
@@ -0,0 +1,156 @@
#include "gun/effects.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
namespace ng {
namespace {
float num(const EntityComponent& c, const char* k, float d) {
auto it = c.attrs.find(k);
return it == c.attrs.end() ? d : (float)std::atof(it->second.c_str());
}
float num(const std::map<std::string, std::string>& a, const char* k, float d) {
auto it = a.find(k);
return it == a.end() ? d : (float)std::atof(it->second.c_str());
}
uint32_t average(const SpriteFrames& s) {
uint64_t r = 0, g = 0, b = 0, n = 0;
for (size_t i = 0; i < (size_t)s.w * s.h && i < s.texels.size(); ++i) {
const uint32_t t = s.texels[i];
if ((t >> 24) < 128) continue;
r += (t >> 16) & 255; g += (t >> 8) & 255; b += t & 255; ++n;
}
if (!n) return 0xFFFFB040u;
return 0xFF000000u | (uint32_t)(r / n) << 16 | (uint32_t)(g / n) << 8 | (uint32_t)(b / n);
}
} // namespace
const SpriteFrames* EffectLib::sprite(const std::string& file, const std::string& animation) {
if (file.empty()) return nullptr;
const std::string key = file + "|" + animation;
auto it = sprites_.find(key);
if (it == sprites_.end()) {
auto s = std::make_unique<SpriteFrames>();
if (!load_sprite_frames(wak_, file, animation, *s)) s.reset();
it = sprites_.emplace(key, std::move(s)).first;
}
return it->second.get();
}
uint32_t EffectLib::sprite_color(const SpriteFrames* s) const { return s ? average(*s) : 0xFFFFB040u; }
ExplosionFx EffectLib::explosion(const std::map<std::string, std::string>& attrs) {
ExplosionFx e;
auto str = [&](const char* k) { auto it = attrs.find(k); return it == attrs.end() ? std::string() : it->second; };
e.radius = num(attrs, "config_explosion.explosion_radius", 0);
e.shake = num(attrs, "config_explosion.camera_shake", 0);
e.sprite = sprite(str("config_explosion.explosion_sprite"));
e.sparks = num(attrs, "config_explosion.sparks_enabled", 0) != 0;
e.sparks0 = (int)num(attrs, "config_explosion.sparks_count_min", 0);
e.sparks1 = (int)num(attrs, "config_explosion.sparks_count_max", 0);
e.material_sparks = num(attrs, "config_explosion.material_sparks_enabled", 0) != 0;
e.msparks0 = (int)num(attrs, "config_explosion.material_sparks_count_min", 0);
e.msparks1 = (int)num(attrs, "config_explosion.material_sparks_count_max", 0);
e.light = num(attrs, "config_explosion.light_enabled", 1) != 0; // (the constructor's defaults, 004b2780: on, 0.08 s, (255, 217, 180), radius x 8)
e.light_color = 0xFF000000u | ((uint32_t)std::clamp((int)num(attrs, "config_explosion.light_r", 255), 0, 255) << 16) | ((uint32_t)std::clamp((int)num(attrs, "config_explosion.light_g", 217), 0, 255) << 8) | (uint32_t)std::clamp((int)num(attrs, "config_explosion.light_b", 180), 0, 255);
e.light_radius = e.radius * num(attrs, "config_explosion.light_radius_coeff", 8.0f);
e.light_frames = std::max(1, (int)std::lround(num(attrs, "config_explosion.light_fade_time", 0.08f) * 60.0f));
{
std::string sm = str("config_explosion.spark_material");
if (sm.empty()) sm = "spark";
if (material_color) if (const uint32_t c = material_color(sm)) {
const float a = material_alpha ? material_alpha(sm) : 1.0f;
auto ch = [&](int sh) { return (uint32_t)std::lround(((c >> sh) & 0xFFu) * a) << sh; };
e.spark_color = 0xFF000000u | ch(16) | ch(8) | ch(0);
}
}
if (e.sprite) e.color = average(*e.sprite);
e.material_color = e.color;
const std::string mat = str("config_explosion.create_cell_material");
if (!mat.empty() && material_color) { const uint32_t c = material_color(mat); if (c) e.material_color = c; }
e.valid = e.sprite || e.sparks || e.material_sparks;
return e;
}
void EffectLib::muzzle(const EntityDef& def, ExplosionFx& out) {
for (const EntityComponent* c : def.all("SpriteComponent")) {
if (c->get("_enabled", "1") == "0") continue;
std::string file = c->get("image_file");
const size_t p = file.find("$["); // "name_0$[1-5].png": one of five variants, the first is used
if (p != std::string::npos) {
const size_t e = file.find(']', p);
if (e != std::string::npos) file = file.substr(0, p) + std::to_string(std::atoi(file.c_str() + p + 2)) + file.substr(e + 1);
}
out.sprite = sprite(file, c->get("rect_animation"));
if (!out.sprite) continue;
out.pivot_x = (float)std::atof(c->get("offset_x", "0").c_str());
out.pivot_y = (float)std::atof(c->get("offset_y", "0").c_str());
out.additive = c->get("additive", "0") == "1";
out.wait = 2;
out.color = average(*out.sprite);
out.valid = true;
break;
}
}
void EffectLib::projectile(const EntityDef& def, ProjFx& fx) {
for (const EntityComponent* c : def.all("LightComponent")) {
if (c->get("_enabled", "1") == "0") continue;
fx.light = {true, (int)num(*c, "r", 255), (int)num(*c, "g", 255), (int)num(*c, "b", 255), num(*c, "radius", 20)};
break;
}
for (const EntityComponent* c : def.all("ProjectileComponent")) {
if (num(*c, "shoot_light_flash_radius", 0) > 0)
fx.shoot_flash = {true, (int)num(*c, "shoot_light_flash_r", 255), (int)num(*c, "shoot_light_flash_g", 255), (int)num(*c, "shoot_light_flash_b", 255), num(*c, "shoot_light_flash_radius", 0)};
auto attrs = c->attrs;
if (!def.all("PhysicsBodyComponent").empty() && !def.all("PhysicsBodyCollisionDamageComponent").empty() && !def.all("DamageModelComponent").empty()) // a breakable thrown item: its explosion is the ExplodeOnDamage one (projectiles.cpp break_speed)
for (const EntityComponent* eod : def.all("ExplodeOnDamageComponent")) { for (const auto& [k, v] : eod->attrs) if (k.compare(0, 17, "config_explosion.") == 0) attrs[k] = v; break; }
fx.boom = explosion(attrs);
break;
}
for (const EntityComponent* c : def.all("SpriteComponent"))
if (c->get("_enabled", "1") != "0" && c->get("additive", "0") == "1") fx.additive = true;
for (const EntityComponent* c : def.all("ParticleEmitterComponent")) {
if (c->get("_enabled", "1") == "0" || c->get("is_emitting", "1") == "0" || (num(*c, "emit_cosmetic_particles", 0) == 0 && num(*c, "create_real_particles", 0) == 0)) continue;
if (!c->get("image_animation_file").empty()) continue; // (an image animation - the pixels of a picture, frame by frame, played once or looped: the host emits it on the projectile's twin, main.cpp)
EmitterDef e;
const std::string mat = c->get("emitted_material_name");
e.real = num(*c, "create_real_particles", 0) != 0 && !mat.empty();
e.only_space = num(*c, "emit_only_if_there_is_space", 0) != 0;
e.material = mat;
if (material_color && !mat.empty()) { const uint32_t col = material_color(mat); if (col) e.color = col; }
e.glow = !material_glow || mat.empty() || material_glow(mat);
e.rmin = num(*c, "area_circle_radius.min", 0); e.rmax = num(*c, "area_circle_radius.max", 0);
if (material_alpha && !mat.empty() && !e.glow) e.alpha = (uint32_t)std::lround(std::clamp(material_alpha(mat), 0.0f, 1.0f) * 255.0f);
if (material_alpha && !mat.empty() && !e.real && e.glow && num(*c, "render_ultrabright", 0) == 0) { // (premultiplied: these particles are additive; render_ultrabright (6b:10) draws them at the full colour)
const float a = material_alpha(mat);
auto ch = [&](int sh) { return (uint32_t)std::lround(((e.color >> sh) & 0xFFu) * a) << sh; };
e.color = (e.color & 0xFF000000u) | ch(16) | ch(8) | ch(0);
}
e.trail = num(*c, "is_trail", 0) != 0;
e.trail_gap = num(*c, "trail_gap", 0);
e.delay = std::max(0, (int)num(*c, "delay_frames", 0));
e.away = num(*c, "velocity_always_away_from_center", 0);
e.attractor = num(*c, "attractor_force", 0);
e.as_long = num(*c, "draw_as_long", 0) != 0;
e.xv0 = num(*c, "x_vel_min", 0) / 60.0f; e.xv1 = num(*c, "x_vel_max", 0) / 60.0f;
e.yv0 = num(*c, "y_vel_min", 0) / 60.0f; e.yv1 = num(*c, "y_vel_max", 0) / 60.0f;
e.gravity = num(*c, "gravity.y", 0) / 3600.0f;
e.count0 = (int)num(*c, "count_min", 1); e.count1 = std::max(e.count0, (int)num(*c, "count_max", 1));
e.life0 = num(*c, "lifetime_min", 0.2f) * 60.0f; e.life1 = std::max(e.life0, num(*c, "lifetime_max", 0.2f) * 60.0f);
e.interval0 = std::max(1, (int)num(*c, "emission_interval_min_frames", 1)); e.interval1 = std::max(e.interval0, (int)num(*c, "emission_interval_max_frames", 1));
e.jitter = num(*c, "airflow_force", 0) * 0.004f;
e.ox0 = num(*c, "x_pos_offset_min", 0); e.ox1 = num(*c, "x_pos_offset_max", 0);
e.oy0 = num(*c, "y_pos_offset_min", 0); e.oy1 = num(*c, "y_pos_offset_max", 0);
e.fade = num(*c, "fade_based_on_lifetime", 0) != 0; // (the component default is 0; 43 projectile emitters leave it out)
e.collide = num(*c, "collide_with_grid", 1) != 0;
e.force = num(*c, "cosmetic_force_create", 1) != 0;
fx.emitters.push_back(e);
}
}
} // namespace ng