aboutsummaryrefslogtreecommitdiff
path: root/src/dmtool.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/dmtool.cc')
-rw-r--r--src/dmtool.cc448
1 files changed, 41 insertions, 407 deletions
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;