sots-engine/src/game/data/techtree.h

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