#include "core/materials.h" #include #include #include #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 parse_tags(const std::string& s) { std::vector 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 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")) { // : 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. 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 "); // Pass 1: index materials in file order (children may precede their parent). std::vector 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 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 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