aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYour Name <you@example.com>2021-05-05 09:44:50 -0400
committerYour Name <you@example.com>2021-05-05 09:44:50 -0400
commit2a9f262e6db5906db445d465e500d7ba8c90fab3 (patch)
tree34f850754b0c9114ede9d7b2bb8da90dffddc4fe
parent8614137f7f32f2c9f3c11419110cd70dd7f3b505 (diff)
downloaddmtool-2a9f262e6db5906db445d465e500d7ba8c90fab3.tar.gz
dmtool-2a9f262e6db5906db445d465e500d7ba8c90fab3.tar.bz2
dmtool-2a9f262e6db5906db445d465e500d7ba8c90fab3.zip
Implemented additional commands
-rwxr-xr-xparser/scrapeToJson.py5
-rw-r--r--src/creature.cc75
-rw-r--r--src/creature.h17
-rw-r--r--src/dmtool.cc249
-rw-r--r--src/entry.h4
-rw-r--r--src/feature.cc2
-rw-r--r--src/feature.h1
-rw-r--r--src/jsonable.h14
-rw-r--r--src/rules.cc11
-rw-r--r--src/rules.h60
-rw-r--r--src/settings.cc2
-rw-r--r--src/settings.h3
-rw-r--r--src/spellcasting.cc34
-rw-r--r--src/spellcasting.h23
-rw-r--r--src/utils.cc15
-rw-r--r--src/utils.h6
-rw-r--r--src/weapon.cc2
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, ", ") + ")";
}