Files
Noita-GPU-Vulkan/src/core/materials.cpp
T
2026-10-19 20:59:15 +02:00

187 lines
7.4 KiB
C++

#include "core/materials.h"
#include <set>
#include <cstdlib>
#include <stdexcept>
#include "core/xml.h"
namespace ng {
namespace {
uint32_t parse_hex_color(const std::string& s) {
return (uint32_t)std::strtoul(s.c_str(), nullptr, 16);
}
CellType parse_type(const std::string& s) {
if (s == "liquid") return CellType::Liquid;
if (s == "gas") return CellType::Gas;
if (s == "solid") return CellType::Solid;
if (s == "fire") return CellType::Fire;
return CellType::Air;
}
// "[fire],[hot]" -> {"fire","hot"}
std::vector<std::string> parse_tags(const std::string& s) {
std::vector<std::string> out;
std::string cur;
for (char c : s) {
if (c == '[') cur.clear();
else if (c == ']') out.push_back(cur);
else cur += c;
}
return out;
}
void apply_attrs(Material& m, const XmlNode& n) {
std::set<std::string> seen; // a duplicated attribute keeps its first occurrence (00dde120)
for (auto& [k, v] : n.attrs) {
if (k == "name" || k == "_parent" || k == "_inherit_reactions") continue;
if (!seen.insert(k).second) continue;
m.attrs[k] = v;
}
if (auto* v = n.attr("cell_type")) m.type = parse_type(*v);
if (auto* v = n.attr("wang_color")) m.wang_color = parse_hex_color(*v);
if (auto* v = n.attr("tags")) m.tags = parse_tags(*v);
if (auto* g = n.child("Graphics"))
if (auto* c = g->attr("color")) m.graphics_color = parse_hex_color(*c);
if (auto* g = n.child("Graphics"))
if (auto* t = g->attr("texture_file")) m.graphics_texture = *t;
if (auto* se = n.child("StatusEffects")) { // <Ingestion><StatusEffect type amount/>: kept as status.ingestion = "TYPE:amount;TYPE:amount" (the stains come from the status_effects attribute and liquid_stains)
for (auto& ch : se->children) {
if (ch->name != "Ingestion" && ch->name != "Stains") continue;
std::string list;
for (auto& e : ch->children) if (e->name == "StatusEffect") list += e->attr_or("type") + ":" + e->attr_or("amount", "1") + ";";
m.attrs[ch->name == "Ingestion" ? "status.ingestion" : "status.stains"] = list;
}
}
if (auto* x = n.child("ExplosionConfig")) // the explosion of a burning cell or a solid body of this material: kept as explosion.<attribute>
for (auto& [k, v] : x->attrs) m.attrs["explosion." + k] = v;
}
} // namespace
void MaterialDb::load(std::string_view xml_text) {
auto root = parse_xml(xml_text);
if (root->name != "Materials") throw std::runtime_error("materials: root is not <Materials>");
// Pass 1: index materials in file order (children may precede their parent).
std::vector<const XmlNode*> nodes;
for (auto& c : root->children) {
if (c->name != "CellData" && c->name != "CellDataChild") continue;
std::string name = c->attr_or("name");
if (name.empty()) continue;
if (auto dup = by_name_.find(name); dup != by_name_.end()) { nodes[dup->second] = c.get(); continue; } // (a mod's file defines it again: the later definition wins)
Material m;
m.name = std::move(name);
m.index = (int)mats_.size();
by_name_[m.name] = m.index;
mats_.push_back(std::move(m));
nodes.push_back(c.get());
}
// Pass 2: resolve inheritance depth-first.
enum : uint8_t { Todo, Busy, Done };
std::vector<uint8_t> state(mats_.size(), Todo);
auto resolve = [&](auto&& self, int idx) -> void {
if (state[idx] == Done) return;
if (state[idx] == Busy) throw std::runtime_error("materials: parent cycle at " + mats_[idx].name);
state[idx] = Busy;
Material& m = mats_[idx];
const XmlNode& n = *nodes[idx];
if (n.name == "CellDataChild") {
m.parent = n.attr_or("_parent");
auto it = by_name_.find(m.parent);
if (it == by_name_.end())
throw std::runtime_error("materials: " + m.name + " has unknown parent " + m.parent);
self(self, it->second);
const Material& p = mats_[it->second];
m.type = p.type;
m.wang_color = p.wang_color;
m.graphics_color = p.graphics_color;
m.graphics_texture = p.graphics_texture;
m.tags = p.tags;
m.attrs = p.attrs;
}
apply_attrs(m, n);
state[idx] = Done;
};
for (int i = 0; i < (int)mats_.size(); ++i) resolve(resolve, i);
for (auto& m : mats_)
if (m.wang_color) by_wang_.emplace(m.wang_color, m.index);
for (auto& c : root->children) {
if (c->name == "Reaction") {
Reaction r;
r.probability = (float)std::atof(c->attr_or("probability", "0").c_str());
std::set<std::string> seenKeys;
for (auto& [k, v] : c->attrs) {
if (!seenKeys.insert(k).second) continue; // first occurrence wins
if (k.size() == 11 && k.compare(0, 10, "input_cell") == 0 && k[10] >= '1' && k[10] <= '3')
r.in[k[10] - '1'] = v;
else if (k.size() == 12 && k.compare(0, 11, "output_cell") == 0 && k[11] >= '1' && k[11] <= '3')
r.out[k[11] - '1'] = v;
else if (k != "probability")
r.attrs[k] = v;
}
reactions_.push_back(std::move(r));
}
}
}
void MaterialDb::add_loose_twins() {
const size_t n = mats_.size();
loose_twin_.assign(n, -1);
for (size_t i = 0; i < n; ++i) {
if (mats_[i].type != CellType::Liquid || attr_int(mats_[i], "liquid_static", 0) != 1 || attr_int(mats_[i], "liquid_sand", 0) != 1) continue;
Material t = mats_[i];
t.name += "#loose";
t.attrs["liquid_static"] = "0";
t.index = (int)mats_.size();
by_name_[t.name] = t.index;
loose_twin_[i] = t.index;
mats_.push_back(std::move(t));
}
loose_twin_.resize(mats_.size(), -1);
}
const Material* MaterialDb::find(std::string_view name) const {
auto it = by_name_.find(std::string(name));
return it == by_name_.end() ? nullptr : &mats_[it->second];
}
const Material* MaterialDb::by_wang_color(uint32_t argb) const {
auto it = by_wang_.find(argb);
return it == by_wang_.end() ? nullptr : &mats_[it->second];
}
float attr_float(const Material& m, const char* key, float def) {
auto it = m.attrs.find(key);
return it == m.attrs.end() ? def : (float)std::atof(it->second.c_str());
}
int attr_int(const Material& m, const char* key, int def) {
auto it = m.attrs.find(key);
return it == m.attrs.end() ? def : std::atoi(it->second.c_str());
}
SimParams sim_params(const Material& m) {
SimParams p; // member initialisers are the engine defaults
p.density = attr_float(m, "density", p.density);
p.gravity = attr_float(m, "liquid_gravity", p.gravity);
p.viscosity = attr_int(m, "liquid_viscosity", p.viscosity);
p.damping = attr_float(m, "liquid_damping", p.damping);
p.flow_speed = attr_float(m, "liquid_flow_speed", p.flow_speed);
p.gas_speed = attr_int(m, "gas_speed", p.gas_speed);
p.gas_up = attr_int(m, "gas_upwards_speed", p.gas_up);
p.gas_horizontal = attr_int(m, "gas_horizontal_speed", p.gas_horizontal);
p.gas_down = attr_int(m, "gas_downwards_speed", p.gas_down);
p.lifetime = attr_int(m, "lifetime", p.lifetime);
p.sand = attr_int(m, "liquid_sand", 0) != 0;
p.is_static = m.type == CellType::Solid || attr_int(m, "liquid_static", 0) == 1;
return p;
}
} // namespace ng