102 lines
4.4 KiB
C++
102 lines
4.4 KiB
C++
// game::data -- the research graph (TechTree/MasterTechList.tech).
|
|
//
|
|
// tech {
|
|
// name "IND_Waldo" family "IND" type P threat N group TORPS option_cost 1.2
|
|
// requires "TECH" | "GRP_<group>" -- prerequisite beyond the allows edge
|
|
// allows "CHILD RP:cost Human:% Zuul:% Hiver:% Tarkas:% Liir:% Morrigi:%"
|
|
// strategy { inc TECHBEN_X dec TECHBEN_Y }
|
|
// ship { section STEM ... } -- "new section" notice (not a build gate)
|
|
// weapon { filename "Weapons/x.weapon" }
|
|
// }
|
|
//
|
|
// `allows` is a directed edge parent -> child with the research-point cost of
|
|
// the child when reached through this parent and a per-species availability
|
|
// percentage. A species not named on the edge gets the engine default of 100
|
|
// (data-parsers.md; the file never writes it). Species are indexed in the
|
|
// engine's species order (common.h), so `_NPC` has a slot and is always 100.
|
|
#pragma once
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#include "game/data/block.h"
|
|
#include "game/data/common.h"
|
|
#include "mars/parse/blocks.h"
|
|
|
|
namespace game::data {
|
|
|
|
constexpr int kDefaultAllowPercent = 100;
|
|
|
|
struct AllowsEdge {
|
|
std::string from, to;
|
|
std::optional<std::int64_t> rp;
|
|
std::array<int, kSpeciesCount> pct{}; // effective values (default 100)
|
|
std::array<bool, kSpeciesCount> pct_written{}; // which ones the file spelled out
|
|
std::vector<std::string> unparsed; // tokens that were neither RP: nor a species
|
|
std::string text; // the allows string as written
|
|
int line = 0;
|
|
|
|
AllowsEdge() { pct.fill(kDefaultAllowPercent); pct_written.fill(false); }
|
|
int percent(Species s) const { return pct[static_cast<std::size_t>(s)]; }
|
|
};
|
|
|
|
// Parse one allows string. `problems` (optional) receives an Unparsed entry
|
|
// for leftover tokens / missing RP.
|
|
AllowsEdge parse_allows(std::string_view text, std::string from, int line = 0,
|
|
std::vector<Problem>* problems = nullptr, std::string_view file = {});
|
|
|
|
struct TechNode {
|
|
std::string name;
|
|
std::string family; // as written (often absent)
|
|
std::string family_inferred; // upper-cased name prefix before '_'
|
|
std::string type; // "P" on project techs
|
|
std::optional<std::int64_t> threat;
|
|
std::string group; // as written; group membership is matched upper-cased
|
|
std::optional<double> option_cost;
|
|
std::optional<bool> unlock_explicitly;
|
|
std::vector<std::string> requires_tech;
|
|
std::vector<std::string> benefits_inc, benefits_dec; // TECHBEN_* tokens, kept as data
|
|
std::vector<std::string> sections; // ship{section} stems
|
|
std::vector<std::string> weapon_files; // weapon{filename}
|
|
std::vector<std::size_t> allows; // indices into TechTree::edges
|
|
std::optional<std::string> display_name, description; // TECHNAME_/TECHDESC_ (catalog fills)
|
|
Block raw;
|
|
int line = 0;
|
|
|
|
bool is_root() const { return starts_with_fold(name.size() >= 5 ? name.substr(name.size() - 5) : name, "_ROOT"); }
|
|
};
|
|
|
|
inline bool is_group_ref(std::string_view token) { return starts_with_fold(token, "GRP_"); }
|
|
inline std::string_view group_of_ref(std::string_view token) { return token.substr(4); } // after GRP_
|
|
|
|
class TechTree {
|
|
public:
|
|
std::vector<TechNode> nodes;
|
|
std::vector<AllowsEdge> edges;
|
|
// upper-cased group name -> member tech names, first-seen order
|
|
std::vector<std::pair<std::string, std::vector<std::string>>> groups;
|
|
|
|
const TechNode* find(std::string_view name) const; // case-insensitive
|
|
const std::vector<std::string>* group_members(std::string_view name_or_ref) const; // "TORPS" or "GRP_Torps"
|
|
std::vector<const AllowsEdge*> edges_to(std::string_view name) const;
|
|
std::vector<const AllowsEdge*> edges_from(std::string_view name) const;
|
|
std::vector<const TechNode*> roots() const; // nodes no edge points at
|
|
|
|
// A `requires_tech` token is satisfiable if it names a tech or a non-empty group.
|
|
bool requirement_exists(std::string_view token) const;
|
|
|
|
void rebuild_index();
|
|
|
|
private:
|
|
std::unordered_map<std::string, std::size_t> by_name_; // folded
|
|
};
|
|
|
|
Loaded<TechTree> load_tech_tree(const mars::parse::Document& doc, std::string file = {});
|
|
Loaded<TechTree> parse_tech_tree(std::string_view text, std::string file = {});
|
|
|
|
} // namespace game::data
|