aboutsummaryrefslogtreecommitdiff
path: root/src/dmtool.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/dmtool.cc')
-rw-r--r--src/dmtool.cc249
1 files changed, 192 insertions, 57 deletions
diff --git a/src/dmtool.cc b/src/dmtool.cc
index 8ade820..ba47616 100644
--- a/src/dmtool.cc
+++ b/src/dmtool.cc
@@ -3,6 +3,8 @@
#include "creature.h"
#include "dice.h"
#include "weapon.h"
+#include "spell.h"
+#include "spellcasting.h"
#include <iostream>
#include <vector>
#include <string>
@@ -30,7 +32,7 @@ void usage(const std::string& exename) {
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]" << 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;
@@ -40,18 +42,22 @@ void usage(const std::string& exename) {
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 to creature." << 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;
}
-std::shared_ptr<entry::Entry> instantiate(const fs::path& path) {
+template<typename T> std::shared_ptr<T> instantiate(const fs::path& path) {
+ std::shared_ptr<entry::Entry> ent;
try {
- return entry::Entry::create(utils::loadJson(path));
+ 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());
@@ -59,6 +65,19 @@ std::shared_ptr<entry::Entry> instantiate(const fs::path& path) {
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) {
@@ -66,7 +85,7 @@ void save(const std::shared_ptr<entry::Entry>& e, const fs::path& path) {
}
void print(const fs::path& path) {
- std::cout << instantiate(path)->getText() << std::endl;
+ std::cout << instantiate<entry::Entry>(path)->getText() << std::endl;
}
fs::path getBaseDir() {
@@ -150,7 +169,7 @@ void mkdir(std::vector<std::string> args) {
void cp(fs::path src, fs::path dest) {
if(fs::directory_entry(src).is_regular_file()) {
- save(instantiate(src), dest);
+ save(instantiate<entry::Entry>(src), dest);
} else {
mkdir({dest});
for(fs::directory_entry de : fs::directory_iterator(src)) {
@@ -176,88 +195,203 @@ void rm(std::vector<std::string> args) {
}
}
+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) {
- std::shared_ptr<entry::Entry> e = instantiate(getTruePath(args[0]));
- std::shared_ptr<creature::Creature> c = std::dynamic_pointer_cast<creature::Creature>(e);
- if(! c) {
- throw std::runtime_error("Subcommand 'roll' expected a creature but was given an instance of " + e->getType());
- }
+ auto c = instantiate<creature::Creature>(getTruePath(args[0]));
args.erase(args.begin());
std::string rollName = utils::join(args, " ");
- std::transform(rollName.begin(), rollName.end(), rollName.begin(), ::tolower);
+ 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
- try {
- rules::Skill skill = rules::Skill::string2skill(rollName);
+ rules::Skill skill = tryGetAbilityOrSkill<rules::Skill>(rollName);
+ rules::Ability ability = tryGetAbilityOrSkill<rules::Ability>(rollName);
+ if(skill) {
printResults(skill.getName(), "check", rolled, c->getSkillBonus(skill));
- return;
- } catch(std::exception& e) {} // eat.
- try {
- rules::Ability ability = rules::Ability::string2ability(rollName);
+ } else if(ability) {
printResults(ability.getFull(), "save", rolled, c->getAbilitySaveBonus(ability));
- return;
- } catch(std::exception& e) {} // eat.
- 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;
+ } 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;
+ }
}
}
}
-std::tuple<fs::path, std::shared_ptr<creature::Creature>, int> getDmgHealAmnt(const std::string& subcommand, std::vector<std::string> args) {
- fs::path p = getTruePath(args[0]);
- std::shared_ptr<entry::Entry> e = instantiate(p);
- std::shared_ptr<creature::Creature> c = std::dynamic_pointer_cast<creature::Creature>(e);
- if(! c) {
- throw std::runtime_error("Subcommand '" + subcommand + "' expected a creature but was given an instance of " + e->getType());
- }
- int amnt = 0;
- try {
- amnt = std::stoi(args[1]);
- } catch(std::exception& e) {
- throw std::runtime_error("Subcommand '" + subcommand + "' expected an integer but was given " + args[1]);
- }
- return {p, c, amnt};
-}
-
void healOrDamage(bool heal, std::vector<std::string> args) {
- fs::path p;
- std::shared_ptr<creature::Creature> c;
- int amnt;
- std::string healOrDamage = heal? "heal" : "damage";
- std::tie(p, c, amnt) = getDmgHealAmnt(healOrDamage, 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 {
- std::string dmgType = "force";
if(args.size() == 3) {
dmgType = args[2];
}
- std::vector<std::string> qualifiers; // TODO
c->applyDamage(amnt, dmgType, qualifiers);
}
- std::cout << (heal? "Healing " : "Damaging ") << c->getGivenName() << " the " << c->getCreatureName() << " by " << amnt << ". HP: " << initHP << " -> " << c->getHP() << "." << std::endl;
+ 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) {}
-void set(std::vector<std::string> args) {}
-void add(std::vector<std::string> args) {}
+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;
+ }
+}
const std::map<std::string, std::vector<int>> nargs({
{"cp", {2}},
{"mv", {2}},
- {"damage", {2, 3}},
- {"heal", {2}}
+ {"damage", {2, 3, 4, 5, 6}},
+ {"heal", {2}},
});
void checkArgs(std::string cmd, std::vector<std::string> args) {
@@ -298,6 +432,7 @@ int main(int argc, char *argv[]) {
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);
} catch(std::exception& e) {