diff options
Diffstat (limited to 'src/dmtool.cc')
-rw-r--r-- | src/dmtool.cc | 448 |
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; |