aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYour Name <you@example.com>2021-05-06 14:13:28 -0400
committerYour Name <you@example.com>2021-05-06 14:13:28 -0400
commit9f3802690f9dd9452e96d1d7a879291978d66e35 (patch)
tree6d6c17b39abdb9490119241bc4fc061744b46d7d /src
parent2a9f262e6db5906db445d465e500d7ba8c90fab3 (diff)
downloaddmtool-9f3802690f9dd9452e96d1d7a879291978d66e35.tar.gz
dmtool-9f3802690f9dd9452e96d1d7a879291978d66e35.tar.bz2
dmtool-9f3802690f9dd9452e96d1d7a879291978d66e35.zip
Refactoring
Diffstat (limited to 'src')
-rw-r--r--src/cmd.cc33
-rw-r--r--src/cmd.h28
-rw-r--r--src/cmd_fsops.cc85
-rw-r--r--src/cmd_manipulate.cc186
-rw-r--r--src/cmd_query.cc49
-rw-r--r--src/cmd_usage.cc47
-rw-r--r--src/creature.cc6
-rw-r--r--src/creature.h2
-rw-r--r--src/dmtool.bash97
-rw-r--r--src/dmtool.cc448
-rw-r--r--src/jsonable.h33
-rw-r--r--src/rules.h7
-rw-r--r--src/spellcasting.h7
-rw-r--r--src/utils.cc16
-rw-r--r--src/utils.h58
-rw-r--r--src/weapon.h2
16 files changed, 624 insertions, 480 deletions
diff --git a/src/cmd.cc b/src/cmd.cc
new file mode 100644
index 0000000..7be5e01
--- /dev/null
+++ b/src/cmd.cc
@@ -0,0 +1,33 @@
+#include "cmd.h"
+#include "settings.h"
+#include <vector>
+#include <string>
+#include <filesystem>
+#include <algorithm>
+
+namespace cmd {
+ std::vector<std::string> getVirtDirs() {
+ return {"weapons", "armor", "spells", "creatures"};
+ }
+
+ // Not idempotent: only do once!
+ std::filesystem::path getTruePath(std::filesystem::path virtPath) {
+ std::filesystem::path p;
+ auto virtPaths = getVirtDirs();
+ if(std::find(virtPaths.begin(), virtPaths.end(), *virtPath.begin()) != virtPaths.end()) {
+ p = settings::getString(*virtPath.begin());
+ // Erase root (part to be replaced by virtPaths)
+ std::filesystem::path tmp;
+ auto it = virtPath.begin();
+ while(++it != virtPath.end()) {
+ tmp /= *it;
+ }
+ virtPath = tmp;
+ } else {
+ p = settings::getString("savedir");
+ }
+ p /= virtPath;
+ if(std::filesystem::directory_entry(p.string() + ".json").is_regular_file()) return p.string() + ".json";
+ return p;
+ }
+}
diff --git a/src/cmd.h b/src/cmd.h
new file mode 100644
index 0000000..6a8b738
--- /dev/null
+++ b/src/cmd.h
@@ -0,0 +1,28 @@
+#pragma once
+#include <vector>
+#include <string>
+#include <filesystem>
+
+namespace cmd {
+ // Corresponds to commands
+ std::string usage(const std::string& exename);
+ std::string list(std::vector<std::string> args);
+ std::string mkdir(std::vector<std::string> args);
+ std::string cp(std::vector<std::string> args);
+ std::string mv(std::vector<std::string> args);
+ std::string rm(std::vector<std::string> args);
+ std::string attacks(std::vector<std::string> args);
+ std::string roll(std::vector<std::string> args);
+ std::string heal(std::vector<std::string> args);
+ std::string damage(std::vector<std::string> args, std::vector<std::string> flags);
+ std::string reset(std::vector<std::string> args);
+ std::string set(std::vector<std::string> args);
+ std::string add(std::vector<std::string> args);
+ std::string del(std::vector<std::string> args);
+
+ // Command-centric helpers
+
+ // Not idempotent: only do once!
+ std::filesystem::path getTruePath(std::filesystem::path virtPath);
+ std::vector<std::string> getVirtDirs(void);
+}
diff --git a/src/cmd_fsops.cc b/src/cmd_fsops.cc
new file mode 100644
index 0000000..eca505e
--- /dev/null
+++ b/src/cmd_fsops.cc
@@ -0,0 +1,85 @@
+#include "cmd.h"
+#include "utils.h"
+#include "entry.h"
+#include <filesystem>
+#include <sstream>
+
+namespace fs = std::filesystem;
+
+namespace cmd {
+ std::string list(const fs::path& p) {
+ std::stringstream text;
+ if(p.empty()) {
+ // Print read-only dirs
+ for(std::string name : getVirtDirs()) {
+ text << name << "/ (read only)" << std::endl;
+ }
+ }
+ fs::path truePath = getTruePath(p);
+ if(fs::directory_entry(truePath).is_regular_file()) {
+ text << utils::instantiate<entry::Entry>(truePath)->getText() << std::endl;
+ }
+ else if(fs::directory_entry(truePath).is_directory()) {
+ for(fs::directory_entry de : fs::directory_iterator(truePath)) {
+ if(de.is_directory()) {
+ text << de.path().filename().string() << "/" << std::endl;
+ } else {
+ text << de.path().stem().string() << std::endl;
+ }
+ }
+ }
+ else {
+ text << "Unknown path " << p << std::endl;
+ }
+ return text.str();
+ }
+
+ std::string list(std::vector<std::string> args) {
+ std::stringstream text;
+ if(args.empty()) {
+ text << list("");
+ } else {
+ for(std::string dir : args) {
+ text << list(dir);
+ }
+ }
+ return text.str();
+ }
+
+ std::string mkdir(std::vector<std::string> args) {
+ for(std::string s : args) {
+ fs::create_directories(getTruePath(s));
+ }
+ return "";
+ }
+
+ void cp(fs::path src, fs::path dest) {
+ if(fs::directory_entry(src).is_regular_file()) {
+ utils::saveJson(*utils::instantiate<entry::Entry>(src), dest);
+ } else {
+ mkdir({dest});
+ for(fs::directory_entry de : fs::directory_iterator(src)) {
+ cp(de.path(), dest / de.path().filename());
+ }
+ }
+ }
+
+ std::string cp(std::vector<std::string> args) {
+ // Operate by intantiating and saving
+ // We do recursive!
+ cp(getTruePath(args[0]), getTruePath(args[1]));
+ return "";
+ }
+
+ std::string mv(std::vector<std::string> args) {
+ fs::rename(getTruePath(args[0]), getTruePath(args[1]));
+ return "";
+ }
+
+ std::string rm(std::vector<std::string> args) {
+ for(std::string s : args) {
+ fs::remove_all(getTruePath(s));
+ }
+ return "";
+ }
+}
diff --git a/src/cmd_manipulate.cc b/src/cmd_manipulate.cc
new file mode 100644
index 0000000..fe59ebf
--- /dev/null
+++ b/src/cmd_manipulate.cc
@@ -0,0 +1,186 @@
+#include "cmd.h"
+#include "utils.h"
+#include "creature.h"
+#include "item.h"
+#include "spellcasting.h"
+#include <sstream>
+#include <memory>
+
+namespace fs = std::filesystem;
+
+namespace cmd {
+ std::string healOrDamage(bool heal, std::vector<std::string> args, std::vector<std::string> flags) {
+ std::stringstream text;
+ std::vector<rules::Qualifier> qualifiers;
+ if(! heal) {
+ for(auto flag : flags) {
+ if(flag == "m" || flag == "magical") {
+ qualifiers.push_back(rules::Qualifier::Magical());
+ } else if(flag == "s" || flag == "silvered") {
+ qualifiers.push_back(rules::Qualifier::Silvered());
+ } else if(flag == "a" || flag == "adamantine") {
+ qualifiers.push_back(rules::Qualifier::Adamantine());
+ }
+ }
+ }
+ fs::path p = getTruePath(args[0]);
+ auto c = utils::instantiate<creature::Creature>(p);
+ int amnt = utils::parseInt(args[1]);
+ int initHP = c->getHP();
+ std::string dmgType = "force";
+ if(heal) {
+ c->applyHealing(amnt);
+ } else {
+ if(args.size() == 3) {
+ dmgType = args[2];
+ }
+ c->applyDamage(amnt, dmgType, qualifiers);
+ }
+ text << (heal? "Healing " : "Damaging ") << c->getGivenName() << " the " << c->getCreatureName() << " by " << amnt;
+ if(! heal) {
+ std::string qualsString;
+ std::vector<std::string> positiveQuals;
+ for(auto qual : qualifiers) {
+ positiveQuals.push_back(qual.getPositive());
+ }
+ if(! positiveQuals.empty()) {
+ qualsString = " " + utils::join(positiveQuals, ", ");
+ }
+ text << qualsString << " " << dmgType << " damage";
+ }
+ text << ". HP: " << initHP << " -> " << c->getHP() << "." << std::endl;
+ utils::saveJson(*c, p);
+ return text.str();
+ }
+
+ std::string heal(std::vector<std::string> args) {
+ return healOrDamage(true, args, {});
+ }
+
+ std::string damage(std::vector<std::string> args, std::vector<std::string> flags) {
+ return healOrDamage(false, args, flags);
+ }
+
+ std::string reset(std::vector<std::string> args) {
+ for(std::string s : args) {
+ fs::path p = getTruePath(s);
+ auto c = utils::instantiate<creature::Creature>(p);
+ c->longRest();
+ utils::saveJson(*c, p);
+ }
+ return "";
+ }
+
+ std::string set(std::vector<std::string> args) {
+ if(args.size() < 3) {
+ throw std::runtime_error("Subcommand 'set' requires at least 3 arguments");
+ }
+ fs::path p = getTruePath(args[0]);
+ args.erase(args.begin()); // remove path from args
+ auto c = utils::instantiate<creature::Creature>(p);
+ if(args[0] == "name") {
+ args.erase(args.begin()); // remove "name" from args
+ c->setGivenName(utils::join(args, " "));
+ } else if(args[0] == "proficiency") {
+ c->setProficiency(utils::parseInt(args[1]));
+ } else {
+ // Either an ability or a skill. If skill, then it could be multiple words long.
+ std::string toSet = args.back();
+ args.erase(--args.end());
+ std::string abilityOrSkill = utils::join(args, " ");
+ rules::Skill skill = rules::tryGetAbilityOrSkill<rules::Skill>(abilityOrSkill);
+ rules::Ability ability = rules::tryGetAbilityOrSkill<rules::Ability>(abilityOrSkill);
+ if(skill) {
+ // ensure lower case
+ utils::lower(toSet);
+ int level = -1;
+ if(toSet == "none") level = 0;
+ else if(toSet == "proficient") level = 1;
+ else if(toSet == "expert") level = 2;
+ if(level == -1) {
+ throw std::runtime_error("Skill levels can be set to none, proficient, or expert, but " + toSet + " was given.");
+ }
+ c->setProfLevel(skill, level);
+ } else if(ability) {
+ c->setScore(ability, utils::parseInt(toSet));
+ } else {
+ throw std::runtime_error("Subcommand 'set' expected an ability, skill, proficiency, or name field to set, but was given " + abilityOrSkill);
+ }
+ }
+ utils::saveJson(*c, p);
+ return "";
+ }
+
+ std::string add(std::vector<std::string> args) {
+ std::stringstream text;
+ fs::path p = getTruePath(args[0]);
+ args.erase(args.begin()); // remove path from args
+ auto c = utils::instantiate<creature::Creature>(p);
+ std::string addName = utils::join(args, " ");
+ std::shared_ptr<entry::Entry> ent;
+ fs::path path = getTruePath(addName);
+ if(fs::directory_entry(path).exists()) {
+ ent = utils::instantiate<entry::Entry>(path);
+ } else {
+ ent = entry::Entry::create(utils::findByName(addName));
+ }
+ // Determine if it is an item or a spell
+ auto i = std::dynamic_pointer_cast<entry::Item>(ent);
+ if(i) {
+ c->addInventoryItem(i);
+ } else {
+ auto s = std::dynamic_pointer_cast<entry::Spell>(ent);
+ if(s) {
+ c->addSpell(s);
+ } else {
+ throw std::runtime_error("Could not add the " + ent->getType() + " " + ent->getName() + " to " + c->getGivenName() + " the " + c->getName() + ": Requires a weapon, armor, or spell, but received object of type " + ent->getType());
+ }
+ }
+ utils::saveJson(*c, p);
+ text << "Added the " << ent->getType() << " " << ent->getName() << " to " << c->getGivenName() << " the " << c->getName() << std::endl;
+ return text.str();
+ }
+
+ std::string del(std::vector<std::string> args) {
+ std::stringstream text;
+ fs::path p = getTruePath(args[0]);
+ args.erase(args.begin()); // remove path from args
+ auto c = utils::instantiate<creature::Creature>(p);
+ //Atempt to load the item if it's a path
+ std::string itemName = utils::join(args, " ");
+ try {
+ auto i = utils::instantiate<entry::Entry>(getTruePath(itemName));
+ if(i) {
+ itemName = i->getName();
+ }
+ } catch(std::exception& e) {} // eat.
+ utils::lower(itemName);
+ // Loop through all of c's stuff, searching for itemName
+ std::shared_ptr<entry::Entry> removed;
+ for(auto item : c->getInventory()) {
+ std::string name = item->getName();
+ if(utils::lower(name) == itemName) {
+ c->removeInventoryItem(item);
+ removed = item;
+ break;
+ }
+ }
+ if(! removed) {
+ for(auto spell : c->getSpellcasting()->getSpells()) {
+ std::string name = spell->getName();
+ if(utils::lower(name) == itemName) {
+ c->removeSpell(spell);
+ removed = spell;
+ break;
+ }
+ }
+ }
+ utils::saveJson(*c, p);
+ if(removed) {
+ text << "Successfully removed the " << removed->Entry::getType() << " " << removed->getName() << std::endl;
+ } else {
+ text << "Could not find any inventory item nor spell by that name" << std::endl;
+ }
+ return text.str();
+ }
+}
diff --git a/src/cmd_query.cc b/src/cmd_query.cc
new file mode 100644
index 0000000..7d4e6d6
--- /dev/null
+++ b/src/cmd_query.cc
@@ -0,0 +1,49 @@
+#include "cmd.h"
+#include "utils.h"
+#include "creature.h"
+#include "dice.h"
+#include "weapon.h"
+#include <sstream>
+
+namespace cmd {
+ std::string attacks(std::vector<std::string> args) {
+ std::stringstream text;
+ auto c = utils::instantiate<creature::Creature>(getTruePath(args[0]));
+ for(auto w : creature::getAttacks(*c)) {
+ text << w->getName() << std::endl;
+ }
+ return text.str();
+ }
+
+ std::string roll(std::vector<std::string> args) {
+ std::stringstream text;
+ auto c = utils::instantiate<creature::Creature>(getTruePath(args[0]));
+ args.erase(args.begin());
+ std::string rollName = utils::join(args, " ");
+ utils::lower(rollName);
+ int rolled = dice::roll(20);
+ auto printResults = [&text](std::string name, std::string type, int rolled, int bonus) {
+ text << name << " " << type << ": " << rolled << " (d20) + " << bonus << " (" << name << " " << type << " bonus) = " << rolled + bonus << std::endl;
+ };
+ // Search through skills, saves, and attacks to roll
+ rules::Skill skill = rules::tryGetAbilityOrSkill<rules::Skill>(rollName);
+ rules::Ability ability = rules::tryGetAbilityOrSkill<rules::Ability>(rollName);
+ if(skill) {
+ printResults(skill.getName(), "check", rolled, c->getSkillBonus(skill));
+ } else if(ability) {
+ printResults(ability.getFull(), "save", rolled, c->getAbilitySaveBonus(ability));
+ } else {
+ for(auto w : creature::getAttacks(*c)) {
+ if(w->getName() == rollName) {
+ text << w->getText(*c) << std::endl;
+ int abilityBonus = c->getBonus(creature::getBestAbility(getAbilityOptions(*w), *c));
+ int bonus = abilityBonus + c->getProficiency();
+ printResults(w->getName(), "attack", rolled, bonus);
+ text << " on hit: " << entry::formatDmg(*w, *c) << std::endl;
+ break;
+ }
+ }
+ }
+ return text.str();
+ }
+}
diff --git a/src/cmd_usage.cc b/src/cmd_usage.cc
new file mode 100644
index 0000000..03dae56
--- /dev/null
+++ b/src/cmd_usage.cc
@@ -0,0 +1,47 @@
+#include "cmd.h"
+#include <sstream>
+#include <string>
+
+namespace cmd {
+ std::string usage(const std::string& exename) {
+ std::stringstream text;
+ text << "Usage:" << std::endl;
+ std::string indOpt = " " + exename + " ";
+ std::string indDesc = " ";
+ text << indOpt << "[ls] [subfolder]" << std::endl;
+ text << indDesc << "List creatures and objects." << std::endl;
+ text << indOpt << "cp old-path new-path" << std::endl;
+ text << indDesc << "Copy old-path to new-path, instantiating it." << std::endl;
+ text << indOpt << "mkdir path" << std::endl;
+ text << indDesc << "Make a new directory for holding creatures and objects." << std::endl;
+ text << indOpt << "mv old-path new-path" << std::endl;
+ text << indDesc << "Move old-path to new-path." << std::endl;
+ text << indOpt << "rm path" << std::endl;
+ text << indDesc << "Remove existing creature, object, or directory." << std::endl;
+ text << indOpt << "attacks path" << std::endl;
+ text << indDesc << "List attacks available for a creature" << std::endl;
+ text << indOpt << "roll path name" << std::endl;
+ text << indDesc << "Roll a skill check, save, or attack." << std::endl;
+ text << indOpt << "damage path amount [type] [--magical,-m] [--silvered,-s] [--adamantine,-a]" << std::endl;
+ text << indDesc << "Damage creature by amount. Type defaults to \"force\"." << std::endl;
+ text << indOpt << "heal path amount" << std::endl;
+ text << indDesc << "Heal creature by amount." << std::endl;
+ text << indOpt << "reset path" << std::endl;
+ text << indDesc << "Reset creature to full health (as if completing a long rest)." << std::endl;
+ text << indOpt << "set path field value" << std::endl;
+ text << indDesc << "Set a field to a new value, where field is any of:" << std::endl;
+ text << indDesc << " ability (str, dex, con, int, wis, cha); value is new ability score" << std::endl;
+ text << indDesc << " skill (athletics, \"sleight of hand\", etc.); value is (none|proficient|expert)" << std::endl;
+ text << indDesc << " proficiency; value is new proficency bonus." << std::endl;
+ text << indDesc << " name; value is new given name." << std::endl;
+ text << indOpt << "edit path" << std::endl;
+ text << indDesc << "Edit notes associated with creature." << std::endl;
+ text << indOpt << "add path entry" << std::endl;
+ text << indDesc << "Add entry to creature, where entry is an item or spell." << std::endl;
+ text << indOpt << "del path entry" << std::endl;
+ text << indDesc << "Delete entry from creature, where entry is an item or spell." << std::endl;
+ text << indOpt << "help" << std::endl;
+ text << indDesc << "Show this help." << std::endl;
+ return text.str();
+ }
+}
diff --git a/src/creature.cc b/src/creature.cc
index 21943fe..dcd2324 100644
--- a/src/creature.cc
+++ b/src/creature.cc
@@ -25,7 +25,7 @@ namespace creature {
}
Creature::Creature(const json& data, const json& base)
- : Entry(base), inventory(json2ptrvec<entry::Item>(data["inventory"])), stats(makeMap<rules::Ability>(data["stats"])), skills(makeMap<rules::Skill>(data["skills"])), proficiency(data["prof"]), size(data["size"]), alignment(data["alignment"]), hdCount(data["hit_die_count"]), hdSides(data["hit_die_sides"]), speed(data["speed"]), saves(json2vec<rules::Ability>(data["saves"])), langs(data["langs"]), cr(data["cr"]), natArmorName(data["natural_armor"]["name"]), natArmorBonus(data["natural_armor"]["bonus"]), dmgImmunities(json2vec<dmgType>(data["d_immunities"])), dmgResistances(json2vec<dmgType>(data["d_resistances"])), dmgVulnerabilities(json2vec<dmgType>(data["d_vulnerabilities"])), condImmunities(json2vec<dmgType>(data["c_immunities"])), features(json2ptrvec<entry::Feature>(data["features"]))
+ : Entry(base), inventory(utils::json2ptrvec<entry::Item>(data["inventory"])), stats(makeMap<rules::Ability>(data["stats"])), skills(makeMap<rules::Skill>(data["skills"])), proficiency(data["prof"]), size(data["size"]), alignment(data["alignment"]), hdCount(data["hit_die_count"]), hdSides(data["hit_die_sides"]), speed(data["speed"]), saves(utils::json2vec<rules::Ability>(data["saves"])), langs(data["langs"]), cr(data["cr"]), natArmorName(data["natural_armor"]["name"]), natArmorBonus(data["natural_armor"]["bonus"]), dmgImmunities(utils::json2vec<dmgType>(data["d_immunities"])), dmgResistances(utils::json2vec<dmgType>(data["d_resistances"])), dmgVulnerabilities(utils::json2vec<dmgType>(data["d_vulnerabilities"])), condImmunities(utils::json2vec<dmgType>(data["c_immunities"])), features(utils::json2ptrvec<entry::Feature>(data["features"]))
{
// Initialize names and hp
if(((map<string, json>) data).contains("givenName")) {
@@ -64,8 +64,8 @@ namespace creature {
data["givenName"] = givenName;
data["hpMax"] = hpMax;
data["hp"] = hp;
- data["inventory"] = ptrvec2json(inventory);
- data["features"] = ptrvec2json(features);
+ data["inventory"] = utils::ptrvec2json(inventory);
+ data["features"] = utils::ptrvec2json(features);
return data;
}
diff --git a/src/creature.h b/src/creature.h
index cdbd2b9..0970f04 100644
--- a/src/creature.h
+++ b/src/creature.h
@@ -21,7 +21,7 @@ namespace creature {
class dmgType : public Jsonable {
public:
- dmgType(const json& data) : type(data["type"]), qualifiers(json2vec<rules::Qualifier>(data["qualifiers"])) {}
+ dmgType(const json& data) : type(data["type"]), qualifiers(utils::json2vec<rules::Qualifier>(data["qualifiers"])) {}
std::string type;
std::vector<rules::Qualifier> qualifiers;
std::string getText() const {
diff --git a/src/dmtool.bash b/src/dmtool.bash
index 41f70e9..ffc75f7 100644
--- a/src/dmtool.bash
+++ b/src/dmtool.bash
@@ -60,35 +60,80 @@ _dmtool_complete_entries () {
fi
}
+_dmtool_skills()
+{
+ echo -e "athletics\nacrobatics\nstealth\narcana\nhistory\ninvestigation\nnature\nreligion\ninsight\nmedicine\nperception\nsurvival\ndeception\nintimidation\nperformance\npersuasion\nsleight of hand\nanimal handling"
+}
+
+_dmtool_abilities()
+{
+ echo -e "str\ndex\ncon\nint\nwis\ncha"
+}
+
+_dmtool_complete_skills_abilities()
+{
+ local IFS=$'\n'
+ COMPREPLY+=($(compgen -W "$(_dmtool_skills)" -- ${cur}))
+ COMPREPLY+=($(compgen -W "$(_dmtool_abilities)" -- ${cur}))
+}
+
_dmtool()
{
- COMPREPLY=()
- local cur="${COMP_WORDS[COMP_CWORD]}"
- local commands="ls cp mkdir mv rm roll damage heal reset set help"
- if [[ $COMP_CWORD -gt 1 ]]; then
- local lastarg="${COMP_WORDS[$COMP_CWORD-1]}"
- case "${COMP_WORDS[1]}" in
- ls|mkdir|rm|reset)
- _dmtool_complete_entries
- ;;
- cp|mv)
- if [[ $COMP_CWORD -le 3 ]]; then
- _dmtool_complete_entries
+ COMPREPLY=()
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ local commands="ls cp mkdir mv rm attacks roll damage heal reset set edit add del help"
+ if [[ $COMP_CWORD -gt 1 ]]; then
+ local lastarg="${COMP_WORDS[$COMP_CWORD-1]}"
+ case "${COMP_WORDS[1]}" in
+ ls|mkdir|rm|reset)
+ _dmtool_complete_entries
+ ;;
+ cp|mv|add)
+ if [[ $COMP_CWORD -le 3 ]]; then
+ _dmtool_complete_entries
+ fi
+ ;;
+ attacks|roll|damage|heal|set|edit|del)
+ if [[ $COMP_CWORD -le 2 ]]; then
+ _dmtool_complete_entries
+ else
+ case "${COMP_WORDS[1]}" in
+ roll)
+ _dmtool_complete_skills_abilities
+ # Add in attacks
+ local IFS=$'\n'
+ opts="$(${COMP_WORDS[0]} attacks ${COMP_WORDS[2]})"
+ COMPREPLY+=($(compgen -W "$opts" -- ${cur}))
+ ;;
+ damage)
+ COMPREPLY+=($(compgen -W "--magical -m --silvered -s --adamantine -a" -- ${cur}))
+ if [[ $COMP_CWORD -eq 4 ]]; then
+ COMPREPLY+=($(compgen -W "slashing piercing bludgeoning poison acid fire cold radiant necrotic lightning thunder force psychic" -- ${cur}))
+ fi
+ ;;
+ set)
+ if [[ $COMP_CWORD -eq 3 ]]; then
+ _dmtool_complete_skills_abilities
+ COMPREPLY+=($(compgen -W "proficiency name" -- ${cur}))
+ elif [[ $COMP_CWORD -eq 4 ]]; then
+ local skills="$(_dmtool_skills)"
+ if [[ "$skills" =~ "$lastarg" ]]; then
+ COMPREPLY+=($(compgen -W "none proficient expert" -- ${cur}))
fi
- ;;
- roll|damage|heal|set|add)
- if [[ $COMP_CWORD -le 2 ]]; then
- _dmtool_complete_entries
- else
- # Other various stuff
- :
- fi
- ;;
- esac
- else
- COMPREPLY+=($(compgen -W "${commands}" -- ${cur}))
- _dmtool_complete_entries 1
- fi
+ fi
+ ;;
+ del)
+ #TODO: Add items and spells
+ _dmtool_complete_skills_abilities
+ ;;
+ esac
+ fi
+ ;;
+ esac
+ else
+ COMPREPLY+=($(compgen -W "${commands}" -- ${cur}))
+ _dmtool_complete_entries 1
+ fi
}
complete -o filenames -F _dmtool dmtool
diff --git a/src/dmtool.cc b/src/dmtool.cc
index ba47616..e9d8ed6 100644
--- a/src/dmtool.cc
+++ b/src/dmtool.cc
@@ -1,396 +1,15 @@
-#include "entry.h"
-#include "settings.h"
-#include "creature.h"
-#include "dice.h"
-#include "weapon.h"
-#include "spell.h"
-#include "spellcasting.h"
+#include "cmd.h"
+#include "utils.h"
#include <iostream>
-#include <vector>
-#include <string>
-#include <filesystem>
-#include <system_error>
#include <exception>
-#include <memory>
#include <algorithm>
-
-namespace fs = std::filesystem;
-
-void usage(const std::string& exename) {
- std::cout << "Usage:" << std::endl;
- std::string indOpt = " " + exename + " ";
- std::string indDesc = " ";
- std::cout << indOpt << "[ls] [subfolder]" << std::endl;
- std::cout << indDesc << "List creatures and objects." << std::endl;
- std::cout << indOpt << "cp old-path new-path" << std::endl;
- std::cout << indDesc << "Copy old-path to new-path, instantiating it." << std::endl;
- std::cout << indOpt << "mkdir path" << std::endl;
- std::cout << indDesc << "Make a new directory for holding creatures and objects." << std::endl;
- std::cout << indOpt << "mv old-path new-path" << std::endl;
- std::cout << indDesc << "Move old-path to new-path." << std::endl;
- std::cout << indOpt << "rm path" << std::endl;
- std::cout << indDesc << "Remove existing creature, object, or directory." << std::endl;
- std::cout << indOpt << "roll path name" << std::endl;
- std::cout << indDesc << "Roll a skill check, save, or attack." << std::endl;
- std::cout << indOpt << "damage path amount [type] [--magical,-m] [--silvered,-s] [--adamantine,-a]" << std::endl;
- std::cout << indDesc << "Damage creature by amount. Type defaults to \"force\"." << std::endl;
- std::cout << indOpt << "heal path amount" << std::endl;
- std::cout << indDesc << "Heal creature by amount." << std::endl;
- std::cout << indOpt << "reset path" << std::endl;
- std::cout << indDesc << "Reset creature to full health (as if completing a long rest)." << std::endl;
- std::cout << indOpt << "set path field value" << std::endl;
- std::cout << indDesc << "Set a field to a new value, where field is any of:" << std::endl;
- std::cout << indDesc << " ability (str, dex, con, int, wis, cha); value is new ability score" << std::endl;
- std::cout << indDesc << " skill (athletics, \"sleight of hand\", etc.); value is (none|proficient|expert)" << std::endl;
- std::cout << indDesc << " proficiency; value is new proficency bonus." << std::endl;
- std::cout << indDesc << " name; value is new given name." << std::endl;
- std::cout << indOpt << "edit path" << std::endl;
- std::cout << indDesc << "Edit notes associated with creature." << std::endl;
- std::cout << indOpt << "add path entry" << std::endl;
- std::cout << indDesc << "Add entry to creature, where entry is an item or spell." << std::endl;
- std::cout << indOpt << "del path entry" << std::endl;
- std::cout << indDesc << "Delete entry from creature, where entry is an item or spell." << std::endl;
- std::cout << indOpt << "help" << std::endl;
- std::cout << indDesc << "Show this help." << std::endl;
-}
-
-template<typename T> std::shared_ptr<T> instantiate(const fs::path& path) {
- std::shared_ptr<entry::Entry> ent;
- try {
- ent = entry::Entry::create(utils::loadJson(path));
- } catch(std::exception& e) {
- if(fs::directory_entry(path).exists()) {
- throw std::runtime_error("Invalid json: " + path.string());
- } else {
- throw std::runtime_error("No such file nor directory: " + path.string());
- }
- }
- std::shared_ptr<T> t = std::dynamic_pointer_cast<T>(ent);
- if(! t) {
- throw std::runtime_error("Wrong instance type: " + ent->getType());
- }
- return t;
-}
-
-int parseInt(const std::string& s) {
- try {
- return std::stoi(s);
- } catch(std::exception& e) {
- throw std::runtime_error("An integer was expected but " + s + " was given");
- }
-}
-
-void save(const std::shared_ptr<entry::Entry>& e, const fs::path& path) {
- utils::saveJson(*e, path);
-}
-
-void print(const fs::path& path) {
- std::cout << instantiate<entry::Entry>(path)->getText() << std::endl;
-}
-
-fs::path getBaseDir() {
- return settings::getString("savedir");
-}
-
-fs::path eraseRoot(fs::path& src) {
- if(src.empty()) return src;
- fs::path tmp;
- auto it = src.begin();
- while(++it != src.end()) {
- tmp /= *it;
- }
- src = tmp;
- return src;
-}
-
-const std::vector<std::string> virtPaths({"weapons", "armor", "spells", "creatures"});
-
-fs::path getTruePath(fs::path virtPath) {
- fs::path p;
- if(std::find(virtPaths.begin(), virtPaths.end(), *virtPath.begin()) != virtPaths.end()) {
- p = settings::getString(*virtPath.begin());
- eraseRoot(virtPath);
- } else {
- p = getBaseDir();
- }
- p /= virtPath;
- if(fs::directory_entry(p.string() + ".json").is_regular_file()) return p.string() + ".json";
- return p;
-}
-
-// Ensure the system is set up correctly
-void initFS() {
- fs::directory_entry de = fs::directory_entry(getBaseDir());
- if(! de.exists()) {
- fs::create_directories(de);
- }
-}
-
-void list(const fs::path& p) {
- if(p.empty()) {
- // Print read-only dirs
- for(std::string name : virtPaths) {
- std::cout << name << "/ (read only)" << std::endl;
- }
- }
- fs::path truePath = getTruePath(p);
- if(fs::directory_entry(truePath).is_regular_file()) {
- print(truePath);
- }
- else if(fs::directory_entry(truePath).is_directory()) {
- for(fs::directory_entry de : fs::directory_iterator(truePath)) {
- if(de.is_directory()) {
- std::cout << de.path().filename().string() << "/" << std::endl;
- } else {
- std::cout << de.path().stem().string() << std::endl;
- }
- }
- }
- else {
- std::cerr << "Unknown path " << p << std::endl;
- }
-}
-
-void list(std::vector<std::string> args) {
- if(args.empty()) {
- list("");
- } else {
- for(std::string dir : args) {
- list(dir);
- }
- }
-}
-
-void mkdir(std::vector<std::string> args) {
- for(std::string s : args) {
- fs::create_directories(getTruePath(s));
- }
-}
-
-void cp(fs::path src, fs::path dest) {
- if(fs::directory_entry(src).is_regular_file()) {
- save(instantiate<entry::Entry>(src), dest);
- } else {
- mkdir({dest});
- for(fs::directory_entry de : fs::directory_iterator(src)) {
- cp(de.path(), dest / de.path().filename());
- }
- }
-
-}
-
-void cp(std::vector<std::string> args) {
- // Operate by intantiating and saving
- // We do recursive!
- cp(getTruePath(args[0]), getTruePath(args[1]));
-}
-
-void mv(std::vector<std::string> args) {
- fs::rename(getTruePath(args[0]), getTruePath(args[1]));
-}
-
-void rm(std::vector<std::string> args) {
- for(std::string s : args) {
- fs::remove_all(getTruePath(s));
- }
-}
-
-template<typename T> T tryGetAbilityOrSkill(std::string src) {
- try {
- return T::fromString(src);
- } catch(std::exception& e) {} // eat.
- return T();
-}
-
-void roll(std::vector<std::string> args) {
- auto c = instantiate<creature::Creature>(getTruePath(args[0]));
- args.erase(args.begin());
- std::string rollName = utils::join(args, " ");
- utils::lower(rollName);
- int rolled = dice::roll(20);
- auto printResults = [](std::string name, std::string type, int rolled, int bonus) {
- std::cout << name << " " << type << ": " << rolled << " (d20) + " << bonus << " (" << name << " " << type << " bonus) = " << rolled + bonus << std::endl;
- };
- // Search through skills, saves, and attacks to roll
- rules::Skill skill = tryGetAbilityOrSkill<rules::Skill>(rollName);
- rules::Ability ability = tryGetAbilityOrSkill<rules::Ability>(rollName);
- if(skill) {
- printResults(skill.getName(), "check", rolled, c->getSkillBonus(skill));
- } else if(ability) {
- printResults(ability.getFull(), "save", rolled, c->getAbilitySaveBonus(ability));
- } else {
- for(auto w : creature::getAttacks(*c)) {
- if(w->getName() == rollName) {
- std::cout << w->getText(*c) << std::endl;
- int abilityBonus = c->getBonus(creature::getBestAbility(getAbilityOptions(*w), *c));
- int bonus = abilityBonus + c->getProficiency();
- printResults(w->getName(), "attack", rolled, bonus);
- std::cout << " on hit: " << entry::formatDmg(*w, *c) << std::endl;
- return;
- }
- }
- }
-}
-
-void healOrDamage(bool heal, std::vector<std::string> args) {
- std::vector<rules::Qualifier> qualifiers;
- if(! heal) {
- auto it = args.begin();
- while(it != args.end()) {
- if(*it == "-m" || *it == "--magical") {
- qualifiers.push_back(rules::Qualifier::Magical());
- args.erase(it);
- } else if(*it == "-s" || *it == "--silvered") {
- qualifiers.push_back(rules::Qualifier::Silvered());
- args.erase(it);
- } else if(*it == "-a" || *it == "--adamantine") {
- qualifiers.push_back(rules::Qualifier::Adamantine());
- args.erase(it);
- } else {
- it++;
- }
- }
- }
- fs::path p = getTruePath(args[0]);
- auto c = instantiate<creature::Creature>(p);
- int amnt = parseInt(args[1]);
- int initHP = c->getHP();
- std::string dmgType = "force";
- if(heal) {
- c->applyHealing(amnt);
- } else {
- if(args.size() == 3) {
- dmgType = args[2];
- }
- c->applyDamage(amnt, dmgType, qualifiers);
- }
- std::cout << (heal? "Healing " : "Damaging ") << c->getGivenName() << " the " << c->getCreatureName() << " by " << amnt;
- if(! heal) {
- std::vector<std::string> positiveQuals;
- for(auto qual : qualifiers) {
- positiveQuals.push_back(qual.getPositive());
- }
- std::cout << " " << utils::join(positiveQuals, ", ") << " " << dmgType << " damage";
- }
- std::cout << ". HP: " << initHP << " -> " << c->getHP() << "." << std::endl;
- save(c, p);
-}
-
-void reset(std::vector<std::string> args) {
- for(std::string s : args) {
- fs::path p = getTruePath(s);
- auto c = instantiate<creature::Creature>(p);
- c->longRest();
- save(c, p);
- }
-
-}
-
-void set(std::vector<std::string> args) {
- if(args.size() < 3) {
- throw std::runtime_error("Subcommand 'set' requires at least 3 arguments");
- }
- fs::path p = getTruePath(args[0]);
- args.erase(args.begin()); // remove path from args
- auto c = instantiate<creature::Creature>(p);
- if(args[0] == "name") {
- args.erase(args.begin()); // remove "name" from args
- c->setGivenName(utils::join(args, " "));
- } else if(args[0] == "proficiency") {
- c->setProficiency(parseInt(args[1]));
- } else {
- // Either an ability or a skill. If skill, then it could be multiple words long.
- std::string toSet = args.back();
- args.erase(--args.end());
- std::string abilityOrSkill = utils::join(args, " ");
- rules::Skill skill = tryGetAbilityOrSkill<rules::Skill>(abilityOrSkill);
- rules::Ability ability = tryGetAbilityOrSkill<rules::Ability>(abilityOrSkill);
- if(skill) {
- // ensure lower case
- utils::lower(toSet);
- int level = -1;
- if(toSet == "none") level = 0;
- else if(toSet == "proficient") level = 1;
- else if(toSet == "expert") level = 2;
- if(level == -1) {
- throw std::runtime_error("Skill levels can be set to none, proficient, or expert, but " + toSet + " was given.");
- }
- c->setProfLevel(skill, level);
- } else if(ability) {
- c->setScore(ability, parseInt(toSet));
- } else {
- throw std::runtime_error("Subcommand 'set' expected an ability, skill, proficiency, or name field to set, but was given " + abilityOrSkill);
- }
- }
- save(c, p);
-}
-
-void add(std::vector<std::string> args) {
- fs::path p = getTruePath(args[0]);
- args.erase(args.begin()); // remove path from args
- auto c = instantiate<creature::Creature>(p);
- std::string addName = utils::join(args, " ");
- std::shared_ptr<entry::Entry> ent;
- fs::path path = getTruePath(addName);
- if(fs::directory_entry(path).exists()) {
- ent = instantiate<entry::Entry>(path);
- } else {
- ent = entry::Entry::create(utils::findByName(addName));
- }
- // Determine if it is an item or a spell
- auto i = std::dynamic_pointer_cast<entry::Item>(ent);
- if(i) {
- c->addInventoryItem(i);
- } else {
- auto s = std::dynamic_pointer_cast<entry::Spell>(ent);
- if(s) {
- c->addSpell(s);
- } else {
- throw std::runtime_error("Could not add the " + ent->getType() + " " + ent->getName() + " to " + c->getGivenName() + " the " + c->getName() + ": Requires a weapon, armor, or spell, but received object of type " + ent->getType());
- }
- }
- save(c, p);
- std::cout << "Added the " << ent->getType() << " " << ent->getName() << " to " << c->getGivenName() << " the " << c->getName() << std::endl;
-}
-
-void del(std::vector<std::string> args) {
- fs::path p = getTruePath(args[0]);
- args.erase(args.begin()); // remove path from args
- auto c = instantiate<creature::Creature>(p);
- std::string itemName = utils::join(args, " ");
- utils::lower(itemName);
- // Loop through all of c's stuff, searching for itemName
- std::shared_ptr<entry::Entry> removed;
- for(auto item : c->getInventory()) {
- std::string name = item->getName();
- if(utils::lower(name) == itemName) {
- c->removeInventoryItem(item);
- removed = item;
- break;
- }
- }
- if(! removed) {
- for(auto spell : c->getSpellcasting()->getSpells()) {
- std::string name = spell->getName();
- if(utils::lower(name) == itemName) {
- c->removeSpell(spell);
- removed = spell;
- break;
- }
- }
- }
- save(c, p);
- if(removed) {
- std::cout << "Successfully removed the " << removed->Entry::getType() << " " << removed->getName() << std::endl;
- } else {
- std::cout << "Could not find any inventory item nor spell by that name" << std::endl;
- }
-}
+#include <map>
const std::map<std::string, std::vector<int>> nargs({
{"cp", {2}},
{"mv", {2}},
- {"damage", {2, 3, 4, 5, 6}},
+ {"attacks", {1}},
+ {"damage", {2, 3}},
{"heal", {2}},
});
@@ -403,17 +22,31 @@ void checkArgs(std::string cmd, std::vector<std::string> args) {
}
}
+// Removes flags from args (in-place) and returns vector of flags
+std::vector<std::string> extractFlags(std::vector<std::string>& args) {
+ std::vector<std::string> ret;
+ auto it = args.begin();
+ while(it != args.end()) {
+ if((*it)[0] == '-') {
+ while((*it)[0] == '-') {
+ (*it).erase((*it).begin());
+ }
+ ret.push_back(*it);
+ args.erase(it);
+ } else {
+ it++;
+ }
+ }
+ return ret;
+}
+
int main(int argc, char *argv[]) {
std::string exename = argv[0];
std::vector<std::string> args(&argv[1], &argv[argc]);
- try {
- initFS();
- } catch(fs::filesystem_error& e) {
- std::cerr << e.what() << std::endl;
- return 1;
- }
+ std::vector<std::string> flags = extractFlags(args);
+ cmd::mkdir({cmd::getTruePath("")});
if(args.empty()) {
- list(args);
+ std::cout << cmd::list(args);
return 0;
}
std::string cmd = args[0];
@@ -421,20 +54,21 @@ int main(int argc, char *argv[]) {
args.erase(args.begin());
try {
checkArgs(cmd, args);
- if(cmd == "ls") list(args);
- else if(cmd == "cp") cp(args);
- else if(cmd == "mkdir") mkdir(args);
- else if(cmd == "mv") mv(args);
- else if(cmd == "rm") rm(args);
- else if(cmd == "roll") roll(args);
- else if(cmd == "damage") healOrDamage(false, args);
- else if(cmd == "heal") healOrDamage(true, args);
- else if(cmd == "reset") reset(args);
- else if(cmd == "set") set(args);
- else if(cmd == "add") add(args);
- else if(cmd == "del") del(args);
- else if(cmd == "help") usage(exename);
- else list(argsOrig);
+ if(cmd == "ls") std::cout << cmd::list(args);
+ else if(cmd == "cp") std::cout << cmd::cp(args);
+ else if(cmd == "mkdir") std::cout << cmd::mkdir(args);
+ else if(cmd == "mv") std::cout << cmd::mv(args);
+ else if(cmd == "rm") std::cout << cmd::rm(args);
+ else if(cmd == "attacks") std::cout << cmd::attacks(args);
+ else if(cmd == "roll") std::cout << cmd::roll(args);
+ else if(cmd == "damage") std::cout << cmd::damage(args, flags);
+ else if(cmd == "heal") std::cout << cmd::heal(args);
+ else if(cmd == "reset") std::cout << cmd::reset(args);
+ else if(cmd == "set") std::cout << cmd::set(args);
+ else if(cmd == "add") std::cout << cmd::add(args);
+ else if(cmd == "del") std::cout << cmd::del(args);
+ else if(cmd == "help") std::cout << cmd::usage(exename);
+ else std::cout << cmd::list(argsOrig);
} catch(std::exception& e) {
std::cerr << e.what() << std::endl;
return 1;
diff --git a/src/jsonable.h b/src/jsonable.h
index 0385fb1..7411efa 100644
--- a/src/jsonable.h
+++ b/src/jsonable.h
@@ -1,8 +1,5 @@
#pragma once
#include "json.hpp"
-#include "utils.h"
-#include <memory>
-#include <vector>
class Jsonable {
public:
@@ -10,33 +7,3 @@ class Jsonable {
operator nlohmann::json() const {return toJson();}
virtual ~Jsonable() {}
};
-
-template<typename T> std::vector<T> json2vec(const nlohmann::json& data) {
- using std::begin; using std::end;
- return std::vector<T>(begin(data), end(data));
-}
-
-template<typename T> std::vector<std::shared_ptr<T>> jsonList2ptrvec(const std::string& type, const std::vector<std::string>& names) {
- std::vector<std::shared_ptr<T>> ret;
- for(auto name : names) {
- auto j = utils::loadJson(type, name);
- ret.push_back(std::shared_ptr<T>(new T(j, j)));
- }
- return ret;
-}
-
-template<typename T> std::vector<std::shared_ptr<T>> json2ptrvec(const nlohmann::json& data) {
- std::vector<std::shared_ptr<T>> ret;
- for(nlohmann::json d : data) {
- ret.push_back(T::create(d));
- }
- return ret;
-}
-
-template<typename T> std::vector<nlohmann::json> ptrvec2json(std::vector<T> src) {
- std::vector<nlohmann::json> ret;
- for(T i : src) {
- ret.push_back(i->toJson());
- }
- return ret;
-}
diff --git a/src/rules.h b/src/rules.h
index f41f7ad..c1b130d 100644
--- a/src/rules.h
+++ b/src/rules.h
@@ -143,4 +143,11 @@ namespace rules {
std::ostream& operator<<(std::ostream& os, const Ability& a);
std::ostream& operator<<(std::ostream& os, const Skill& s);
std::ostream& operator<<(std::ostream& os, const Qualifier& q);
+
+ template<typename T> T tryGetAbilityOrSkill(std::string src) {
+ try {
+ return T::fromString(src);
+ } catch(std::exception& e) {} // eat.
+ return T();
+ }
}
diff --git a/src/spellcasting.h b/src/spellcasting.h
index 331a95f..6672dba 100644
--- a/src/spellcasting.h
+++ b/src/spellcasting.h
@@ -4,13 +4,14 @@
#include "spell.h"
#include "jsonable.h"
#include "rules.h"
+#include "utils.h"
#include <memory>
typedef nlohmann::json json;
namespace entry {
struct SlotLevel : public Jsonable {
- SlotLevel(const json& data) : numSlots(data["slots"]), spells(jsonList2ptrvec<Spell>("spells", data["spells"])) {}
+ SlotLevel(const json& data) : numSlots(data["slots"]), spells(utils::jsonList2ptrvec<Spell>("spells", data["spells"])) {}
SlotLevel() : numSlots(0) {}
virtual ~SlotLevel() {}
int numSlots;
@@ -31,7 +32,7 @@ namespace entry {
class Spellcasting : public Feature {
public:
- Spellcasting(const json& data, const json& base) : Feature(base), innate(data["innate"]), ability(rules::Ability(data["spellcasting_ability"])), spellsBySlot(json2ptrvec<SlotLevel>(data["levels"])) {}
+ Spellcasting(const json& data, const json& base) : Feature(base), innate(data["innate"]), ability(rules::Ability(data["spellcasting_ability"])), spellsBySlot(utils::json2ptrvec<SlotLevel>(data["levels"])) {}
// Can also be instantiated programatically
Spellcasting(const std::string& entry, const std::string& name, const std::string& type, const std::string& text, const rules::Ability& ability, bool isInnate) : Feature(entry, name, type, text), innate(isInnate), ability(ability) {}
virtual ~Spellcasting() {}
@@ -47,7 +48,7 @@ namespace entry {
auto data = Feature::toJson();
data["innate"] = innate;
data["spellcasting_ability"] = ability;
- data["levels"] = ptrvec2json(spellsBySlot);
+ data["levels"] = utils::ptrvec2json(spellsBySlot);
return data;
}
diff --git a/src/utils.cc b/src/utils.cc
index 54fa38e..6f25c46 100644
--- a/src/utils.cc
+++ b/src/utils.cc
@@ -10,7 +10,9 @@
#include <map>
#include <algorithm>
-nlohmann::json utils::loadJson(const std::string& path) {
+namespace fs = std::filesystem;
+
+nlohmann::json utils::loadJson(const fs::path& path) {
std::ifstream f(path);
nlohmann::json j;
f >> j;
@@ -35,7 +37,15 @@ nlohmann::json utils::findByName(const std::string& name) {
throw std::invalid_argument("Could not find data matching: " + name);
}
-void utils::saveJson(const nlohmann::json& data, const std::string& path) {
+int utils::parseInt(const std::string& s) {
+ try {
+ return std::stoi(s);
+ } catch(std::exception& e) {
+ throw std::runtime_error("An integer was expected but " + s + " was given");
+ }
+}
+
+void utils::saveJson(const nlohmann::json& data, const fs::path& path) {
std::ofstream f(path);
f << std::setw(4) << data << std::endl;
}
@@ -47,7 +57,7 @@ std::vector<nlohmann::json> utils::loadAllJson(const std::string& directory) {
return cache[directory];
}
std::vector<nlohmann::json> ret;
- for(auto path : std::filesystem::recursive_directory_iterator(directory)) {
+ for(auto path : fs::recursive_directory_iterator(directory)) {
if(path.path().extension() == ".json") {
ret.push_back(utils::loadJson(path.path()));
}
diff --git a/src/utils.h b/src/utils.h
index bf789e1..dea052b 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -1,15 +1,17 @@
#pragma once
#include "json.hpp"
+#include "entry.h"
#include <string>
#include <vector>
#include <map>
#include <sstream>
#include <memory>
#include <stdexcept>
+#include <filesystem>
namespace utils {
- nlohmann::json loadJson(const std::string& path);
-
+ nlohmann::json loadJson(const std::filesystem::path& path);
+
// Recursively loads all .json files under directory
// If called multiple times with same directory, returns a cached result.
std::vector<nlohmann::json> loadAllJson(const std::string& directory);
@@ -20,11 +22,31 @@ namespace utils {
// goes through the available types and searches for the one matching name.
nlohmann::json findByName(const std::string& name);
- void saveJson(const nlohmann::json& data, const std::string& path);
+ void saveJson(const nlohmann::json& data, const std::filesystem::path& path);
// converts in-place
std::string lower(std::string& in);
+ template<typename T> std::shared_ptr<T> instantiate(const std::filesystem::path& path) {
+ std::shared_ptr<entry::Entry> ent;
+ try {
+ ent = entry::Entry::create(loadJson(path));
+ } catch(std::exception& e) {
+ if(std::filesystem::directory_entry(path).exists()) {
+ throw std::runtime_error("Invalid json: " + path.string());
+ } else {
+ throw std::runtime_error("No such file nor directory: " + path.string());
+ }
+ }
+ std::shared_ptr<T> t = std::dynamic_pointer_cast<T>(ent);
+ if(! t) {
+ throw std::runtime_error("Wrong instance type: " + ent->getType());
+ }
+ return t;
+ }
+
+ int parseInt(const std::string& s);
+
template<typename S, typename D> std::shared_ptr<S> loadDFromJson(const nlohmann::json& data) {
try {
return std::shared_ptr<S>(new D(loadJson(data["type"], data["name"]), data));
@@ -78,4 +100,34 @@ namespace utils {
std::string getCostString(int coppers);
std::string toOrdinal(std::size_t number);
+
+ template<typename T> std::vector<T> json2vec(const nlohmann::json& data) {
+ using std::begin; using std::end;
+ return std::vector<T>(begin(data), end(data));
+ }
+
+ template<typename T> std::vector<std::shared_ptr<T>> jsonList2ptrvec(const std::string& type, const std::vector<std::string>& names) {
+ std::vector<std::shared_ptr<T>> ret;
+ for(auto name : names) {
+ auto j = utils::loadJson(type, name);
+ ret.push_back(std::shared_ptr<T>(new T(j, j)));
+ }
+ return ret;
+ }
+
+ template<typename T> std::vector<std::shared_ptr<T>> json2ptrvec(const nlohmann::json& data) {
+ std::vector<std::shared_ptr<T>> ret;
+ for(nlohmann::json d : data) {
+ ret.push_back(T::create(d));
+ }
+ return ret;
+ }
+
+ template<typename T> std::vector<nlohmann::json> ptrvec2json(std::vector<T> src) {
+ std::vector<nlohmann::json> ret;
+ for(T i : src) {
+ ret.push_back(i->toJson());
+ }
+ return ret;
+ }
}
diff --git a/src/weapon.h b/src/weapon.h
index 9384f78..c1047bb 100644
--- a/src/weapon.h
+++ b/src/weapon.h
@@ -38,7 +38,7 @@ namespace entry {
class Weapon : public Item, public Substantial {
public:
- Weapon(const nlohmann::json& data, const nlohmann::json& base) : Item(base), damage(json2vec<Damage>(data["damage"])), properties(data["properties"]), weaponType(data["weapon_type"]), range(data["range"][0], data["range"][1]), reach(data["reach"]), cost(data["cost"]), weight(data["weight"]) {}
+ Weapon(const nlohmann::json& data, const nlohmann::json& base) : Item(base), damage(utils::json2vec<Damage>(data["damage"])), properties(data["properties"]), weaponType(data["weapon_type"]), range(data["range"][0], data["range"][1]), reach(data["reach"]), cost(data["cost"]), weight(data["weight"]) {}
std::vector<Damage> getDamage(void) const {return damage;}
std::set<std::string> getProperties(void) const {return properties;}