aboutsummaryrefslogtreecommitdiff
path: root/src/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd')
-rw-r--r--src/cmd/cmd.cc54
-rw-r--r--src/cmd/cmd.h47
-rw-r--r--src/cmd/cmd_fsops.cc87
-rw-r--r--src/cmd/cmd_manipulate.cc348
-rw-r--r--src/cmd/cmd_query.cc48
-rw-r--r--src/cmd/cmd_usage.cc60
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();
+ }
+}