187 lines
7.4 KiB
C++
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
|