diff options
author | Your Name <you@example.com> | 2021-05-01 15:10:54 -0400 |
---|---|---|
committer | Your Name <you@example.com> | 2021-05-01 15:10:54 -0400 |
commit | 7b5d1e3d46e94262a9c0fd3a01ab4685aea9d12d (patch) | |
tree | d9b808542216f71dbab053ad23145903e96c6401 | |
parent | 5a813a75412ac9b8fadb90c9abd46dd95aee8e9b (diff) | |
download | dmtool-7b5d1e3d46e94262a9c0fd3a01ab4685aea9d12d.tar.gz dmtool-7b5d1e3d46e94262a9c0fd3a01ab4685aea9d12d.tar.bz2 dmtool-7b5d1e3d46e94262a9c0fd3a01ab4685aea9d12d.zip |
Added bash completion, amongst others
-rw-r--r-- | Makefile | 23 | ||||
-rwxr-xr-x | configure | 1 | ||||
-rwxr-xr-x | parser/scrapeToJson.py | 25 | ||||
-rwxr-xr-x | parser/utils.py | 12 | ||||
-rw-r--r-- | src/attack.h | 30 | ||||
-rw-r--r-- | src/creature.cc | 40 | ||||
-rw-r--r-- | src/creature.h | 20 | ||||
-rw-r--r-- | src/dice.cc | 11 | ||||
-rw-r--r-- | src/dice.h | 8 | ||||
-rw-r--r-- | src/dmtool.bash | 94 | ||||
-rw-r--r-- | src/dmtool.cc | 266 | ||||
-rw-r--r-- | src/entry.cc | 4 | ||||
-rw-r--r-- | src/entry.h | 4 | ||||
-rw-r--r-- | src/feature.cc | 3 | ||||
-rw-r--r-- | src/item.cc | 16 | ||||
-rw-r--r-- | src/rules.cc | 33 | ||||
-rw-r--r-- | src/rules.h | 70 | ||||
-rw-r--r-- | src/settings.cc | 13 | ||||
-rw-r--r-- | src/spellcasting.h | 2 | ||||
-rw-r--r-- | src/utils.cc | 3 | ||||
-rw-r--r-- | src/utils.h | 12 | ||||
-rw-r--r-- | src/weapon.cc | 82 | ||||
-rw-r--r-- | src/weapon.h | 46 |
23 files changed, 620 insertions, 198 deletions
@@ -2,7 +2,7 @@ CC=g++ LIBS= CFLAGS=-c -Wall -fPIC -std=c++20 LDFLAGS= -SOURCES=src/armor.cc src/creature.cc src/dmtool.cc src/entry.cc src/feature.cc src/item.cc src/settings.cc src/spellcasting.cc src/spell.cc src/utils.cc src/weapon.cc +SOURCES=src/armor.cc src/creature.cc src/dice.cc src/dmtool.cc src/entry.cc src/feature.cc src/item.cc src/rules.cc src/settings.cc src/spellcasting.cc src/spell.cc src/utils.cc src/weapon.cc OBJECTS=$(SOURCES:.cc=.o) LIBRARY= EXECUTABLE=dmtool @@ -21,6 +21,7 @@ install: $(EXECUTABLE) parsed cp -r parsed/* $(DESTDIR)$(PREFIX)/share/dmtool/ install -d $(DESTDIR)$(PREFIX)/bin/ install -m 755 $(EXECUTABLE) $(DESTDIR)$(PREFIX)/bin/ + install -m 644 src/dmtool.bash $(pkg-config --variable=completionsdir bash-completion)/dmtool $(EXECUTABLE): $(OBJECTS) $(CC) $(OBJECTS) -o $@ $(LDFLAGS) @@ -32,11 +33,15 @@ src/armor.o: src/armor.cc src/armor.h src/item.h src/json.hpp src/entry.h \ src/creature.o: src/creature.cc src/creature.h src/json.hpp src/rules.h \ src/jsonable.h src/utils.h src/entry.h src/dice.h src/feature.h \ - src/weapon.h src/item.h src/armor.h + src/weapon.h src/item.h src/armor.h src/attack.h + $(CC) $(CFLAGS) $< -o $@ + +src/dice.o: src/dice.cc src/dice.h $(CC) $(CFLAGS) $< -o $@ src/dmtool.o: src/dmtool.cc src/entry.h src/json.hpp src/jsonable.h \ - src/utils.h src/settings.h + src/utils.h src/settings.h src/creature.h src/rules.h src/dice.h \ + src/weapon.h src/item.h $(CC) $(CFLAGS) $< -o $@ src/entry.o: src/entry.cc src/entry.h src/json.hpp src/jsonable.h \ @@ -45,11 +50,16 @@ src/entry.o: src/entry.cc src/entry.h src/json.hpp src/jsonable.h \ $(CC) $(CFLAGS) $< -o $@ src/feature.o: src/feature.cc src/json.hpp src/feature.h src/entry.h \ - src/jsonable.h src/utils.h src/spellcasting.h src/spell.h src/rules.h + src/jsonable.h src/utils.h src/spellcasting.h src/spell.h src/rules.h \ + src/attack.h src/weapon.h src/item.h $(CC) $(CFLAGS) $< -o $@ src/item.o: src/item.cc src/json.hpp src/item.h src/entry.h \ - src/jsonable.h src/utils.h src/weapon.h src/armor.h + src/jsonable.h src/utils.h src/weapon.h src/rules.h src/armor.h + $(CC) $(CFLAGS) $< -o $@ + +src/rules.o: src/rules.cc src/rules.h src/jsonable.h src/json.hpp \ + src/utils.h $(CC) $(CFLAGS) $< -o $@ src/settings.o: src/settings.cc src/settings.h @@ -68,7 +78,8 @@ src/utils.o: src/utils.cc src/utils.h src/json.hpp src/settings.h $(CC) $(CFLAGS) $< -o $@ src/weapon.o: src/weapon.cc src/weapon.h src/item.h src/json.hpp \ - src/entry.h src/jsonable.h src/utils.h src/creature.h src/rules.h + src/entry.h src/jsonable.h src/utils.h src/rules.h src/creature.h \ + src/dice.h $(CC) $(CFLAGS) $< -o $@ clean: @@ -28,6 +28,7 @@ install: \$(EXECUTABLE) parsed cp -r parsed/* \$(DESTDIR)\$(PREFIX)/share/dmtool/ install -d \$(DESTDIR)\$(PREFIX)/bin/ install -m 755 \$(EXECUTABLE) \$(DESTDIR)\$(PREFIX)/bin/ + install -m 644 src/dmtool.bash \$(pkg-config --variable=completionsdir bash-completion)/dmtool " # Below here shouldn't need editing diff --git a/parser/scrapeToJson.py b/parser/scrapeToJson.py index 3310f2d..fd8403d 100755 --- a/parser/scrapeToJson.py +++ b/parser/scrapeToJson.py @@ -75,7 +75,7 @@ def processMonster(data, weapons, armors, spells): bonus = armorDict['ac'] typ = armorDict['armor_type'] #desc['inventory'].append(armorDict) - desc['inventory'].append({'name': a, 'type': 'armor', 'text': '{} armor'.format(a)}) + desc['inventory'].append({'entry': 'item', 'name': a, 'type': 'armor', 'text': '{} armor'.format(a)}) break if not found: print('Cound not identify armor: {}'.format(a)) @@ -141,6 +141,7 @@ def processMonster(data, weapons, armors, spells): desc['saves'].append(ability) for action in desc['features']: if re.match('.*Attack:', action['text']): + action['type'] = 'attack' #toHit = int(re.search('\+(\d+) to hit', action['text']).group(1)) #selectedAbility = None #for ability in ['str', 'dex', 'int', 'wis', 'cha', 'con']: @@ -191,16 +192,20 @@ def processMonster(data, weapons, armors, spells): else: details['damage'].append(toAppend) details['text'] = re.search('(?s)(_Hit:_ (?:\d+ [^\.]*\.)?)(.*)', action['text']).group(2).strip() - if len(details['damage']) == 0: - details['damage'].append({'dmg_die_count': 0, 'dmg_die_sides': 0, 'dmg_type': '-'}) + # We may need to move some parts of the name to the text + if '(' in action['name']: + details['text'] = '(' + '('.join(action['name'].split('(')[1:]) + " " + details['text'] + action['name'] = action['name'].split('(')[0].strip() + if action['name'][-1] == '.' or action['name'][-1] == ':': + action['name'] = action['name'][:-1].strip() action['attack'] = {} - for name, value in utils.formatWeapon(action['name'], details['range'][0], details['range'][1], details['reach'], details['damage'][0]['dmg_type'], details['damage'][0]['dmg_die_count'], details['damage'][0]['dmg_die_sides'], action['text']).items(): + for name, value in utils.formatWeapon(action['name'], details['range'][0], details['range'][1], details['reach'], details['damage'], details['text']).items(): action['attack'][name] = value if action['attack']['weapon_type'] != 'unknown': #desc['inventory'].append(action['attack']) - desc['inventory'].append({'name': action['attack']['name'], 'type': 'weapon', 'text': action['text']}) + desc['inventory'].append({'entry': 'item', 'name': action['attack']['name'], 'type': 'weapons', 'text': action['text']}) elif 'spellcasting' in action['name']: - action['type'] = 'spellcasting' + action['type'] = 'spells' #print('{} has spellcasting!'.format(desc['name'])) abilities = ['Intelligence', 'Wisdom', 'Charisma'] for ability in abilities: @@ -281,14 +286,14 @@ def dumpStuff(stuff, destDir): with open(destDir + '/' + thing['name'].replace(' ', '_').replace('/', '') + '.json', 'w') as f: json.dump(thing, f, indent=2) -dumpStuff(weapons, 'parsed/items/weapons/') -dumpStuff(armors, 'parsed/items/armor/') +dumpStuff(weapons, 'parsed/weapons/') +dumpStuff(armors, 'parsed/armor/') dumpStuff(spells, 'parsed/spells/') for monster in Path(utils.docsLoc + '/gamemaster_rules/monsters/').glob('*.md'): #print('Processing {}'.format(monster)) with monster.open() as f: data = f.read() - Path('parsed/monsters/').mkdir(exist_ok=True) - with open('parsed/monsters/' + monster.stem + '.json', 'w') as f: + Path('parsed/creatures/').mkdir(exist_ok=True) + with open('parsed/creatures/' + monster.stem + '.json', 'w') as f: json.dump(processMonster(data, weapons, armors, spells), f, indent=2) diff --git a/parser/utils.py b/parser/utils.py index 34f8af2..34649ca 100755 --- a/parser/utils.py +++ b/parser/utils.py @@ -119,7 +119,7 @@ def getWeapons(): name = parts[1] + ' ' + parts[0] if weapon[2] == '-': weapon[2] = '0d0 -' - damage = {'dmg_type': weapon[2].split(' ')[1]} + damage = {'dmg_type': weapon[2].split(' ')[1], 'is_or': False} if 'd' in weapon[2].split(' ')[0]: damage['dmg_die_count'] = int(weapon[2].split('d')[0]) damage['dmg_die_sides'] = int(weapon[2].split(' ')[0].split('d')[1]) @@ -143,15 +143,17 @@ def getWeapons(): reach += 5 if name in special: proporties.append(special[name]) - weapons.append({'entry': 'item', 'type': 'weapon', 'name': name, 'cost': cost2copper(weapon[1]), 'damage': damage, 'weight': float(Fraction(weapon[3].split(' ')[0].replace('-', '0'))), 'range': rang, 'reach': reach, 'properties': proporties, 'weapon_type': header, 'text': 'Provided from PHB'}) + weapons.append(formatWeapon(name, rang[0], rang[1], reach, [damage], 'Provided from PHB', {'cost': cost2copper(weapon[1]), 'weight': float(Fraction(weapon[3].split(' ')[0].replace('-', '0'))), 'properties': proporties, 'weapon_type': header})) return weapons -def formatWeapon(name, rangeShort, rangeLong, reach, dmgType, dmgCount, dmgSides, text): - baseWeapon = {'cost': 0, 'weight': 0.0, 'properties': [], 'weapon_type': 'unknown'} +# damage is formatted {'dmg_die_count': ddc, 'dmg_die_sides': dds, 'dmg_type': dt, 'is_or': bool} +def formatWeapon(name, rangeShort, rangeLong, reach, damage, text, baseWeapon = {'cost': -1, 'weight': -1.0, 'properties': [], 'weapon_type': 'unknown'}): for weapon in weapons: if weapon['name'] == name: baseWeapon = weapon - return {'name': name, 'type': 'weapon', 'cost': baseWeapon['cost'], 'damage': {'dmg_type': dmgType, 'dmg_die_count': dmgCount, 'dmg_die_sides': dmgSides}, 'weight': baseWeapon['weight'], 'range': [rangeShort, rangeLong], 'reach': reach, 'properties': baseWeapon['properties'], 'weapon_type': baseWeapon['weapon_type'], 'text': text} + for dmg in damage: + assert 'dmg_die_count' in dmg and 'dmg_die_sides' in dmg and 'dmg_type' in dmg and 'is_or' in dmg + return {'entry': 'item', 'type': 'weapons', 'name': name, 'cost': baseWeapon['cost'], 'damage': damage, 'weight': baseWeapon['weight'], 'range': [rangeShort, rangeLong], 'reach': reach, 'properties': baseWeapon['properties'], 'weapon_type': baseWeapon['weapon_type'], 'text': text} spells = [] def getSpells(): 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); + } +} @@ -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; |