From d0e356d09e30a11c1e072415a5088f829d5c0a04 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 16 Jan 2022 21:32:01 -0500 Subject: Worked on features --- src/attack.h | 2 +- src/cmd.cc | 54 ------- src/cmd.h | 47 ------- src/cmd/cmd.cc | 54 +++++++ src/cmd/cmd.h | 47 +++++++ src/cmd/cmd_fsops.cc | 87 ++++++++++++ src/cmd/cmd_manipulate.cc | 348 ++++++++++++++++++++++++++++++++++++++++++++++ src/cmd/cmd_query.cc | 48 +++++++ src/cmd/cmd_usage.cc | 60 ++++++++ src/cmd_fsops.cc | 87 ------------ src/cmd_manipulate.cc | 348 ---------------------------------------------- src/cmd_query.cc | 48 ------- src/cmd_usage.cc | 60 -------- src/creature.cc | 1 - src/creature.h | 2 +- src/dmtool.cc | 2 +- src/entry.cc | 2 +- src/feature.cc | 19 --- src/feature.h | 14 -- src/features/feature.cc | 19 +++ src/features/feature.h | 14 ++ src/rules.cc | 7 + src/rules.h | 44 ++++-- src/spellcasting.h | 2 +- src/utils.h | 2 +- 25 files changed, 724 insertions(+), 694 deletions(-) delete mode 100644 src/cmd.cc delete mode 100644 src/cmd.h create mode 100644 src/cmd/cmd.cc create mode 100644 src/cmd/cmd.h create mode 100644 src/cmd/cmd_fsops.cc create mode 100644 src/cmd/cmd_manipulate.cc create mode 100644 src/cmd/cmd_query.cc create mode 100644 src/cmd/cmd_usage.cc delete mode 100644 src/cmd_fsops.cc delete mode 100644 src/cmd_manipulate.cc delete mode 100644 src/cmd_query.cc delete mode 100644 src/cmd_usage.cc delete mode 100644 src/feature.cc delete mode 100644 src/feature.h create mode 100644 src/features/feature.cc create mode 100644 src/features/feature.h (limited to 'src') diff --git a/src/attack.h b/src/attack.h index 2e4125b..e00eb25 100644 --- a/src/attack.h +++ b/src/attack.h @@ -1,5 +1,5 @@ #pragma once -#include "feature.h" +#include "features/feature.h" #include "weapon.h" namespace creature { diff --git a/src/cmd.cc b/src/cmd.cc deleted file mode 100644 index d84c55b..0000000 --- a/src/cmd.cc +++ /dev/null @@ -1,54 +0,0 @@ -#include "cmd.h" -#include "settings.h" -#include -#include -#include -#include - -namespace cmd { - std::vector 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 parseQualifiers(std::map flags) { - std::vector 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.h b/src/cmd.h deleted file mode 100644 index 978f6db..0000000 --- a/src/cmd.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include -#include -#include -#include -#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 args); - std::string mkdir(std::vector args); - std::string cp(std::vector args); - std::string mv(std::vector args); - std::string rm(std::vector args); - - // Manipulators - std::string heal(std::vector args); - std::string damage(std::vector args, std::map flags); - std::string attack(std::vector args, std::map flags); - std::string save(std::vector args, std::map flags); - std::string reset(std::vector args); - std::string set(std::vector args); - std::string add(std::vector args); - std::string del(std::vector args); - std::string edit(std::vector args); - std::string spellcasting(std::vector args); - std::string git(std::vector args); - - //Queries - std::string attacks(std::vector args); - std::string roll(std::vector args); - - // Command-centric helpers - - // Not idempotent: only do once! - std::filesystem::path getTruePath(std::filesystem::path virtPath); - std::vector getVirtDirs(void); - - // Helper functions - std::string formatRoll(std::string name, std::string type, int rolled, int bonus); - std::vector parseQualifiers(std::map flags); -} 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 +#include +#include +#include + +namespace cmd { + std::vector 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 parseQualifiers(std::map flags) { + std::vector 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 +#include +#include +#include +#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 args); + std::string mkdir(std::vector args); + std::string cp(std::vector args); + std::string mv(std::vector args); + std::string rm(std::vector args); + + // Manipulators + std::string heal(std::vector args); + std::string damage(std::vector args, std::map flags); + std::string attack(std::vector args, std::map flags); + std::string save(std::vector args, std::map flags); + std::string reset(std::vector args); + std::string set(std::vector args); + std::string add(std::vector args); + std::string del(std::vector args); + std::string edit(std::vector args); + std::string spellcasting(std::vector args); + std::string git(std::vector args); + + //Queries + std::string attacks(std::vector args); + std::string roll(std::vector args); + + // Command-centric helpers + + // Not idempotent: only do once! + std::filesystem::path getTruePath(std::filesystem::path virtPath); + std::vector getVirtDirs(void); + + // Helper functions + std::string formatRoll(std::string name, std::string type, int rolled, int bonus); + std::vector parseQualifiers(std::map 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 +#include + +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(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 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 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(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 args) { + // Operate by intantiating and saving + // We do recursive! + cp(getTruePath(args[0]), getTruePath(args[1])); + return ""; + } + + std::string mv(std::vector args) { + fs::rename(getTruePath(args[0]), getTruePath(args[1])); + return ""; + } + + std::string rm(std::vector 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 +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace cmd { + // Call after applying to format printing + std::string formatHealingDamage(const std::shared_ptr& c, int initHP, bool heal, int amnt, const std::string& dmgType, const std::vector& qualifiers) { + std::stringstream text; + text << (heal? "Healing " : "Damaging ") << c->getGivenName() << " the " << c->getCreatureName() << " by " << amnt; + if(! heal) { + std::string qualsString; + std::vector 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& qualifiers) { + auto c = utils::instantiate(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 args, std::map 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 args, std::map 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(getTruePath(args[0])); + args.erase(args.begin()); + fs::path p2 = getTruePath(args.back()); + auto c2 = utils::instantiate(p2); + args.erase(args.end()-1); + std::string attackName = utils::join(args, " "); + utils::lower(attackName); + std::shared_ptr 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(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 args) { + return healOrDamage(true, args, {}); + } + + std::string damage(std::vector args, std::map flags) { + return healOrDamage(false, args, flags); + } + + std::string save(std::vector args, std::map flags) { + if(args.size() < 3) { + throw std::runtime_error("Subcommand 'save' requires at least 3 arguments"); + } + std::stringstream text; + rules::Ability ability = rules::tryGetAbilityOrSkill(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(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 args) { + for(std::string s : args) { + fs::path p = getTruePath(s); + auto c = utils::instantiate(p); + c->longRest(); + utils::saveJson(*c, p); + } + return ""; + } + + std::string set(std::vector 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(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(abilityOrSkill); + rules::Ability ability = rules::tryGetAbilityOrSkill(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 args) { + std::stringstream text; + fs::path p = getTruePath(args[0]); + args.erase(args.begin()); // remove path from args + auto c = utils::instantiate(p); + std::string addName = utils::join(args, " "); + std::shared_ptr ent; + fs::path path = getTruePath(addName); + if(fs::directory_entry(path).exists()) { + ent = utils::instantiate(path); + } else { + ent = entry::Entry::create(utils::findByName(addName)); + } + // Determine if it is an item or a spell + auto i = std::dynamic_pointer_cast(ent); + if(i) { + c->addInventoryItem(i); + } else { + auto s = std::dynamic_pointer_cast(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 args) { + std::stringstream text; + fs::path p = getTruePath(args[0]); + args.erase(args.begin()); // remove path from args + auto c = utils::instantiate(p); + //Atempt to load the item if it's a path + std::string itemName = utils::join(args, " "); + try { + auto i = utils::instantiate(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 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 args) { + auto p = getTruePath(args[0]); + auto e = utils::instantiate(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{in}, {}); + e->setText(newText); + utils::saveJson(e->serialize(), p); + return ""; + } + + std::string spellcasting(std::vector args) { + std::stringstream text; + auto p = getTruePath(args[0]); + auto c = utils::instantiate(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 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 + +namespace cmd { + std::string attacks(std::vector args) { + std::stringstream text; + auto c = utils::instantiate(getTruePath(args[0])); + for(auto w : creature::getAttacks(*c)) { + text << w->getName() << std::endl; + } + return text.str(); + } + + std::string roll(std::vector args) { + std::stringstream text; + auto c = utils::instantiate(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(rollName); + rules::Ability ability = rules::tryGetAbilityOrSkill(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 +#include + +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(); + } +} diff --git a/src/cmd_fsops.cc b/src/cmd_fsops.cc deleted file mode 100644 index ac4bdef..0000000 --- a/src/cmd_fsops.cc +++ /dev/null @@ -1,87 +0,0 @@ -#include "cmd.h" -#include "utils.h" -#include "entry.h" -#include -#include - -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(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 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 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(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 args) { - // Operate by intantiating and saving - // We do recursive! - cp(getTruePath(args[0]), getTruePath(args[1])); - return ""; - } - - std::string mv(std::vector args) { - fs::rename(getTruePath(args[0]), getTruePath(args[1])); - return ""; - } - - std::string rm(std::vector args) { - for(std::string s : args) { - fs::remove_all(getTruePath(s)); - } - return ""; - } -} diff --git a/src/cmd_manipulate.cc b/src/cmd_manipulate.cc deleted file mode 100644 index aa6407d..0000000 --- a/src/cmd_manipulate.cc +++ /dev/null @@ -1,348 +0,0 @@ -#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 -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -namespace cmd { - // Call after applying to format printing - std::string formatHealingDamage(const std::shared_ptr& c, int initHP, bool heal, int amnt, const std::string& dmgType, const std::vector& qualifiers) { - std::stringstream text; - text << (heal? "Healing " : "Damaging ") << c->getGivenName() << " the " << c->getCreatureName() << " by " << amnt; - if(! heal) { - std::string qualsString; - std::vector 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& qualifiers) { - auto c = utils::instantiate(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 args, std::map 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 args, std::map 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(getTruePath(args[0])); - args.erase(args.begin()); - fs::path p2 = getTruePath(args.back()); - auto c2 = utils::instantiate(p2); - args.erase(args.end()-1); - std::string attackName = utils::join(args, " "); - utils::lower(attackName); - std::shared_ptr 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(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 args) { - return healOrDamage(true, args, {}); - } - - std::string damage(std::vector args, std::map flags) { - return healOrDamage(false, args, flags); - } - - std::string save(std::vector args, std::map flags) { - if(args.size() < 3) { - throw std::runtime_error("Subcommand 'save' requires at least 3 arguments"); - } - std::stringstream text; - rules::Ability ability = rules::tryGetAbilityOrSkill(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(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 args) { - for(std::string s : args) { - fs::path p = getTruePath(s); - auto c = utils::instantiate(p); - c->longRest(); - utils::saveJson(*c, p); - } - return ""; - } - - std::string set(std::vector 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(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(abilityOrSkill); - rules::Ability ability = rules::tryGetAbilityOrSkill(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 args) { - std::stringstream text; - fs::path p = getTruePath(args[0]); - args.erase(args.begin()); // remove path from args - auto c = utils::instantiate(p); - std::string addName = utils::join(args, " "); - std::shared_ptr ent; - fs::path path = getTruePath(addName); - if(fs::directory_entry(path).exists()) { - ent = utils::instantiate(path); - } else { - ent = entry::Entry::create(utils::findByName(addName)); - } - // Determine if it is an item or a spell - auto i = std::dynamic_pointer_cast(ent); - if(i) { - c->addInventoryItem(i); - } else { - auto s = std::dynamic_pointer_cast(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 args) { - std::stringstream text; - fs::path p = getTruePath(args[0]); - args.erase(args.begin()); // remove path from args - auto c = utils::instantiate(p); - //Atempt to load the item if it's a path - std::string itemName = utils::join(args, " "); - try { - auto i = utils::instantiate(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 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 args) { - auto p = getTruePath(args[0]); - auto e = utils::instantiate(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{in}, {}); - e->setText(newText); - utils::saveJson(e->serialize(), p); - return ""; - } - - std::string spellcasting(std::vector args) { - std::stringstream text; - auto p = getTruePath(args[0]); - auto c = utils::instantiate(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 args) { - std::string root = getTruePath("").string(); - std::system(("cd " + root + " && " + utils::join(args, " ")).c_str()); - return ""; - } -} diff --git a/src/cmd_query.cc b/src/cmd_query.cc deleted file mode 100644 index e13876f..0000000 --- a/src/cmd_query.cc +++ /dev/null @@ -1,48 +0,0 @@ -#include "cmd.h" -#include "utils.h" -#include "creature.h" -#include "dice.h" -#include "weapon.h" -#include - -namespace cmd { - std::string attacks(std::vector args) { - std::stringstream text; - auto c = utils::instantiate(getTruePath(args[0])); - for(auto w : creature::getAttacks(*c)) { - text << w->getName() << std::endl; - } - return text.str(); - } - - std::string roll(std::vector args) { - std::stringstream text; - auto c = utils::instantiate(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(rollName); - rules::Ability ability = rules::tryGetAbilityOrSkill(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_usage.cc b/src/cmd_usage.cc deleted file mode 100644 index 44daca4..0000000 --- a/src/cmd_usage.cc +++ /dev/null @@ -1,60 +0,0 @@ -#include "cmd.h" -#include -#include - -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(); - } -} diff --git a/src/creature.cc b/src/creature.cc index 6491024..ea8ef30 100644 --- a/src/creature.cc +++ b/src/creature.cc @@ -1,7 +1,6 @@ #include "creature.h" #include "dice.h" #include "rules.h" -#include "feature.h" #include "weapon.h" #include "armor.h" #include "attack.h" diff --git a/src/creature.h b/src/creature.h index 09760e3..de93b0e 100644 --- a/src/creature.h +++ b/src/creature.h @@ -2,7 +2,7 @@ #include "rules.h" #include "utils.h" #include "entry.h" -#include "feature.h" +#include "features/feature.h" #include "item.h" #include diff --git a/src/dmtool.cc b/src/dmtool.cc index 6ae096f..3aec8b2 100644 --- a/src/dmtool.cc +++ b/src/dmtool.cc @@ -1,4 +1,4 @@ -#include "cmd.h" +#include "cmd/cmd.h" #include "utils.h" #include #include diff --git a/src/entry.cc b/src/entry.cc index 663a40a..9c9b3da 100644 --- a/src/entry.cc +++ b/src/entry.cc @@ -1,6 +1,6 @@ #include "entry.h" #include "utils.h" -#include "feature.h" +#include "features/feature.h" #include "item.h" #include "spell.h" #include "creature.h" diff --git a/src/feature.cc b/src/feature.cc deleted file mode 100644 index 941de4e..0000000 --- a/src/feature.cc +++ /dev/null @@ -1,19 +0,0 @@ -#include "feature.h" -#include "spellcasting.h" -#include "attack.h" -#include "utils.h" -#include -#include - -using namespace std; - -namespace entry { - shared_ptr Feature::create(const nlohmann::json& data) { - if(data["type"] == "spells") { - return utils::loadDFromJson(data); - } else if(data["type"] == "attack") { - return utils::loadDFromJson(data); - } - return shared_ptr(new Feature(data)); - } -} diff --git a/src/feature.h b/src/feature.h deleted file mode 100644 index e3c8bdf..0000000 --- a/src/feature.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#include -#include "entry.h" -#include - -namespace entry { - class Feature : public Entry { - public: - Feature() {} - Feature(const std::string& name, const std::string& type, const std::string& text) : Entry("feature", name, type, text) {} - static std::shared_ptr create(const nlohmann::json& data); - virtual ~Feature() {} - }; -} diff --git a/src/features/feature.cc b/src/features/feature.cc new file mode 100644 index 0000000..0fd2d6c --- /dev/null +++ b/src/features/feature.cc @@ -0,0 +1,19 @@ +#include "feature.h" +#include "../spellcasting.h" +#include "../attack.h" +#include "../utils.h" +#include +#include + +using namespace std; + +namespace entry { + shared_ptr Feature::create(const nlohmann::json& data) { + if(data["type"] == "spells") { + return utils::loadDFromJson(data); + } else if(data["type"] == "attack") { + return utils::loadDFromJson(data); + } + return shared_ptr(new Feature(data)); + } +} diff --git a/src/features/feature.h b/src/features/feature.h new file mode 100644 index 0000000..209a30a --- /dev/null +++ b/src/features/feature.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include "../entry.h" +#include + +namespace entry { + class Feature : public Entry { + public: + Feature() {} + Feature(const std::string& name, const std::string& type, const std::string& text) : Entry("feature", name, type, text) {} + static std::shared_ptr create(const nlohmann::json& data); + virtual ~Feature() {} + }; +} diff --git a/src/rules.cc b/src/rules.cc index 96d9a51..900d023 100644 --- a/src/rules.cc +++ b/src/rules.cc @@ -37,6 +37,8 @@ namespace rules { {"non-adamantine", "adamantine"} }; + const std::set Condition::conditions {"blinded", "charmed", "deafened", "frightened", "grappled", "incapacitated", "invisible", "paralyzed", "petrified", "poisoned", "prone", "restrained", "stunned", "unconscious", "exhausted1", "exhausted2", "exhausted3", "exhausted4", "exhausted5", "exhausted6"}; + std::ostream& operator<<(std::ostream& os, const Ability& a) { os << std::string(a); return os; @@ -51,4 +53,9 @@ namespace rules { os << std::string(q); return os; } + + std::ostream& operator<<(std::ostream& os, const Condition& c) { + os << std::string(c); + return os; + } } diff --git a/src/rules.h b/src/rules.h index 2894947..30464a8 100644 --- a/src/rules.h +++ b/src/rules.h @@ -2,6 +2,7 @@ #include "utils.h" #include #include +#include #include #include #include @@ -18,6 +19,10 @@ namespace rules { ri.payload = j; } std::string getPayload(void) const {return payload;} + bool operator==(const RuleItem& rhs) const {return getPayload() == rhs.getPayload();} + bool operator<(const RuleItem& rhs) const {return getPayload() < rhs.getPayload();} + operator std::string() const {return getPayload();} + operator bool() const {return ! getPayload().empty();} protected: std::string payload; }; @@ -26,10 +31,6 @@ namespace rules { public: std::string getFull() const {return abilities.at(getAbbrev());} std::string getAbbrev() const {return getPayload();} - operator std::string() const {return getAbbrev();} - bool operator<(const Ability& rhs) const {return getAbbrev() < rhs.getAbbrev();} - bool operator==(const Ability& rhs) const {return getAbbrev() == rhs.getAbbrev();} - operator bool() const {return ! getAbbrev().empty();} Ability() {} Ability(const std::string& abbrev) { @@ -67,10 +68,6 @@ namespace rules { public: std::string getName() const {return getPayload();} Ability getAbility() const {return Ability(skill2ability.at(getName()));} - operator std::string() const {return getName();} - bool operator<(const Skill& rhs) const {return getName() < rhs.getName();} - bool operator==(const Skill& rhs) const {return getName() == rhs.getName();} - operator bool() const {return ! getName().empty();} virtual ~Skill() {} @@ -128,9 +125,7 @@ namespace rules { } std::string getNegative() const {return getPayload();} std::string getPositive() const {return negative2positive.at(getNegative());} - operator std::string() const {return getNegative();} virtual ~Qualifier() {} - bool operator==(const Qualifier& rhs) const {return getNegative() == rhs.getNegative();} static Qualifier Magical() {return Qualifier("nonmagical");} static Qualifier Silvered() {return Qualifier("non-silvered");} @@ -140,9 +135,38 @@ namespace rules { static const std::map negative2positive; }; + class Condition : public RuleItem { + public: + Condition() {} + Condition(std::string name) { + utils::lower(name); + if(conditions.count(name) == 0) { + throw std::invalid_argument("No such condition: " + name); + } + payload = name; + } + + virtual ~Condition() {} + + void incExhaustion() { + if(payload.find("exhausted") == std::string::npos) { + throw std::invalid_argument("Cannot increment exhaustion on condition " + payload); + } + int exhLvl = utils::parseInt(std::string(1, payload.back())); + if(exhLvl >= 6) { + throw std::invalid_argument("Cannot increment exhaustion beyond level 6"); + } + payload.back() = '0'+exhLvl+1; + } + + private: + static const std::set conditions; + }; + std::ostream& operator<<(std::ostream& os, const Ability& a); std::ostream& operator<<(std::ostream& os, const Skill& s); std::ostream& operator<<(std::ostream& os, const Qualifier& q); + std::ostream& operator<<(std::ostream& os, const Condition& c); template T tryGetAbilityOrSkill(std::string src) { try { diff --git a/src/spellcasting.h b/src/spellcasting.h index 902f860..46d4ba8 100644 --- a/src/spellcasting.h +++ b/src/spellcasting.h @@ -1,5 +1,5 @@ #pragma once -#include "feature.h" +#include "features/feature.h" #include "spell.h" #include "rules.h" #include "utils.h" diff --git a/src/utils.h b/src/utils.h index 77096e7..2998704 100644 --- a/src/utils.h +++ b/src/utils.h @@ -98,7 +98,7 @@ namespace utils { template std::vector> castPtrs(std::vector> from) { std::vector> Ts; for(std::shared_ptr f : from) { - std::shared_ptr t = dynamic_pointer_cast(f); + std::shared_ptr t = std::dynamic_pointer_cast(f); if(t) { Ts.push_back(t); } -- cgit v1.2.3