aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYour Name <you@example.com>2021-05-01 15:10:54 -0400
committerYour Name <you@example.com>2021-05-01 15:10:54 -0400
commit7b5d1e3d46e94262a9c0fd3a01ab4685aea9d12d (patch)
treed9b808542216f71dbab053ad23145903e96c6401 /src
parent5a813a75412ac9b8fadb90c9abd46dd95aee8e9b (diff)
downloaddmtool-7b5d1e3d46e94262a9c0fd3a01ab4685aea9d12d.tar.gz
dmtool-7b5d1e3d46e94262a9c0fd3a01ab4685aea9d12d.tar.bz2
dmtool-7b5d1e3d46e94262a9c0fd3a01ab4685aea9d12d.zip
Added bash completion, amongst others
Diffstat (limited to 'src')
-rw-r--r--src/attack.h30
-rw-r--r--src/creature.cc40
-rw-r--r--src/creature.h20
-rw-r--r--src/dice.cc11
-rw-r--r--src/dice.h8
-rw-r--r--src/dmtool.bash94
-rw-r--r--src/dmtool.cc266
-rw-r--r--src/entry.cc4
-rw-r--r--src/entry.h4
-rw-r--r--src/feature.cc3
-rw-r--r--src/item.cc16
-rw-r--r--src/rules.cc33
-rw-r--r--src/rules.h70
-rw-r--r--src/settings.cc13
-rw-r--r--src/spellcasting.h2
-rw-r--r--src/utils.cc3
-rw-r--r--src/utils.h12
-rw-r--r--src/weapon.cc82
-rw-r--r--src/weapon.h46
19 files changed, 580 insertions, 177 deletions
diff --git a/src/attack.h b/src/attack.h
new file mode 100644
index 0000000..0a9ad68
--- /dev/null
+++ b/src/attack.h
@@ -0,0 +1,30 @@
+#pragma once
+#include "feature.h"
+#include "json.hpp"
+#include "weapon.h"
+
+typedef nlohmann::json json;
+
+namespace creature {
+ class Creature;
+}
+
+namespace entry {
+ class Attack : public Feature {
+ public:
+ Attack(const json& data, const json& base): Feature(base), weapon(data["attack"], data["attack"]) {}
+ virtual ~Attack() {}
+
+ virtual std::string getText(const creature::Creature& c) const override {return weapon.getText(c) + " " + Entry::getText();}
+ Weapon getWeapon(void) {return weapon;}
+
+ json toJson(void) const {
+ auto data = Feature::toJson();
+ data["attack"] = weapon.toJson();
+ return data;
+ }
+
+ private:
+ const Weapon weapon;
+ };
+}
diff --git a/src/creature.cc b/src/creature.cc
index ee735d7..88b26e7 100644
--- a/src/creature.cc
+++ b/src/creature.cc
@@ -5,6 +5,7 @@
#include "feature.h"
#include "weapon.h"
#include "armor.h"
+#include "attack.h"
#include <algorithm>
#include <iostream>
#include <sstream>
@@ -34,7 +35,7 @@ namespace creature {
givenName = "Jerry"; //TODO: Autogenerate
hpMax = this->getBonus(rules::Ability::Con()) * hdCount;
for(int i = 0; i < hdCount; i++) {
- hpMax += roll(hdSides);
+ hpMax += dice::roll(hdSides);
}
hp = hpMax;
}
@@ -61,6 +62,8 @@ namespace creature {
data["langs"] = langs;
data["cr"] = cr;
data["prof"] = proficiency;
+ data["natural_armor"]["name"] = natArmorName;
+ data["natural_armor"]["bonus"] = natArmorBonus;
data["d_immunities"] = dmgImmunities;
data["d_resistances"] = dmgResistances;
data["d_vulnerabilities"] = dmgVulnerabilities;
@@ -162,7 +165,7 @@ namespace creature {
int dex = c.getBonus(rules::Ability::Dex());
int baseBonus = 10 + dex;
int miscBonus = 0;
- for(auto a : getItems<entry::Armor>(c)) {
+ for(auto a : utils::castPtrs<entry::Item, entry::Armor>(c.getInventory())) {
if(c.getScore(rules::Ability::Str()) < a->getStrRequirement()) {
continue;
}
@@ -181,6 +184,24 @@ namespace creature {
return baseBonus + miscBonus;
}
+ vector<shared_ptr<entry::Weapon>> getAttacks(const Creature& c) {
+ vector<shared_ptr<entry::Weapon>> a = utils::castPtrs<entry::Item, entry::Weapon>(c.getInventory());
+ for(auto attack : utils::castPtrs<entry::Feature, entry::Attack>(c.getFeatures())) {
+ a.push_back(shared_ptr<entry::Weapon>(new entry::Weapon(attack->getWeapon())));
+ }
+ return a;
+ }
+
+ rules::Ability getBestAbility(const std::vector<rules::Ability>& abilities, const Creature& c) {
+ const rules::Ability* bestA = &abilities[0];
+ for(auto a : abilities) {
+ if(c.getBonus(a) > c.getBonus(*bestA)) {
+ bestA = &a;
+ }
+ }
+ return *bestA;
+ }
+
template<typename T> vector<string> mapItems(const vector<shared_ptr<T>>& items) {
vector<string> out;
for(auto i : items) {
@@ -189,8 +210,16 @@ namespace creature {
return out;
}
+ vector<string> dmgTypes2text(vector<dmgType> dmg) {
+ vector<string> ret;
+ for(dmgType t : dmg) {
+ ret.push_back(t.getText());
+ }
+ return ret;
+ }
+
string formatDmgTypeVector(string title, vector<dmgType> dmg) {
- return title + ": " + utils::join(dmg, "; ");
+ return title + ": " + utils::join(dmgTypes2text(dmg), "; ");
}
string genText(const Creature& c) {
@@ -199,7 +228,7 @@ namespace creature {
if(! c.getNaturalArmor().first.empty()) {
text << " (" << c.getNaturalArmor().first << ")";
} else {
- string armor = utils::join(mapItems(creature::getItems<entry::Armor>(c)), ", ");
+ string armor = utils::join(mapItems(utils::castPtrs<entry::Item, entry::Armor>(c.getInventory())), ", ");
if(! armor.empty()) {
text << " (" << armor << ")";
}
@@ -261,6 +290,9 @@ namespace creature {
text << " * " << i->getText(c) << endl;
}
}
+ if(! c.Entry::getText().empty()) {
+ text << endl << c.Entry::getText() << endl;
+ }
return text.str();
}
diff --git a/src/creature.h b/src/creature.h
index c3ce5ae..e89978e 100644
--- a/src/creature.h
+++ b/src/creature.h
@@ -8,6 +8,8 @@
namespace entry {
class Feature;
class Item;
+ class Weapon;
+ class Attack;
}
typedef nlohmann::json json;
@@ -20,7 +22,7 @@ namespace creature {
dmgType(const json& data) : type(data["type"]), qualifiers(data["qualifiers"]) {}
std::string type;
std::vector<std::string> qualifiers;
- operator string() const {
+ std::string getText() const {
if(qualifiers.empty()) {
return type;
}
@@ -121,15 +123,9 @@ namespace creature {
};
- // Convenience function to get any instances of T (subclass of Item) in the inventory
- template<typename T> std::vector<std::shared_ptr<T>> getItems(const Creature& c) {
- std::vector<std::shared_ptr<T>> Ts;
- for(auto item : c.getInventory()) {
- std::shared_ptr<T> t = dynamic_pointer_cast<T>(item);
- if(t) {
- Ts.push_back(t);
- }
- }
- return Ts;
- }
+ // Helper function to get all attacks (weapons and pseudo-weapons included)
+ std::vector<std::shared_ptr<entry::Weapon>> getAttacks(const Creature& c);
+
+ // Helper function to get the best ability for this creature (chooses arbitrarily in case of ties)
+ rules::Ability getBestAbility(const std::vector<rules::Ability>& abilities, const Creature& c);
}
diff --git a/src/dice.cc b/src/dice.cc
new file mode 100644
index 0000000..dd7db55
--- /dev/null
+++ b/src/dice.cc
@@ -0,0 +1,11 @@
+#include "dice.h"
+#include <random>
+
+namespace dice {
+ std::mt19937 gen(std::random_device{}());
+
+ int roll(int d) {
+ std::uniform_int_distribution<> dist(1, d);
+ return dist(gen);
+ }
+}
diff --git a/src/dice.h b/src/dice.h
index f9975bc..08910a7 100644
--- a/src/dice.h
+++ b/src/dice.h
@@ -1,9 +1,5 @@
#pragma once
-#include <random>
-std::mt19937 gen(std::random_device{}());
-
-int roll(int d) {
- std::uniform_int_distribution<> dist(1, d);
- return dist(gen);
+namespace dice {
+ int roll(int d);
}
diff --git a/src/dmtool.bash b/src/dmtool.bash
new file mode 100644
index 0000000..41f70e9
--- /dev/null
+++ b/src/dmtool.bash
@@ -0,0 +1,94 @@
+# bash completion file for dmtool
+
+# Modified from bash completion for password-store
+
+_dmtool_complete_entries () {
+ local prefix1="${HOME}/.dmtool/"
+ prefix1="${prefix1%/}/"
+ local prefix2="/usr/share/dmtool/"
+ prefix2="${prefix2%/}/"
+ local suffix=".json"
+ local autoexpand=${1:-0}
+
+ local IFS=$'\n'
+ local items=($(compgen -f $prefix1$cur))
+ items+=($(compgen -f $prefix2$cur))
+
+ # Remember the value of the first item, to see if it is a directory. If
+ # it is a directory, then don't add a space to the completion
+ local firstitem=""
+ # Use counter, can't use ${#items[@]} as we skip hidden directories
+ local i=0 item
+
+ for item in ${items[@]}; do
+ [[ $item =~ /\.[^/]*$ ]] && continue
+
+ # if there is a unique match, and it is a directory with one entry
+ # autocomplete the subentry as well (recursively)
+ if [[ ${#items[@]} -eq 1 && $autoexpand -eq 1 ]]; then
+ while [[ -d $item ]]; do
+ local subitems=($(compgen -f "$item/"))
+ local filtereditems=( ) item2
+ for item2 in "${subitems[@]}"; do
+ [[ $item2 =~ /\.[^/]*$ ]] && continue
+ filtereditems+=( "$item2" )
+ done
+ if [[ ${#filtereditems[@]} -eq 1 ]]; then
+ item="${filtereditems[0]}"
+ else
+ break
+ fi
+ done
+ fi
+
+ # append / to directories
+ [[ -d $item ]] && item="$item/"
+
+ item="${item%$suffix}"
+ local prefix1ed="${item#$prefix1}"
+ COMPREPLY+=("${prefix1ed#$prefix2}")
+ if [[ $i -eq 0 ]]; then
+ firstitem=$item
+ fi
+ let i+=1
+ done
+
+ # The only time we want to add a space to the end is if there is only
+ # one match, and it is not a directory
+ if [[ $i -gt 1 || ( $i -eq 1 && -d $firstitem ) ]]; then
+ compopt -o nospace
+ fi
+}
+
+_dmtool()
+{
+ COMPREPLY=()
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ local commands="ls cp mkdir mv rm roll damage heal reset set help"
+ if [[ $COMP_CWORD -gt 1 ]]; then
+ local lastarg="${COMP_WORDS[$COMP_CWORD-1]}"
+ case "${COMP_WORDS[1]}" in
+ ls|mkdir|rm|reset)
+ _dmtool_complete_entries
+ ;;
+ cp|mv)
+ if [[ $COMP_CWORD -le 3 ]]; then
+ _dmtool_complete_entries
+ fi
+ ;;
+ roll|damage|heal|set|add)
+ if [[ $COMP_CWORD -le 2 ]]; then
+ _dmtool_complete_entries
+ else
+ # Other various stuff
+ :
+ fi
+ ;;
+ esac
+ else
+ COMPREPLY+=($(compgen -W "${commands}" -- ${cur}))
+ _dmtool_complete_entries 1
+ fi
+}
+
+complete -o filenames -F _dmtool dmtool
diff --git a/src/dmtool.cc b/src/dmtool.cc
index 4aee1cf..cf05938 100644
--- a/src/dmtool.cc
+++ b/src/dmtool.cc
@@ -1,57 +1,98 @@
#include "entry.h"
#include "settings.h"
+#include "creature.h"
+#include "dice.h"
+#include "weapon.h"
#include <iostream>
#include <vector>
#include <string>
#include <filesystem>
#include <system_error>
-
-using namespace std;
+#include <exception>
+#include <memory>
+#include <algorithm>
namespace fs = std::filesystem;
-void usage(string exename) {
- cout << "Usage:" << endl;
- string indOpt = " " + exename + " ";
- string indDesc = " ";
- cout << indOpt << "[ls] [subfolder]" << endl;
- cout << indDesc << "List creatures and objects." << endl;
- cout << indOpt << "cp old-path new-path" << endl;
- cout << indDesc << "Copy old-path to new-path." << endl;
- cout << indOpt << "mv old-path new-path" << endl;
- cout << indDesc << "Move old-path to new-path." << endl;
- cout << indOpt << "rm path" << endl;
- cout << indDesc << "Remove existing creature, object, or directory." << endl;
- cout << indOpt << "roll path name" << endl;
- cout << indDesc << "Roll a skill check, save, or attack." << endl;
- cout << indOpt << "damage path amount [type]" << endl;
- cout << indDesc << "Damage creature by amount. Type defaults to \"force\"." << endl;
- cout << indOpt << "heal path amount" << endl;
- cout << indDesc << "Heal creature by amount." << endl;
- cout << indOpt << "reset path" << endl;
- cout << indDesc << "Reset creature to full health (as if completing a long rest)." << endl;
- cout << indOpt << "set path field value" << endl;
- cout << indDesc << "Set a field to a new value, where field is any of:" << endl;
- cout << indDesc << " ability (str, dex, con, int, wis, cha); value is new ability score" << endl;
- cout << indDesc << " skill (athletics, \"sleight of hand\", etc.); value is (none|proficient|expert)" << endl;
- cout << indDesc << " name; value is new given name." << endl;
- cout << indOpt << "add path entry" << endl;
- cout << indDesc << "Add entry to creature, where entry is an item or spell." << endl;
- cout << indOpt << "help" << endl;
- cout << indDesc << "Show this help." << endl;
+void usage(const std::string& exename) {
+ std::cout << "Usage:" << std::endl;
+ std::string indOpt = " " + exename + " ";
+ std::string indDesc = " ";
+ std::cout << indOpt << "[ls] [subfolder]" << std::endl;
+ std::cout << indDesc << "List creatures and objects." << std::endl;
+ std::cout << indOpt << "cp old-path new-path" << std::endl;
+ std::cout << indDesc << "Copy old-path to new-path, instantiating it." << std::endl;
+ std::cout << indOpt << "mkdir path" << std::endl;
+ std::cout << indDesc << "Make a new directory for holding creatures and objects." << std::endl;
+ std::cout << indOpt << "mv old-path new-path" << std::endl;
+ std::cout << indDesc << "Move old-path to new-path." << std::endl;
+ std::cout << indOpt << "rm path" << std::endl;
+ 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 << 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;
+ std::cout << indOpt << "reset path" << std::endl;
+ std::cout << indDesc << "Reset creature to full health (as if completing a long rest)." << std::endl;
+ std::cout << indOpt << "set path field value" << std::endl;
+ 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 << " name; value is new given name." << 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 << "help" << std::endl;
+ std::cout << indDesc << "Show this help." << std::endl;
+}
+
+std::shared_ptr<entry::Entry> instantiate(const fs::path& path) {
+ try {
+ return entry::Entry::create(utils::loadJson(path));
+ } catch(std::exception& e) {
+ if(fs::directory_entry(path).exists()) {
+ throw std::runtime_error("Invalid json: " + path.string());
+ } else {
+ throw std::runtime_error("No such file nor directory: " + path.string());
+ }
+ }
+}
+
+void save(const std::shared_ptr<entry::Entry>& e, const fs::path& path) {
+ utils::saveJson(*e, path);
}
void print(const fs::path& path) {
- auto e = entry::Entry::create(utils::loadJson(path));
- cout << e->getText() << endl;
+ std::cout << instantiate(path)->getText() << std::endl;
}
fs::path getBaseDir() {
return settings::getString("savedir");
}
-fs::path getTruePath(const fs::path& virtPath) {
- fs::path p = getBaseDir() / virtPath;
+fs::path eraseRoot(fs::path& src) {
+ if(src.empty()) return src;
+ fs::path tmp;
+ auto it = src.begin();
+ while(++it != src.end()) {
+ tmp /= *it;
+ }
+ src = tmp;
+ return src;
+}
+
+const std::vector<std::string> virtPaths({"weapons", "armor", "spells", "creatures"});
+
+fs::path getTruePath(fs::path virtPath) {
+ fs::path p;
+ if(std::find(virtPaths.begin(), virtPaths.end(), *virtPath.begin()) != virtPaths.end()) {
+ p = settings::getString(*virtPath.begin());
+ eraseRoot(virtPath);
+ } else {
+ p = getBaseDir();
+ }
+ p /= virtPath;
if(fs::directory_entry(p.string() + ".json").is_regular_file()) return p.string() + ".json";
return p;
}
@@ -61,88 +102,159 @@ void initFS() {
fs::directory_entry de = fs::directory_entry(getBaseDir());
if(! de.exists()) {
fs::create_directories(de);
- fs::copy(settings::getString("weapon"), de.path() / "weapons");
- fs::copy(settings::getString("armor"), de.path() / "armor");
- fs::copy(settings::getString("spellcasting"), de.path() / "spells");
- fs::copy(settings::getString("monsters"), de.path() / "creatures");
}
}
void list(const fs::path& p) {
+ if(p.empty()) {
+ // Print read-only dirs
+ for(std::string name : virtPaths) {
+ std::cout << name << "/ (read only)" << std::endl;
+ }
+ }
fs::path truePath = getTruePath(p);
if(fs::directory_entry(truePath).is_regular_file()) {
print(truePath);
}
else if(fs::directory_entry(truePath).is_directory()) {
- for(fs::directory_entry de : filesystem::directory_iterator(truePath)) {
+ for(fs::directory_entry de : fs::directory_iterator(truePath)) {
if(de.is_directory()) {
- cout << de.path().filename().string() << "/";
+ std::cout << de.path().filename().string() << "/" << std::endl;
} else {
- cout << de.path().stem().string();
+ std::cout << de.path().stem().string() << std::endl;
}
- cout << "" << endl;
}
}
else {
- cerr << "Unknown path " << p << endl;
+ std::cerr << "Unknown path " << p << std::endl;
}
}
-void list(vector<string> args) {
+void list(std::vector<std::string> args) {
if(args.empty()) {
list("");
} else {
- for(string dir : args) {
+ for(std::string dir : args) {
list(dir);
}
}
}
-void cp(vector<string> args) {
+void mkdir(std::vector<std::string> args) {
+ for(std::string s : args) {
+ fs::create_directories(getTruePath(s));
+ }
+}
+
+void cp(fs::path src, fs::path dest) {
+ if(fs::directory_entry(src).is_regular_file()) {
+ save(instantiate(src), dest);
+ } else {
+ mkdir({dest});
+ for(fs::directory_entry de : fs::directory_iterator(src)) {
+ cp(de.path(), dest / de.path().filename());
+ }
+ }
+
+}
+
+void cp(std::vector<std::string> args) {
if(args.size() != 2) {
- cerr << "Subcommand 'cp' expected 2 arguments but got " << args.size() << endl;
+ throw std::runtime_error("Subcommand 'cp' expected 2 arguments but got " + std::to_string(args.size()));
}
- fs::path src = getTruePath(args[0]);
- fs::path dest = getTruePath(args[1]);
- fs::copy(src, dest);
+ // Operate by intantiating and saving
+ // We do recursive!
+ cp(getTruePath(args[0]), getTruePath(args[1]));
}
-void mv(vector<string> args) {}
-void rm(vector<string> args) {}
-void roll(vector<string> args) {}
-void damage(vector<string> args) {}
-void heal(vector<string> args) {}
-void reset(vector<string> args) {}
-void set(vector<string> args) {}
-void add(vector<string> args) {}
+void mv(std::vector<std::string> args) {
+ if(args.size() != 2) {
+ throw std::runtime_error("Subcommand 'mv' expected 2 arguments but got " + std::to_string(args.size()));
+ }
+ fs::rename(getTruePath(args[0]), getTruePath(args[1]));
+}
+
+void rm(std::vector<std::string> args) {
+ for(std::string s : args) {
+ fs::remove_all(getTruePath(s));
+ }
+}
+
+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());
+ }
+ args.erase(args.begin());
+ std::string rollName = utils::join(args, " ");
+ std::transform(rollName.begin(), rollName.end(), rollName.begin(), ::tolower);
+ 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);
+ printResults(skill.getName(), "check", rolled, c->getSkillBonus(skill));
+ return;
+ } catch(std::exception& e) {} // eat.
+ try {
+ rules::Ability ability = rules::Ability::string2ability(rollName);
+ 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;
+ }
+ }
+}
+
+void damage(std::vector<std::string> args) {}
+void heal(std::vector<std::string> args) {}
+void reset(std::vector<std::string> args) {}
+void set(std::vector<std::string> args) {}
+void add(std::vector<std::string> args) {}
int main(int argc, char *argv[]) {
- string exename = argv[0];
- vector<string> args(&argv[1], &argv[argc]);
+ std::string exename = argv[0];
+ std::vector<std::string> args(&argv[1], &argv[argc]);
try {
initFS();
- } catch (fs::filesystem_error& e) {
- cerr << e.what() << endl;
+ } catch(fs::filesystem_error& e) {
+ std::cerr << e.what() << std::endl;
return 1;
}
if(args.empty()) {
list(args);
return 0;
}
- string cmd = args[0];
- vector<string> argsOrig(args);
+ std::string cmd = args[0];
+ std::vector<std::string> argsOrig(args);
args.erase(args.begin());
- if(cmd == "ls") list(args);
- else if(cmd == "cp") cp(args);
- else if(cmd == "mv") mv(args);
- else if(cmd == "rm") rm(args);
- else if(cmd == "roll") roll(args);
- else if(cmd == "damage") damage(args);
- else if(cmd == "heal") heal(args);
- else if(cmd == "reset") reset(args);
- else if(cmd == "set") set(args);
- else if(cmd == "add") add(args);
- else if(cmd == "help") usage(exename);
- else list(argsOrig);
+ try {
+ if(cmd == "ls") list(args);
+ else if(cmd == "cp") cp(args);
+ else if(cmd == "mkdir") mkdir(args);
+ else if(cmd == "mv") mv(args);
+ else if(cmd == "rm") rm(args);
+ else if(cmd == "roll") roll(args);
+ else if(cmd == "damage") damage(args);
+ else if(cmd == "heal") heal(args);
+ else if(cmd == "reset") reset(args);
+ else if(cmd == "set") set(args);
+ else if(cmd == "add") add(args);
+ else if(cmd == "help") usage(exename);
+ else list(argsOrig);
+ } catch(std::exception& e) {
+ std::cerr << e.what() << std::endl;
+ return 1;
+ }
return 0;
}
diff --git a/src/entry.cc b/src/entry.cc
index a3b4133..ef2ec96 100644
--- a/src/entry.cc
+++ b/src/entry.cc
@@ -13,9 +13,9 @@ namespace entry {
return Feature::create(data);
} else if(data["entry"] == "item") {
return Item::create(data);
- } else if(data["entry"] == "creature") {
+ } else if(data["entry"] == "creatures") {
return utils::loadDFromJson<Entry, creature::Creature>(data);
- } else if(data["entry"] == "spell") {
+ } else if(data["entry"] == "spells") {
return utils::loadDFromJson<Entry, Spell>(data);
}
throw std::invalid_argument("Invalid entry: " + std::string(data["entry"]));
diff --git a/src/entry.h b/src/entry.h
index 1badd40..e354c82 100644
--- a/src/entry.h
+++ b/src/entry.h
@@ -28,6 +28,7 @@ namespace entry {
virtual nlohmann::json toJson(void) const {
return nlohmann::json({
+ {"entry", entry},
{"name", name},
{"type", type},
{"text", text}
@@ -35,9 +36,10 @@ namespace entry {
}
protected:
- Entry(const nlohmann::json& data) : name(data["name"]), type(data["type"]), text(data["text"]) {};
+ Entry(const nlohmann::json& data) : entry(data["entry"]), name(data["name"]), type(data["type"]), text(data["text"]) {};
private:
+ const std::string entry;
const std::string name;
const std::string type;
const std::string text;
diff --git a/src/feature.cc b/src/feature.cc
index 4972ccc..1f83402 100644
--- a/src/feature.cc
+++ b/src/feature.cc
@@ -1,6 +1,7 @@
#include "json.hpp"
#include "feature.h"
#include "spellcasting.h"
+#include "attack.h"
#include "utils.h"
#include <sstream>
#include <map>
@@ -12,6 +13,8 @@ namespace entry {
shared_ptr<Feature> Feature::create(const json& data) {
if(data["type"] == "spellcasting") {
return utils::loadDFromJson<Feature, Spellcasting>(data);
+ } else if(data["type"] == "attack") {
+ return utils::loadDFromJson<Feature, Attack>(data);
}
return shared_ptr<Feature>(new Feature(data));
}
diff --git a/src/item.cc b/src/item.cc
index 6c91206..3691c28 100644
--- a/src/item.cc
+++ b/src/item.cc
@@ -12,7 +12,7 @@ typedef nlohmann::json json;
namespace entry {
shared_ptr<Item> Item::create(const json& data) {
- if(data["type"] == "weapon") {
+ if(data["type"] == "weapons") {
return utils::loadDFromJson<Item, Weapon>(data);
} else if(data["type"] == "armor") {
return utils::loadDFromJson<Item, Armor>(data);
@@ -22,8 +22,18 @@ namespace entry {
string genText(const Substantial& s) {
stringstream text;
- text << "Cost: " << s.getCost() << " cp, i.e., " << utils::getCostString(s.getCost());
- text << ". Weight: " << s.getWeight() << " lbs.";
+ if(s.getCost() >= 0) {
+ text << "Cost: ";
+ string costStr = to_string(s.getCost()) + " cp";
+ text << costStr;
+ string condensedCostStr = utils::getCostString(s.getCost());
+ if(costStr != condensedCostStr) {
+ text << ", i.e., " << condensedCostStr;
+ }
+ }
+ if(s.getWeight() >= 0) {
+ text << ". Weight: " << s.getWeight() << " lbs.";
+ }
return text.str();
}
}
diff --git a/src/rules.cc b/src/rules.cc
new file mode 100644
index 0000000..a9ca395
--- /dev/null
+++ b/src/rules.cc
@@ -0,0 +1,33 @@
+#include "rules.h"
+
+namespace rules {
+ const std::map<std::string, std::string> Ability::abilities {
+ {"str", "Strength"},
+ {"dex", "Dexterity"},
+ {"con", "Constitution"},
+ {"int", "Intelligence"},
+ {"wis", "Wisdom"},
+ {"cha", "Charisma"}
+ };
+
+ const std::map<std::string, std::string> Skill::skill2ability {
+ {"Athletics", "str"},
+ {"Acrobatics", "dex"},
+ {"Sleight of Hand", "dex"},
+ {"Stealth", "dex"},
+ {"Arcana", "int"},
+ {"History", "int"},
+ {"Investigation", "int"},
+ {"Nature", "int"},
+ {"Religion", "int"},
+ {"Animal Handling", "wis"},
+ {"Insight", "wis"},
+ {"Medicine", "wis"},
+ {"Perception", "wis"},
+ {"Survival", "wis"},
+ {"Deception", "cha"},
+ {"Intimidation", "cha"},
+ {"Performance", "cha"},
+ {"Persuasion", "cha"}
+ };
+}
diff --git a/src/rules.h b/src/rules.h
index 3a3ffbc..d49f17c 100644
--- a/src/rules.h
+++ b/src/rules.h
@@ -4,16 +4,16 @@
#include <map>
#include <string>
#include <iostream>
-
-using namespace std;
+#include <algorithm>
+#include <stdexcept>
namespace rules {
class Ability : public Jsonable {
public:
- string getFull() const {return abilities.at(getAbbrev());}
- string getAbbrev() const {return abbrev;}
- operator string() const {return getAbbrev();}
+ std::string getFull() const {return abilities.at(getAbbrev());}
+ std::string getAbbrev() const {return abbrev;}
+ operator std::string() const {return getAbbrev();}
virtual nlohmann::json toJson(void) const {
return getAbbrev();
}
@@ -32,23 +32,28 @@ 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);
+ for(auto [abbrev, full] : abilities) {
+ transform(full.begin(), full.end(), full.begin(), ::tolower);
+ if(s == abbrev || s == full) {
+ return Ability(abbrev);
+ }
+ }
+ throw std::invalid_argument("Cannot find an ability for input: \"" + s + "\"");
+ }
+
private:
- const string abbrev;
+ const std::string abbrev;
- const map<string, string> abilities {
- {"str", "Strength"},
- {"dex", "Dexterity"},
- {"con", "Constitution"},
- {"int", "Intelligence"},
- {"wis", "Wisdom"},
- {"cha", "Charisma"}
- };
+ static const std::map<std::string, std::string> abilities;
};
class Skill : public Jsonable {
public:
- string getName() const {return name;}
+ std::string getName() const {return name;}
Ability getAbility() const {return Ability(skill2ability.at(getName()));}
+ operator std::string() const {return getName();}
virtual nlohmann::json toJson(void) const {
return getName();
}
@@ -78,28 +83,21 @@ namespace rules {
explicit Skill(const nlohmann::json& data) : name(data) {}
+ static Skill string2skill(std::string s) {
+ transform(s.begin(), s.end(), s.begin(), ::tolower);
+ for(auto& [name, _] : skill2ability) {
+ std::string n = name;
+ transform(n.begin(), n.end(), n.begin(), ::tolower);
+ if(s == n) {
+ return Skill(name);
+ }
+ }
+ throw std::invalid_argument("Cannot find a skill for input: \"" + s + "\"");
+ }
+
private:
- const string name;
+ const std::string name;
- const map<string, string> skill2ability {
- {"Athletics", "str"},
- {"Acrobatics", "dex"},
- {"Sleight of Hand", "dex"},
- {"Stealth", "dex"},
- {"Arcana", "int"},
- {"History", "int"},
- {"Investigation", "int"},
- {"Nature", "int"},
- {"Religion", "int"},
- {"Animal Handling", "wis"},
- {"Insight", "wis"},
- {"Medicine", "wis"},
- {"Perception", "wis"},
- {"Survival", "wis"},
- {"Deception", "cha"},
- {"Intimidation", "cha"},
- {"Performance", "cha"},
- {"Persuasion", "cha"}
- };
+ static const std::map<std::string, std::string> skill2ability;
};
}
diff --git a/src/settings.cc b/src/settings.cc
index 56c26b3..96dc158 100644
--- a/src/settings.cc
+++ b/src/settings.cc
@@ -2,13 +2,14 @@
#include <map>
#include <cstdlib>
#include <regex>
+#include <stdexcept>
namespace settings {
const std::map<std::string, std::string> dummySettings {
- {"weapon", "/usr/share/dmtool/items/weapons/"},
- {"armor", "/usr/share/dmtool/items/armor/"},
- {"spellcasting", "/usr/share/dmtool/spells/"},
- {"monsters", "/usr/share/dmtool/monsters/"},
+ {"weapons", "/usr/share/dmtool/weapons/"},
+ {"armor", "/usr/share/dmtool/armor/"},
+ {"spells", "/usr/share/dmtool/spells/"},
+ {"creatures", "/usr/share/dmtool/creatures/"},
{"savedir", "~/.dmtool/"}
};
@@ -30,10 +31,10 @@ namespace settings {
}
}
- // Returns the setting, or an empty string in the case of an error
+ // Returns the setting, or an exception in the case of an error
std::string getString(const std::string& key) {
if(! dummySettings.contains(key)) {
- return "";
+ throw std::invalid_argument("Cannot find key: \"" + key + "\"");
}
std::string ret = dummySettings.at(key);
autoExpandEnvironmentVariables(ret);
diff --git a/src/spellcasting.h b/src/spellcasting.h
index 3a55277..0bbdf84 100644
--- a/src/spellcasting.h
+++ b/src/spellcasting.h
@@ -15,7 +15,7 @@ namespace entry {
const std::vector<Spell> spells;
json toJson(void) const {
- std::vector<string> s;
+ std::vector<std::string> s;
for(auto spell : spells) {
s.push_back(spell.getName());
}
diff --git a/src/utils.cc b/src/utils.cc
index a44eced..f58dc61 100644
--- a/src/utils.cc
+++ b/src/utils.cc
@@ -72,6 +72,9 @@ std::vector<std::pair<std::string, int>> utils::copper2coins(int coppers) {
coppers -= amnt.second * largest.second;
ret.push_back(amnt);
}
+ if(ret.empty()) {
+ ret.push_back({"cp", 0});
+ }
return ret;
}
diff --git a/src/utils.h b/src/utils.h
index bdf7ddc..e44bdf2 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -29,6 +29,18 @@ namespace utils {
}
}
+ template<typename F, typename T> std::vector<std::shared_ptr<T>> castPtrs(std::vector<std::shared_ptr<F>> from) {
+ std::vector<std::shared_ptr<T>> Ts;
+ for(std::shared_ptr<F> f : from) {
+ std::shared_ptr<T> t = dynamic_pointer_cast<T>(f);
+ if(t) {
+ Ts.push_back(t);
+ }
+ }
+ return Ts;
+ }
+
+
template<typename Container> std::string join(Container parts, std::string joiner) {
std::stringstream out;
bool isFirst = true;
diff --git a/src/weapon.cc b/src/weapon.cc
index b3dad1d..dd60b6c 100644
--- a/src/weapon.cc
+++ b/src/weapon.cc
@@ -1,6 +1,7 @@
#include "weapon.h"
#include "creature.h"
#include "rules.h"
+#include "dice.h"
#include <string>
#include <sstream>
#include <algorithm>
@@ -8,13 +9,6 @@
using namespace std;
namespace entry {
- int Weapon::getDamageDieSides(bool versatile) const {
- if(versatile && getProperties().count("versatile")) {
- return damageDieSides + 2;
- }
- return damageDieSides;
- }
-
string getTextHelper(const Weapon& w, string toHitBonus, string damageBonus) {
stringstream text;
text << "+" << toHitBonus << " to hit, ";
@@ -27,9 +21,25 @@ namespace entry {
if(w.getRange().second > 0) {
text << "range " << w.getRange().first << "/" << w.getRange().second << " ft.";
}
- text << " Hit: " << w.getDamageDieCount() << "d" << w.getDamageDieSides() << " + " << damageBonus << " " << w.getDamageType() << " damage";
- if(w.getProperties().count("versatile")) {
- text << " (or " << w.getDamageDieCount() << "d" << w.getDamageDieSides(true) << " + " << damageBonus << " " << w.getDamageType() << " damage if two-handed)";
+ text << " Hit: ";
+ auto dmgs = w.getDamage();
+ for(size_t i = 0; i < dmgs.size(); i++) {
+ const Damage& d = dmgs[i];
+ text << d.count << "d" << d.sides;
+ if(i == 0) {
+ if(w.getProperties().count("versatile")) {
+ text << " (or " << d.count << "d" << d.sides + 2 << " if two-handed)";
+ }
+ text << " + " << damageBonus;
+ }
+ text << " " << d.type << " damage";
+ if(i < dmgs.size()-1) {
+ if(d.isOr) {
+ text << " or ";
+ } else {
+ text << " plus ";
+ }
+ }
}
text << ".";
auto props = w.getProperties();
@@ -39,10 +49,56 @@ namespace entry {
if(! props.empty()) {
text << " Additional properties: " << utils::join(props, ", ") << ".";
}
+ if(! w.Entry::getText().empty()) {
+ text << " " << w.Entry::getText();
+ }
text << " " << genText(static_cast<const Substantial&>(w));
return text.str();
}
+ vector<Damage> rollDmg(const Weapon& w, bool versatile) {
+ vector<Damage> dmgs = w.getDamage();
+ bool first = true;
+ for(Damage& d : dmgs) {
+ d.rolled = 0;
+ int sides = d.sides;
+ if(first && versatile && w.getProperties().count("versatile")) {
+ sides += 2;
+ }
+ first = false;
+ for(int i = 0; i < d.count; i++) {
+ d.rolled += dice::roll(sides);
+ }
+ }
+ return dmgs;
+ }
+
+ string formatDmg(const Weapon& w, const creature::Creature& c) {
+ stringstream text;
+ vector<Damage> dmgsNoVersatile = rollDmg(w, false);
+ vector<Damage> dmgsVersatile = rollDmg(w, true);
+ int abilityBonus = c.getBonus(creature::getBestAbility(getAbilityOptions(w), c));
+ for(size_t i = 0; i < dmgsNoVersatile.size(); i++) {
+ if(i == 0) {
+ text << dmgsNoVersatile[i].rolled + abilityBonus;
+ if(w.getProperties().count("versatile")) {
+ text << " (or " << dmgsVersatile[i].rolled + abilityBonus << " if two-handed)";
+ }
+ } else {
+ text << dmgsNoVersatile[i].rolled;
+ }
+ text << " " << dmgsNoVersatile[i].type << " damage";
+ if(i < dmgsNoVersatile.size()-1) {
+ if(dmgsNoVersatile[i].isOr) {
+ text << " or ";
+ } else {
+ text << " plus ";
+ }
+ }
+ }
+ return text.str();
+ }
+
vector<rules::Ability> getAbilityOptions(const Weapon& w) {
// Do finesse
if(w.getProperties().count("finesse")) {
@@ -76,11 +132,7 @@ namespace entry {
stringstream text;
text << genText(static_cast<const Item&>(w), c);
// Determine best ability bonus
- auto abilities = getAbilityOptions(w);
- int abilityBonus = c.getBonus(abilities[0]);
- if(abilities.size() == 2) {
- abilityBonus = max(abilityBonus, c.getBonus(abilities[1]));
- }
+ int abilityBonus = c.getBonus(creature::getBestAbility(getAbilityOptions(w), c));
text << ": " << getTextHelper(w, to_string(abilityBonus + c.getProficiency()), to_string(abilityBonus));
return text.str();
}
diff --git a/src/weapon.h b/src/weapon.h
index 7a8c0ba..9384f78 100644
--- a/src/weapon.h
+++ b/src/weapon.h
@@ -1,5 +1,6 @@
#pragma once
#include "item.h"
+#include "rules.h"
#include "json.hpp"
#include <set>
@@ -9,44 +10,61 @@ namespace creature {
namespace entry {
class Weapon;
+ class Damage;
std::string genText(const Weapon& w, const creature::Creature& c);
+ std::vector<rules::Ability> getAbilityOptions(const Weapon& w);
+ std::vector<Damage> rollDmg(const Weapon& w, bool versatile=false);
+ std::string formatDmg(const Weapon& w, const creature::Creature& c);
+
+ class Damage : public Jsonable {
+ public:
+ Damage(const nlohmann::json& data) : count(data["dmg_die_count"]), sides(data["dmg_die_sides"]), type(data["dmg_type"]), isOr(data["is_or"]) {}
+ const int count;
+ const int sides;
+ const std::string type;
+ const bool isOr;
+ int rolled = 0;
+
+ nlohmann::json toJson(void) const {
+ return nlohmann::json({
+ {"dmg_die_count", count},
+ {"dmg_die_sides", sides},
+ {"dmg_type", type},
+ {"is_or", isOr}
+ });
+ }
+ };
class Weapon : public Item, public Substantial {
public:
- Weapon(const nlohmann::json& data, const nlohmann::json& base) : Item(base), damageType(data["damage"]["dmg_type"]), damageDieCount(data["damage"]["dmg_die_count"]), damageDieSides(data["damage"]["dmg_die_sides"]), properties(data["properties"]), weaponType(data["weapon_type"]), range(data["range"][0], data["range"][1]), reach(data["reach"]), cost(data["cost"]), weight(data["weight"]) {}
+ Weapon(const nlohmann::json& data, const nlohmann::json& base) : Item(base), damage(json2vec<Damage>(data["damage"])), properties(data["properties"]), weaponType(data["weapon_type"]), range(data["range"][0], data["range"][1]), reach(data["reach"]), cost(data["cost"]), weight(data["weight"]) {}
- std::string getDamageType(void) const {return damageType;}
- int getDamageDieCount(void) const {return damageDieCount;}
- int getDamageDieSides(bool versatile=false) const;
+ std::vector<Damage> getDamage(void) const {return damage;}
std::set<std::string> getProperties(void) const {return properties;}
std::string getWeaponType(void) const {return weaponType;}
std::pair<int, int> getRange(void) const {return range;}
int getReach(void) const {return reach;}
int getCost(void) const {return cost;}
double getWeight(void) const {return weight;}
-
+
virtual std::string getText() const override;
virtual std::string getText(const creature::Creature& c) const override {return genText(*this, c);}
- /*virtual nlohmann::json toJson(void) const {
+ virtual nlohmann::json toJson(void) const {
auto data = Item::toJson();
- data["damage"]["dmg_type"] = damageType;
- data["damage"]["dmg_die_count"] = damageDieCount;
- data["damage"]["dmg_die_sides"] = damageDieSides;
+ data["damage"] = damage;
data["properties"] = properties;
- data["type"] = weaponType;
+ data["weapon_type"] = weaponType;
data["range"] = range;
data["reach"] = reach;
data["cost"] = cost;
data["weight"] = weight;
return data;
- }*/
+ }
private:
- const std::string damageType;
- const int damageDieCount;
- const int damageDieSides;
+ const std::vector<Damage> damage;
const std::set<std::string> properties;
const std::string weaponType;
const std::pair<const int, const int> range;