diff options
author | Your Name <you@example.com> | 2021-05-05 09:44:50 -0400 |
---|---|---|
committer | Your Name <you@example.com> | 2021-05-05 09:44:50 -0400 |
commit | 2a9f262e6db5906db445d465e500d7ba8c90fab3 (patch) | |
tree | 34f850754b0c9114ede9d7b2bb8da90dffddc4fe | |
parent | 8614137f7f32f2c9f3c11419110cd70dd7f3b505 (diff) | |
download | dmtool-2a9f262e6db5906db445d465e500d7ba8c90fab3.tar.gz dmtool-2a9f262e6db5906db445d465e500d7ba8c90fab3.tar.bz2 dmtool-2a9f262e6db5906db445d465e500d7ba8c90fab3.zip |
Implemented additional commands
-rwxr-xr-x | parser/scrapeToJson.py | 5 | ||||
-rw-r--r-- | src/creature.cc | 75 | ||||
-rw-r--r-- | src/creature.h | 17 | ||||
-rw-r--r-- | src/dmtool.cc | 249 | ||||
-rw-r--r-- | src/entry.h | 4 | ||||
-rw-r--r-- | src/feature.cc | 2 | ||||
-rw-r--r-- | src/feature.h | 1 | ||||
-rw-r--r-- | src/jsonable.h | 14 | ||||
-rw-r--r-- | src/rules.cc | 11 | ||||
-rw-r--r-- | src/rules.h | 60 | ||||
-rw-r--r-- | src/settings.cc | 2 | ||||
-rw-r--r-- | src/settings.h | 3 | ||||
-rw-r--r-- | src/spellcasting.cc | 34 | ||||
-rw-r--r-- | src/spellcasting.h | 23 | ||||
-rw-r--r-- | src/utils.cc | 15 | ||||
-rw-r--r-- | src/utils.h | 6 | ||||
-rw-r--r-- | src/weapon.cc | 2 |
17 files changed, 409 insertions, 114 deletions
diff --git a/parser/scrapeToJson.py b/parser/scrapeToJson.py index 21758d0..9128190 100755 --- a/parser/scrapeToJson.py +++ b/parser/scrapeToJson.py @@ -15,19 +15,22 @@ def processMonster(data, weapons, armors, spells): desc[name] = "" for name in ['d_resistances', 'd_vulnerabilities', 'd_immunities', 'c_immunities']: # Formatted a, b, c[; d, e, and f from nonmagical attacks[ that aren't g]] + # Blerg. Also can be "not made with g weapons" # Maybe without the a, b, c part parts = [desc[name]] if '; ' in desc[name]: parts = desc[name].split('; ') desc[name] = [] for part in parts: - part = part.strip() + part = part.strip().lower() # Look for "nonmagical", and "that aren't x" qualifiers = [] if 'nonmagical' in part: qualifiers.append('nonmagical') if 'that aren\'t' in part: qualifiers.append('non-' + re.search('(?<=that aren\'t ).*$', part).group(0)) + if 'not made with ' in part: + qualifiers.append('non-' + re.search('(?<=not made with )\w*', part).group(0)) for typ in re.findall('([a-z]+(?=,)|^[a-z]+$|(?<=, )[a-z]+$|(?<=and )[a-z]+(?= from)|(?<=and )[a-z]+(?= \(from))', part): desc[name].append({'type': typ, 'qualifiers': qualifiers}) # Calc things about hp diff --git a/src/creature.cc b/src/creature.cc index e1f2b61..21943fe 100644 --- a/src/creature.cc +++ b/src/creature.cc @@ -6,6 +6,7 @@ #include "weapon.h" #include "armor.h" #include "attack.h" +#include "spellcasting.h" #include <algorithm> #include <iostream> #include <sstream> @@ -24,7 +25,7 @@ namespace creature { } Creature::Creature(const json& data, const json& base) - : Entry(base), inventory(json2ptrvec<entry::Item>(data["inventory"])), stats(makeMap<rules::Ability>(data["stats"])), skills(makeMap<rules::Skill>(data["skills"])), size(data["size"]), alignment(data["alignment"]), hdCount(data["hit_die_count"]), hdSides(data["hit_die_sides"]), speed(data["speed"]), saves(json2vec<rules::Ability>(data["saves"])), langs(data["langs"]), cr(data["cr"]), proficiency(data["prof"]), natArmorName(data["natural_armor"]["name"]), natArmorBonus(data["natural_armor"]["bonus"]), dmgImmunities(json2vec<dmgType>(data["d_immunities"])), dmgResistances(json2vec<dmgType>(data["d_resistances"])), dmgVulnerabilities(json2vec<dmgType>(data["d_vulnerabilities"])), condImmunities(json2vec<dmgType>(data["c_immunities"])), features(json2ptrvec<entry::Feature>(data["features"])) + : Entry(base), inventory(json2ptrvec<entry::Item>(data["inventory"])), stats(makeMap<rules::Ability>(data["stats"])), skills(makeMap<rules::Skill>(data["skills"])), proficiency(data["prof"]), size(data["size"]), alignment(data["alignment"]), hdCount(data["hit_die_count"]), hdSides(data["hit_die_sides"]), speed(data["speed"]), saves(json2vec<rules::Ability>(data["saves"])), langs(data["langs"]), cr(data["cr"]), natArmorName(data["natural_armor"]["name"]), natArmorBonus(data["natural_armor"]["bonus"]), dmgImmunities(json2vec<dmgType>(data["d_immunities"])), dmgResistances(json2vec<dmgType>(data["d_resistances"])), dmgVulnerabilities(json2vec<dmgType>(data["d_vulnerabilities"])), condImmunities(json2vec<dmgType>(data["c_immunities"])), features(json2ptrvec<entry::Feature>(data["features"])) { // Initialize names and hp if(((map<string, json>) data).contains("givenName")) { @@ -41,14 +42,6 @@ namespace creature { } } - template<typename T> vector<json> getJsonVectP(vector<T> src) { - vector<json> ret; - for(T i : src) { - ret.push_back(i->toJson()); - } - return ret; - } - nlohmann::json Creature::toJson() const { nlohmann::json data = Entry::toJson(); data["size"] = size; @@ -71,19 +64,19 @@ namespace creature { data["givenName"] = givenName; data["hpMax"] = hpMax; data["hp"] = hp; - data["inventory"] = getJsonVectP(inventory); - data["features"] = getJsonVectP(features); + data["inventory"] = ptrvec2json(inventory); + data["features"] = ptrvec2json(features); return data; } // True if type without matching qualifiers is in subdata - bool conditionApplies(const string& type, const vector<string>& qualifiers, const vector<dmgType> subdata) { + bool conditionApplies(const string& type, const vector<rules::Qualifier>& qualifiers, const vector<dmgType> subdata) { bool applies = false; for(dmgType con : subdata) { if(con.type == type) { applies = true; - for(string qualifier : qualifiers) { - if(find(con.qualifiers.begin(), con.qualifiers.end(), qualifier) == con.qualifiers.end()) { + for(auto qualifier : qualifiers) { + if(find(con.qualifiers.begin(), con.qualifiers.end(), qualifier) != con.qualifiers.end()) { applies = false; } } @@ -92,7 +85,7 @@ namespace creature { return applies; } - void Creature::applyDamage(int amount, const string& type, const vector<string>& qualifiers) { + void Creature::applyDamage(int amount, const string& type, const vector<rules::Qualifier>& qualifiers) { if(! conditionApplies(type, qualifiers, dmgImmunities)) { if(conditionApplies(type, qualifiers, dmgResistances)) { hp -= amount / 2; @@ -131,9 +124,45 @@ namespace creature { inventory.push_back(item); } - void Creature::removeInventoryItem(const string& name) { + shared_ptr<entry::Spellcasting> Creature::getSpellcasting() const { + for(auto f : getFeatures()) { + if(f->getType() == "spells") { + return dynamic_pointer_cast<entry::Spellcasting>(f); + } + } + return shared_ptr<entry::Spellcasting>(); + } + + void Creature::addSpell(shared_ptr<entry::Spell> spell) { + auto sc = getSpellcasting(); + if(! sc) { + throw runtime_error("Creature " + getName() + " has no spellcasting"); + } + std::size_t level = spell->getLevel(); + while(sc->getSlotLevels().size() <= level) { + sc->addSlotLevel(); + } + sc->getSlotLevels()[level]->spells.push_back(spell); + } + + void Creature::removeSpell(shared_ptr<entry::Spell> spell) { + auto sc = getSpellcasting(); + if(sc) { + if(sc->getSlotLevels().size() > (size_t) spell->getLevel()) { + auto sl = sc->getSlotLevels()[spell->getLevel()]; + for(auto it = sl->spells.begin(); it != sl->spells.end(); it++) { + if(*it == spell) { + sl->spells.erase(it); + break; + } + } + } + } + } + + void Creature::removeInventoryItem(shared_ptr<entry::Item> item) { for(auto it = inventory.begin(); it != inventory.end(); it++) { - if((*it)->getName() == name) { + if(*it == item) { inventory.erase(it); break; } @@ -158,7 +187,7 @@ namespace creature { void Creature::setScore(const rules::Ability& ability, int score) { int initBonus = getBonus(ability); - stats.insert({ability, score}); + stats[ability] = score; if(ability == rules::Ability::Con()) { int delta = getBonus(ability) - initBonus; hpMax += delta * hdCount; @@ -167,7 +196,14 @@ namespace creature { } void Creature::setProfLevel(const rules::Skill& skill, int level) { - skills.insert({skill, level}); + skills[skill] = level; + if(level == 0) { + skills.erase(skill); + } + } + + void Creature::longRest() { + hp = hpMax; } const int getAC(const Creature& c) { @@ -267,6 +303,7 @@ namespace creature { if(! c.getLanguages().empty()) { text << "Languages: " << c.getLanguages() << endl; } + text << "Proficiency: " << c.getProficiency() << endl; if(! c.getSkills().empty()) { text << endl << "Skills:" << endl; for(auto skill : c.getSkills()) { diff --git a/src/creature.h b/src/creature.h index ee4d5ba..cdbd2b9 100644 --- a/src/creature.h +++ b/src/creature.h @@ -10,6 +10,8 @@ namespace entry { class Item; class Weapon; class Attack; + class Spell; + class Spellcasting; } typedef nlohmann::json json; @@ -19,9 +21,9 @@ namespace creature { class dmgType : public Jsonable { public: - dmgType(const json& data) : type(data["type"]), qualifiers(data["qualifiers"]) {} + dmgType(const json& data) : type(data["type"]), qualifiers(json2vec<rules::Qualifier>(data["qualifiers"])) {} std::string type; - std::vector<std::string> qualifiers; + std::vector<rules::Qualifier> qualifiers; std::string getText() const { if(qualifiers.empty()) { return type; @@ -74,6 +76,7 @@ namespace creature { int getBonus(const rules::Ability& ability) const {return std::floor((getScore(ability) - 10) / 2.0);} int getProficiency(void) const {return proficiency;} std::vector<std::shared_ptr<entry::Feature>> getFeatures(void) const {return features;} + std::shared_ptr<entry::Spellcasting> getSpellcasting(void) const; std::vector<std::shared_ptr<entry::Item>> getInventory(void) const {return inventory;} std::vector<dmgType> getDmgImmunities(void) const {return dmgImmunities;} std::vector<dmgType> getDmgResistances(void) const {return dmgResistances;} @@ -83,12 +86,16 @@ namespace creature { // Setters (mutators) void setGivenName(std::string name) {givenName = name;} - void applyDamage(int amount, const std::string& type, const std::vector<std::string>& qualifiers); + void applyDamage(int amount, const std::string& type, const std::vector<rules::Qualifier>& qualifiers); void applyHealing(int amount); void setScore(const rules::Ability& ability, int score); void setProfLevel(const rules::Skill& skill, int level); + void setProficiency(int prof) {proficiency = prof;} void addInventoryItem(std::shared_ptr<entry::Item> item); - void removeInventoryItem(const std::string& itemName); + void addSpell(std::shared_ptr<entry::Spell> spell); + void removeSpell(std::shared_ptr<entry::Spell> spell); + void removeInventoryItem(std::shared_ptr<entry::Item> item); + void longRest(void); virtual json toJson(void) const; @@ -100,6 +107,7 @@ namespace creature { std::vector<std::shared_ptr<entry::Item>> inventory; std::map<rules::Ability, int> stats; std::map<rules::Skill, int> skills; + int proficiency; //Immutable variables //const std::string creatureName; @@ -113,7 +121,6 @@ namespace creature { const std::vector<std::string> senses; const std::string langs; const double cr; - const int proficiency; const std::string natArmorName; const int natArmorBonus; const std::vector<dmgType> dmgImmunities; 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) { diff --git a/src/entry.h b/src/entry.h index e354c82..ced8544 100644 --- a/src/entry.h +++ b/src/entry.h @@ -36,7 +36,9 @@ namespace entry { } protected: - Entry(const nlohmann::json& data) : entry(data["entry"]), name(data["name"]), type(data["type"]), text(data["text"]) {}; + Entry(const nlohmann::json& data) : Entry(data["entry"], data["name"], data["type"], data["text"]) {}; + // Can also be instantiated programmatically + Entry(const std::string& entry, const std::string& name, const std::string& type, const std::string& text) : entry(entry), name(name), type(type), text(text) {}; private: const std::string entry; diff --git a/src/feature.cc b/src/feature.cc index 1f83402..c42aba2 100644 --- a/src/feature.cc +++ b/src/feature.cc @@ -11,7 +11,7 @@ typedef nlohmann::json json; namespace entry { shared_ptr<Feature> Feature::create(const json& data) { - if(data["type"] == "spellcasting") { + if(data["type"] == "spells") { return utils::loadDFromJson<Feature, Spellcasting>(data); } else if(data["type"] == "attack") { return utils::loadDFromJson<Feature, Attack>(data); diff --git a/src/feature.h b/src/feature.h index 69112ea..5b900d2 100644 --- a/src/feature.h +++ b/src/feature.h @@ -11,5 +11,6 @@ namespace entry { protected: Feature(const nlohmann::json& data) : Entry(data) {} + Feature(const std::string& entry, const std::string& name, const std::string& type, const std::string& text) : Entry(entry, name, type, text) {}; }; } diff --git a/src/jsonable.h b/src/jsonable.h index 2c0da6b..0385fb1 100644 --- a/src/jsonable.h +++ b/src/jsonable.h @@ -16,11 +16,11 @@ template<typename T> std::vector<T> json2vec(const nlohmann::json& data) { return std::vector<T>(begin(data), end(data)); } -template<typename T> std::vector<T> jsonList2vec(const std::string& type, const std::vector<std::string>& names) { - std::vector<T> ret; +template<typename T> std::vector<std::shared_ptr<T>> jsonList2ptrvec(const std::string& type, const std::vector<std::string>& names) { + std::vector<std::shared_ptr<T>> ret; for(auto name : names) { auto j = utils::loadJson(type, name); - ret.push_back(T(j, j)); + ret.push_back(std::shared_ptr<T>(new T(j, j))); } return ret; } @@ -32,3 +32,11 @@ template<typename T> std::vector<std::shared_ptr<T>> json2ptrvec(const nlohmann: } return ret; } + +template<typename T> std::vector<nlohmann::json> ptrvec2json(std::vector<T> src) { + std::vector<nlohmann::json> ret; + for(T i : src) { + ret.push_back(i->toJson()); + } + return ret; +} diff --git a/src/rules.cc b/src/rules.cc index 15dcb34..96d9a51 100644 --- a/src/rules.cc +++ b/src/rules.cc @@ -31,6 +31,12 @@ namespace rules { {"Persuasion", "cha"} }; + const std::map<std::string, std::string> Qualifier::negative2positive { + {"nonmagical", "magical"}, + {"non-silvered", "silvered"}, + {"non-adamantine", "adamantine"} + }; + std::ostream& operator<<(std::ostream& os, const Ability& a) { os << std::string(a); return os; @@ -40,4 +46,9 @@ namespace rules { os << std::string(s); return os; } + + std::ostream& operator<<(std::ostream& os, const Qualifier& q) { + os << std::string(q); + return os; + } } diff --git a/src/rules.h b/src/rules.h index 18cdef0..f41f7ad 100644 --- a/src/rules.h +++ b/src/rules.h @@ -1,5 +1,6 @@ #pragma once #include "jsonable.h" +#include "utils.h" #include <vector> #include <map> #include <string> @@ -19,9 +20,14 @@ namespace rules { } 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() {} - explicit Ability(const nlohmann::json& data) : abbrev(data) {} + explicit Ability(const nlohmann::json& data) : abbrev(data) { + if(! abilities.contains(abbrev)) { + throw std::invalid_argument("No such ability: " + abbrev); + } + } virtual ~Ability() {} @@ -32,10 +38,10 @@ namespace rules { static Ability Wis() {return Ability("wis");} static Ability Cha() {return Ability("cha");} - static Ability string2ability(std::string s) { - transform(s.begin(), s.end(), s.begin(), ::tolower); + static Ability fromString(std::string s) { + utils::lower(s); for(auto [abbrev, full] : abilities) { - transform(full.begin(), full.end(), full.begin(), ::tolower); + utils::lower(full); if(s == abbrev || s == full) { return Ability(abbrev); } @@ -59,6 +65,7 @@ namespace rules { } 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() {} @@ -80,14 +87,19 @@ namespace rules { static Skill Intimidation() {return Skill("Intimidation");} static Skill Performance() {return Skill("Performance");} static Skill Persuasion() {return Skill("Persuasion");} + + Skill() {}; + explicit Skill(const nlohmann::json& data) : name(data) { + if(! skill2ability.contains(name)) { + throw std::invalid_argument("No such skill: " + name); + } + } - explicit Skill(const nlohmann::json& data) : name(data) {} - - static Skill string2skill(std::string s) { - transform(s.begin(), s.end(), s.begin(), ::tolower); + static Skill fromString(std::string s) { + utils::lower(s); for(auto& [name, _] : skill2ability) { std::string n = name; - transform(n.begin(), n.end(), n.begin(), ::tolower); + utils::lower(n); if(s == n) { return Skill(name); } @@ -100,7 +112,35 @@ namespace rules { static const std::map<std::string, std::string> skill2ability; }; - + + class Qualifier : public Jsonable { + public: + Qualifier() {} + Qualifier(const nlohmann::json& data) : negative(data) { + if(! negative2positive.contains(negative)) { + throw std::invalid_argument("No such qualifier: " + negative); + } + } + std::string getNegative() const {return negative;} + std::string getPositive() const {return negative2positive.at(getNegative());} + operator std::string() const {return getNegative();} + virtual nlohmann::json toJson(void) 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");} + static Qualifier Adamantine() {return Qualifier("non-adamantine");} + + private: + const std::string negative; + + static const std::map<std::string, std::string> negative2positive; + }; + 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); } diff --git a/src/settings.cc b/src/settings.cc index 96dc158..1ae3f07 100644 --- a/src/settings.cc +++ b/src/settings.cc @@ -40,4 +40,6 @@ namespace settings { autoExpandEnvironmentVariables(ret); return ret; } + + const std::vector<std::string> objectTypes {"weapons", "armor", "spells"}; } diff --git a/src/settings.h b/src/settings.h index a2312df..1109bcd 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,6 +1,9 @@ #pragma once #include <string> +#include <vector> namespace settings { std::string getString(const std::string& key); + + extern const std::vector<std::string> objectTypes; } diff --git a/src/spellcasting.cc b/src/spellcasting.cc index 9b39741..2606b53 100644 --- a/src/spellcasting.cc +++ b/src/spellcasting.cc @@ -8,6 +8,20 @@ using namespace std; namespace entry { + shared_ptr<SlotLevel> SlotLevel::create(const json& data) { + return shared_ptr<SlotLevel>(new SlotLevel(data)); + } + + vector<shared_ptr<Spell>> Spellcasting::getSpells() const { + vector<shared_ptr<Spell>> ret; + for(auto sl : getSlotLevels()) { + for(auto spell : sl->spells) { + ret.push_back(spell); + } + } + return ret; + } + string Spellcasting::getText(const creature::Creature& c) const { return genText(*this, c); } @@ -21,10 +35,15 @@ namespace entry { if(s.isInnate()) { text << " Spellcasting is innate."; } - int levelNumber = 0; - for(auto level : s.getSpellsBySlot()) { + int levelNumber = -1; + for(auto level : s.getSlotLevels()) { + levelNumber++; + // Skip if it's empty + if(level->numSlots == 0 && level->spells.empty()) { + continue; + } text << endl; - if(level.numSlots == 0) { + if(levelNumber == 0) { if(s.isInnate()) { text << " At will: "; } else { @@ -32,17 +51,16 @@ namespace entry { } } else { if(s.isInnate()) { - text << " " << level.numSlots << "/day each: "; + text << " " << level->numSlots << "/day each: "; } else { - text << " " << utils::toOrdinal(levelNumber) << " level (" << level.numSlots << " slots): "; + text << " " << utils::toOrdinal(levelNumber) << " level (" << level->numSlots << " slots): "; } } vector<string> names; - for(auto spell : level.spells) { - names.push_back(spell.getName()); + for(auto spell : level->spells) { + names.push_back(spell->getName()); } text << utils::join(names, ", "); - levelNumber++; } return text.str(); diff --git a/src/spellcasting.h b/src/spellcasting.h index 0bbdf84..331a95f 100644 --- a/src/spellcasting.h +++ b/src/spellcasting.h @@ -4,20 +4,23 @@ #include "spell.h" #include "jsonable.h" #include "rules.h" +#include <memory> typedef nlohmann::json json; namespace entry { struct SlotLevel : public Jsonable { - SlotLevel(const json& data) : numSlots(data["slots"]), spells(jsonList2vec<Spell>("spellcasting", data["spells"])) {} + SlotLevel(const json& data) : numSlots(data["slots"]), spells(jsonList2ptrvec<Spell>("spells", data["spells"])) {} + SlotLevel() : numSlots(0) {} virtual ~SlotLevel() {} - const int numSlots; - const std::vector<Spell> spells; + int numSlots; + std::vector<std::shared_ptr<Spell>> spells; + static std::shared_ptr<SlotLevel> create(const nlohmann::json& data); json toJson(void) const { std::vector<std::string> s; for(auto spell : spells) { - s.push_back(spell.getName()); + s.push_back(spell->getName()); } return json({ {"slots", numSlots}, @@ -28,26 +31,30 @@ namespace entry { class Spellcasting : public Feature { public: - Spellcasting(const json& data, const json& base) : Feature(base), innate(data["innate"]), ability(rules::Ability(data["spellcasting_ability"])), spellsBySlot(json2vec<SlotLevel>(data["levels"])) {} + Spellcasting(const json& data, const json& base) : Feature(base), innate(data["innate"]), ability(rules::Ability(data["spellcasting_ability"])), spellsBySlot(json2ptrvec<SlotLevel>(data["levels"])) {} + // Can also be instantiated programatically + Spellcasting(const std::string& entry, const std::string& name, const std::string& type, const std::string& text, const rules::Ability& ability, bool isInnate) : Feature(entry, name, type, text), innate(isInnate), ability(ability) {} virtual ~Spellcasting() {} bool isInnate(void) const {return innate;} rules::Ability getAbility(void) const {return ability;} - std::vector<SlotLevel> getSpellsBySlot(void) const {return spellsBySlot;} + const std::vector<std::shared_ptr<SlotLevel>>& getSlotLevels(void) const {return spellsBySlot;} + void addSlotLevel(void) {spellsBySlot.push_back(std::shared_ptr<SlotLevel>(new SlotLevel()));} + std::vector<std::shared_ptr<Spell>> getSpells(void) const; virtual std::string getText(const creature::Creature& c) const; virtual json toJson(void) const { auto data = Feature::toJson(); data["innate"] = innate; data["spellcasting_ability"] = ability; - data["levels"] = spellsBySlot; + data["levels"] = ptrvec2json(spellsBySlot); return data; } private: const bool innate; const rules::Ability ability; - const std::vector<SlotLevel> spellsBySlot; + std::vector<std::shared_ptr<SlotLevel>> spellsBySlot; }; std::string genText(const Spellcasting& s, const creature::Creature& c); diff --git a/src/utils.cc b/src/utils.cc index f58dc61..54fa38e 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -8,6 +8,7 @@ #include <filesystem> #include <stdexcept> #include <map> +#include <algorithm> nlohmann::json utils::loadJson(const std::string& path) { std::ifstream f(path); @@ -25,6 +26,15 @@ nlohmann::json utils::loadJson(const std::string& type, const std::string& name) throw std::invalid_argument("Unknown name: `" + name + "' for type `" + type + "'."); } +nlohmann::json utils::findByName(const std::string& name) { + for(auto type : settings::objectTypes) { + try { + return utils::loadJson(type, name); + } catch(std::exception& e) {} // eat. + } + throw std::invalid_argument("Could not find data matching: " + name); +} + void utils::saveJson(const nlohmann::json& data, const std::string& path) { std::ofstream f(path); f << std::setw(4) << data << std::endl; @@ -103,3 +113,8 @@ std::string utils::toOrdinal(std::size_t number) { } return std::to_string(number) + suffix; } + +std::string utils::lower(std::string& in) { + std::transform(in.begin(), in.end(), in.begin(), ::tolower); + return in; +} diff --git a/src/utils.h b/src/utils.h index 2a66101..bf789e1 100644 --- a/src/utils.h +++ b/src/utils.h @@ -17,8 +17,14 @@ namespace utils { // looks up directory in settings. Returns element matching name. nlohmann::json loadJson(const std::string& type, const std::string& name); + // goes through the available types and searches for the one matching name. + nlohmann::json findByName(const std::string& name); + void saveJson(const nlohmann::json& data, const std::string& path); + // converts in-place + std::string lower(std::string& in); + template<typename S, typename D> std::shared_ptr<S> loadDFromJson(const nlohmann::json& data) { try { return std::shared_ptr<S>(new D(loadJson(data["type"], data["name"]), data)); diff --git a/src/weapon.cc b/src/weapon.cc index dd60b6c..db81047 100644 --- a/src/weapon.cc +++ b/src/weapon.cc @@ -120,7 +120,7 @@ namespace entry { auto abilities = getAbilityOptions(*this); string abilityString; if(abilities.size() == 1) { - abilityString = abilities[0]; + abilityString = string(abilities[0]); } else { abilityString = "max(" + utils::join(abilities, ", ") + ")"; } |