diff options
author | Your Name <you@example.com> | 2022-01-16 21:32:01 -0500 |
---|---|---|
committer | Your Name <you@example.com> | 2022-01-16 21:32:01 -0500 |
commit | d0e356d09e30a11c1e072415a5088f829d5c0a04 (patch) | |
tree | 1e64d37b9b424cd74c30ad4c8225828c7a76874e /src/cmd | |
parent | 3f78a7e1647ba94129236bd2bf4fc855c109628a (diff) | |
download | dmtool-d0e356d09e30a11c1e072415a5088f829d5c0a04.tar.gz dmtool-d0e356d09e30a11c1e072415a5088f829d5c0a04.tar.bz2 dmtool-d0e356d09e30a11c1e072415a5088f829d5c0a04.zip |
Worked on features
Diffstat (limited to 'src/cmd')
-rw-r--r-- | src/cmd/cmd.cc | 54 | ||||
-rw-r--r-- | src/cmd/cmd.h | 47 | ||||
-rw-r--r-- | src/cmd/cmd_fsops.cc | 87 | ||||
-rw-r--r-- | src/cmd/cmd_manipulate.cc | 348 | ||||
-rw-r--r-- | src/cmd/cmd_query.cc | 48 | ||||
-rw-r--r-- | src/cmd/cmd_usage.cc | 60 |
6 files changed, 644 insertions, 0 deletions
diff --git a/src/cmd/cmd.cc b/src/cmd/cmd.cc new file mode 100644 index 0000000..a1fd760 --- /dev/null +++ b/src/cmd/cmd.cc @@ -0,0 +1,54 @@ +#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; + } + + std::string formatRoll(std::string name, std::string type, int rolled, int bonus) { + std::stringstream text; + text << name << " " << type << ": " << rolled << " (d20) + " << bonus << " (" << name << " " << type << " bonus) = " << rolled + bonus << std::endl; + return text.str(); + } + + std::vector<rules::Qualifier> parseQualifiers(std::map<std::string, std::string> flags) { + std::vector<rules::Qualifier> qualifiers; + for(auto flagPair : flags) { + auto flag = flagPair.first; + 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()); + } + } + return qualifiers; + } +} diff --git a/src/cmd/cmd.h b/src/cmd/cmd.h new file mode 100644 index 0000000..ff96b32 --- /dev/null +++ b/src/cmd/cmd.h @@ -0,0 +1,47 @@ +#pragma once +#include <vector> +#include <map> +#include <string> +#include <filesystem> +#include "../rules.h" + +namespace cmd { + // Corresponds to commands + + // Usage gets a category of its own + std::string usage(const std::string& exename); + + // Filesystem operations + 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); + + // Manipulators + std::string heal(std::vector<std::string> args); + std::string damage(std::vector<std::string> args, std::map<std::string, std::string> flags); + std::string attack(std::vector<std::string> args, std::map<std::string, std::string> flags); + std::string save(std::vector<std::string> args, std::map<std::string, 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); + std::string edit(std::vector<std::string> args); + std::string spellcasting(std::vector<std::string> args); + std::string git(std::vector<std::string> args); + + //Queries + std::string attacks(std::vector<std::string> args); + std::string roll(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); + + // Helper functions + std::string formatRoll(std::string name, std::string type, int rolled, int bonus); + std::vector<rules::Qualifier> parseQualifiers(std::map<std::string, std::string> flags); +} diff --git a/src/cmd/cmd_fsops.cc b/src/cmd/cmd_fsops.cc new file mode 100644 index 0000000..e638b96 --- /dev/null +++ b/src/cmd/cmd_fsops.cc @@ -0,0 +1,87 @@ +#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.path().filename().string()[0] != '.') { + 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)->serialize(), 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/cmd_manipulate.cc b/src/cmd/cmd_manipulate.cc new file mode 100644 index 0000000..18df098 --- /dev/null +++ b/src/cmd/cmd_manipulate.cc @@ -0,0 +1,348 @@ +#include "cmd.h" +#include "../utils.h" +#include "../creature.h" +#include "../item.h" +#include "../spellcasting.h" +#include "../settings.h" +#include "../weapon.h" +#include "../dice.h" +#include "../armor.h" +#include <sstream> +#include <memory> +#include <cstdlib> +#include <fstream> +#include <iterator> +#include <algorithm> + +namespace fs = std::filesystem; + +namespace cmd { + // Call after applying to format printing + std::string formatHealingDamage(const std::shared_ptr<creature::Creature>& c, int initHP, bool heal, int amnt, const std::string& dmgType, const std::vector<rules::Qualifier>& qualifiers) { + std::stringstream text; + 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; + return text.str(); + } + + std::string healOrDamageProgrammatic(fs::path p, bool heal, int amnt, std::string dmgType, const std::vector<rules::Qualifier>& qualifiers) { + auto c = utils::instantiate<creature::Creature>(p); + int initHP = c->getHP(); + if(heal) { + c->applyHealing(amnt); + } else { + c->applyDamage(amnt, dmgType, qualifiers); + } + utils::saveJson(*c, p); + return formatHealingDamage(c, initHP, heal, amnt, dmgType, qualifiers); + } + + std::string healOrDamage(bool heal, std::vector<std::string> args, std::map<std::string, std::string> flags) { + auto qualifiers = parseQualifiers(flags); + fs::path p = getTruePath(args[0]); + int amnt = utils::parseInt(args[1]); + std::string dmgType = "force"; + if(args.size() == 3) { + dmgType = args[2]; + } + return healOrDamageProgrammatic(p, heal, amnt, dmgType, qualifiers); + } + + std::string attack(std::vector<std::string> args, std::map<std::string, std::string> flags) { + std::stringstream text; + bool is2h = flags.find("2") != flags.end(); + bool is1h = flags.find("1") != flags.end(); + if(is2h and is1h) { + text << "ERROR: Cannot be both 1 handed and 2 handed!" << std::endl; + return text.str(); + } + auto c1 = utils::instantiate<creature::Creature>(getTruePath(args[0])); + args.erase(args.begin()); + fs::path p2 = getTruePath(args.back()); + auto c2 = utils::instantiate<creature::Creature>(p2); + args.erase(args.end()-1); + std::string attackName = utils::join(args, " "); + utils::lower(attackName); + std::shared_ptr<entry::Weapon> w; + for(auto weap : creature::getAttacks(*c1)) { + if(weap->getName() == attackName) { + w = weap; + break; + } + } + text << w->getText(*c1) << std::endl; + int rolled = dice::roll(20); + int bonus = w->getToHitBonus(*c1); + text << formatRoll(w->getName(), "attack", rolled, bonus); + int ac = creature::getAC(*c2); + if(rolled + bonus >= ac) { + text << " Hit (" << (rolled + bonus) << " to hit >= " << ac << " ac): "; + bool wants2h = true; + for(auto a : utils::castPtrs<entry::Item, entry::Armor>(c1->getInventory())) { + if(a->getArmorType() == "shield") { + wants2h = false; + } + } + if(is2h) { + wants2h = true; + } else if(is1h) { + wants2h = false; + } + auto dmg = entry::rollDmg(*w, wants2h); + text << entry::formatDmg(*w, *c1, dmg) << std::endl; + for(auto d : dmg) { + text << " " << healOrDamageProgrammatic(p2, false, d.rolled, d.dmg_type, {}); + } + } else { + text << " Miss (" << (rolled + bonus) << " to hit < " << ac << " ac)" << std::endl; + } + return text.str(); + } + + std::string heal(std::vector<std::string> args) { + return healOrDamage(true, args, {}); + } + + std::string damage(std::vector<std::string> args, std::map<std::string, std::string> flags) { + return healOrDamage(false, args, flags); + } + + std::string save(std::vector<std::string> args, std::map<std::string, std::string> flags) { + if(args.size() < 3) { + throw std::runtime_error("Subcommand 'save' requires at least 3 arguments"); + } + std::stringstream text; + rules::Ability ability = rules::tryGetAbilityOrSkill<rules::Ability>(args[0]); + if(! ability) { + throw std::runtime_error("Requires a valid ability name but received \"" + args[0] + "\"."); + } + args.erase(args.begin()); + int DC = utils::parseInt(args[0]); + args.erase(args.begin()); + // Now iterate over the paths + for(std::string s : args) { + fs::path p = getTruePath(s); + auto c = utils::instantiate<creature::Creature>(p); + int initHP = c->getHP(); + int rolled = dice::roll(20); + int bonus = c->getAbilitySaveBonus(ability); + int damage = 0; + std::string type = "force"; + bool halves = flags.find("halves") != flags.end(); + if(flags.find("damage") != flags.end()) { + damage = utils::parseInt(flags.at("damage")); + if(flags.find("type") != flags.end()) type = flags.at("type"); + auto qualifiers = parseQualifiers(flags); + rolled = c->saveOrDamage(ability, DC, damage, type, qualifiers, halves); + rolled -= bonus; // It's combined in creature + } + bool passed = rolled + bonus >= DC; + text << c->getName() << " " << (passed? "PASS" : "FAIL") << ": "; + text << formatRoll(ability.getFull(), "save", rolled, bonus); + if(flags.find("damage") != flags.end() and (halves or ! passed)) { + text << formatHealingDamage(c, initHP, false, damage, type, parseQualifiers(flags)); + } + utils::saveJson(*c, p); + } + return text.str(); + } + + 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(); + } + + std::string edit(std::vector<std::string> args) { + auto p = getTruePath(args[0]); + auto e = utils::instantiate<entry::Entry>(p); + auto editor = settings::getString("editor"); + // General workflow: copy notes (text) from e to a temp file, edit it, then copy back. + fs::path tmp("/tmp/dmtool.tmp"); + std::ofstream out(tmp); + out << e->Entry::getText(); + out.close(); + std::system((editor + " " + tmp.string()).c_str()); + std::ifstream in(tmp); + std::string newText(std::istreambuf_iterator<char>{in}, {}); + e->setText(newText); + utils::saveJson(e->serialize(), p); + return ""; + } + + std::string spellcasting(std::vector<std::string> args) { + std::stringstream text; + auto p = getTruePath(args[0]); + auto c = utils::instantiate<creature::Creature>(p); + auto subcommand = args[1]; + if(subcommand != "init" && subcommand != "ability" && subcommand != "level") { + throw std::runtime_error("Unknown option \"" + subcommand + "\""); + } + if(subcommand == "init") { + c->addSpellcasting(); + } else { + auto sc = c->getSpellcasting(); + if(! sc) { + throw std::runtime_error("Creature " + c->getName() + " has no spellcasting"); + } + text << "Added spellcasting to " << c->getName() << std::endl; + if(subcommand == "ability") { + if(args.size() != 3) { + throw std::runtime_error("Subcommand \"spellcasting ability\" requires an additional parameter, but none was given"); + } + sc->setAbility(args[2]); + text << "Set " << c->getName() << " spellcasting ability to " << args[2] << std::endl; + } else { // subcommand == "level" + if(args.size() != 4) { + throw std::runtime_error("Subcommand \"spellcasting level\" requires more parameters"); + } + int level = utils::parseInt(args[2]); + int slots = utils::parseInt(args[3]); + if(level <= 0 || slots < 0) { + throw std::runtime_error("Spellcasting target out of range"); + } + while(sc->getSlotLevels().size() <= (std::size_t) level) { + sc->addSlotLevel(); + } + sc->getSlotLevels()[level]->numSlots = slots; + text << "Gave " << c->getName() << " " << slots << " " << utils::toOrdinal(level) << " level spell slots" << std::endl; + } + } + utils::saveJson(*c, p); + return text.str(); + } + + std::string git(std::vector<std::string> args) { + std::string root = getTruePath("").string(); + std::system(("cd " + root + " && " + utils::join(args, " ")).c_str()); + return ""; + } +} diff --git a/src/cmd/cmd_query.cc b/src/cmd/cmd_query.cc new file mode 100644 index 0000000..9c2dae6 --- /dev/null +++ b/src/cmd/cmd_query.cc @@ -0,0 +1,48 @@ +#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); + // Search through skills, saves, and attacks to roll + if(rollName == "init" or rollName == "initiative") { + text << formatRoll("Initiative", "check", rolled, c->getInitiative()); + } + rules::Skill skill = rules::tryGetAbilityOrSkill<rules::Skill>(rollName); + rules::Ability ability = rules::tryGetAbilityOrSkill<rules::Ability>(rollName); + if(skill) { + text << formatRoll(skill.getName(), "check", rolled, c->getSkillBonus(skill)); + } else if(ability) { + text << formatRoll(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 bonus = w->getToHitBonus(*c); + text << formatRoll(w->getName(), "attack", rolled, bonus); + text << " on hit: " << entry::formatDmg(*w, *c) << std::endl; + break; + } + } + } + return text.str(); + } +} diff --git a/src/cmd/cmd_usage.cc b/src/cmd/cmd_usage.cc new file mode 100644 index 0000000..44daca4 --- /dev/null +++ b/src/cmd/cmd_usage.cc @@ -0,0 +1,60 @@ +#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 << "attack path1 name path2 [-1] [-2]" << std::endl; + text << indDesc << "Roll an attack for path1 attacking path2. Handedness defaults to 2h if versatile and not holding a shield, otherwise use -1 and -2." << std::endl; + text << indOpt << "save ability DC paths [--damage DMG] [--type TYPE] [--halves] [--magical,-m] [--silvered,-s] [--adamantine,-a]" << std::endl; + text << indDesc << "Force creatures at paths to make an ability save at DC. Default: report pass/fail." << std::endl; + text << indDesc << " If --damage, failed saves take TYPE damage (default force), passed saves negate." << std::endl; + text << indDesc << " If --halves, passed saves halve damage rather than negate." << 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 << "spellcasting path SUBCOMMAND" << std::endl; + text << indDesc << "Manipulate creature's spellcasting feature, where subcommand is any of:" << std::endl; + text << indDesc << " init; adds spellcasting to a creature which currently does not have spellcasting" << std::endl; + text << indDesc << " ability value; sets the spellcasting ability, where value is the ability name" << std::endl; + text << indDesc << " level l slots; sets the number of slots at spell level l to slots" << std::endl; + text << indOpt << "git [command]" << std::endl; + text << indDesc << "Execute a git command within the dmtool folder." << std::endl; + text << indOpt << "help" << std::endl; + text << indDesc << "Show this help." << std::endl; + return text.str(); + } +} |