diff options
Diffstat (limited to 'src')
| -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 | 
16 files changed, 405 insertions, 113 deletions
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, ", ") + ")";          }  | 
