diff options
author | Your Name <you@example.com> | 2021-05-06 14:13:28 -0400 |
---|---|---|
committer | Your Name <you@example.com> | 2021-05-06 14:13:28 -0400 |
commit | 9f3802690f9dd9452e96d1d7a879291978d66e35 (patch) | |
tree | 6d6c17b39abdb9490119241bc4fc061744b46d7d /src | |
parent | 2a9f262e6db5906db445d465e500d7ba8c90fab3 (diff) | |
download | dmtool-9f3802690f9dd9452e96d1d7a879291978d66e35.tar.gz dmtool-9f3802690f9dd9452e96d1d7a879291978d66e35.tar.bz2 dmtool-9f3802690f9dd9452e96d1d7a879291978d66e35.zip |
Refactoring
Diffstat (limited to 'src')
-rw-r--r-- | src/cmd.cc | 33 | ||||
-rw-r--r-- | src/cmd.h | 28 | ||||
-rw-r--r-- | src/cmd_fsops.cc | 85 | ||||
-rw-r--r-- | src/cmd_manipulate.cc | 186 | ||||
-rw-r--r-- | src/cmd_query.cc | 49 | ||||
-rw-r--r-- | src/cmd_usage.cc | 47 | ||||
-rw-r--r-- | src/creature.cc | 6 | ||||
-rw-r--r-- | src/creature.h | 2 | ||||
-rw-r--r-- | src/dmtool.bash | 97 | ||||
-rw-r--r-- | src/dmtool.cc | 448 | ||||
-rw-r--r-- | src/jsonable.h | 33 | ||||
-rw-r--r-- | src/rules.h | 7 | ||||
-rw-r--r-- | src/spellcasting.h | 7 | ||||
-rw-r--r-- | src/utils.cc | 16 | ||||
-rw-r--r-- | src/utils.h | 58 | ||||
-rw-r--r-- | src/weapon.h | 2 |
16 files changed, 624 insertions, 480 deletions
diff --git a/src/cmd.cc b/src/cmd.cc new file mode 100644 index 0000000..7be5e01 --- /dev/null +++ b/src/cmd.cc @@ -0,0 +1,33 @@ +#include "cmd.h" +#include "settings.h" +#include <vector> +#include <string> +#include <filesystem> +#include <algorithm> + +namespace cmd { + std::vector<std::string> getVirtDirs() { + return {"weapons", "armor", "spells", "creatures"}; + } + + // Not idempotent: only do once! + std::filesystem::path getTruePath(std::filesystem::path virtPath) { + std::filesystem::path p; + auto virtPaths = getVirtDirs(); + if(std::find(virtPaths.begin(), virtPaths.end(), *virtPath.begin()) != virtPaths.end()) { + p = settings::getString(*virtPath.begin()); + // Erase root (part to be replaced by virtPaths) + std::filesystem::path tmp; + auto it = virtPath.begin(); + while(++it != virtPath.end()) { + tmp /= *it; + } + virtPath = tmp; + } else { + p = settings::getString("savedir"); + } + p /= virtPath; + if(std::filesystem::directory_entry(p.string() + ".json").is_regular_file()) return p.string() + ".json"; + return p; + } +} diff --git a/src/cmd.h b/src/cmd.h new file mode 100644 index 0000000..6a8b738 --- /dev/null +++ b/src/cmd.h @@ -0,0 +1,28 @@ +#pragma once +#include <vector> +#include <string> +#include <filesystem> + +namespace cmd { + // Corresponds to commands + std::string usage(const std::string& exename); + std::string list(std::vector<std::string> args); + std::string mkdir(std::vector<std::string> args); + std::string cp(std::vector<std::string> args); + std::string mv(std::vector<std::string> args); + std::string rm(std::vector<std::string> args); + std::string attacks(std::vector<std::string> args); + std::string roll(std::vector<std::string> args); + std::string heal(std::vector<std::string> args); + std::string damage(std::vector<std::string> args, std::vector<std::string> flags); + std::string reset(std::vector<std::string> args); + std::string set(std::vector<std::string> args); + std::string add(std::vector<std::string> args); + std::string del(std::vector<std::string> args); + + // Command-centric helpers + + // Not idempotent: only do once! + std::filesystem::path getTruePath(std::filesystem::path virtPath); + std::vector<std::string> getVirtDirs(void); +} diff --git a/src/cmd_fsops.cc b/src/cmd_fsops.cc new file mode 100644 index 0000000..eca505e --- /dev/null +++ b/src/cmd_fsops.cc @@ -0,0 +1,85 @@ +#include "cmd.h" +#include "utils.h" +#include "entry.h" +#include <filesystem> +#include <sstream> + +namespace fs = std::filesystem; + +namespace cmd { + std::string list(const fs::path& p) { + std::stringstream text; + if(p.empty()) { + // Print read-only dirs + for(std::string name : getVirtDirs()) { + text << name << "/ (read only)" << std::endl; + } + } + fs::path truePath = getTruePath(p); + if(fs::directory_entry(truePath).is_regular_file()) { + text << utils::instantiate<entry::Entry>(truePath)->getText() << std::endl; + } + else if(fs::directory_entry(truePath).is_directory()) { + for(fs::directory_entry de : fs::directory_iterator(truePath)) { + if(de.is_directory()) { + text << de.path().filename().string() << "/" << std::endl; + } else { + text << de.path().stem().string() << std::endl; + } + } + } + else { + text << "Unknown path " << p << std::endl; + } + return text.str(); + } + + std::string list(std::vector<std::string> args) { + std::stringstream text; + if(args.empty()) { + text << list(""); + } else { + for(std::string dir : args) { + text << list(dir); + } + } + return text.str(); + } + + std::string mkdir(std::vector<std::string> args) { + for(std::string s : args) { + fs::create_directories(getTruePath(s)); + } + return ""; + } + + void cp(fs::path src, fs::path dest) { + if(fs::directory_entry(src).is_regular_file()) { + utils::saveJson(*utils::instantiate<entry::Entry>(src), dest); + } else { + mkdir({dest}); + for(fs::directory_entry de : fs::directory_iterator(src)) { + cp(de.path(), dest / de.path().filename()); + } + } + } + + std::string cp(std::vector<std::string> args) { + // Operate by intantiating and saving + // We do recursive! + cp(getTruePath(args[0]), getTruePath(args[1])); + return ""; + } + + std::string mv(std::vector<std::string> args) { + fs::rename(getTruePath(args[0]), getTruePath(args[1])); + return ""; + } + + std::string rm(std::vector<std::string> args) { + for(std::string s : args) { + fs::remove_all(getTruePath(s)); + } + return ""; + } +} diff --git a/src/cmd_manipulate.cc b/src/cmd_manipulate.cc new file mode 100644 index 0000000..fe59ebf --- /dev/null +++ b/src/cmd_manipulate.cc @@ -0,0 +1,186 @@ +#include "cmd.h" +#include "utils.h" +#include "creature.h" +#include "item.h" +#include "spellcasting.h" +#include <sstream> +#include <memory> + +namespace fs = std::filesystem; + +namespace cmd { + std::string healOrDamage(bool heal, std::vector<std::string> args, std::vector<std::string> flags) { + std::stringstream text; + std::vector<rules::Qualifier> qualifiers; + if(! heal) { + for(auto flag : flags) { + if(flag == "m" || flag == "magical") { + qualifiers.push_back(rules::Qualifier::Magical()); + } else if(flag == "s" || flag == "silvered") { + qualifiers.push_back(rules::Qualifier::Silvered()); + } else if(flag == "a" || flag == "adamantine") { + qualifiers.push_back(rules::Qualifier::Adamantine()); + } + } + } + fs::path p = getTruePath(args[0]); + auto c = utils::instantiate<creature::Creature>(p); + int amnt = utils::parseInt(args[1]); + int initHP = c->getHP(); + std::string dmgType = "force"; + if(heal) { + c->applyHealing(amnt); + } else { + if(args.size() == 3) { + dmgType = args[2]; + } + c->applyDamage(amnt, dmgType, qualifiers); + } + text << (heal? "Healing " : "Damaging ") << c->getGivenName() << " the " << c->getCreatureName() << " by " << amnt; + if(! heal) { + std::string qualsString; + std::vector<std::string> positiveQuals; + for(auto qual : qualifiers) { + positiveQuals.push_back(qual.getPositive()); + } + if(! positiveQuals.empty()) { + qualsString = " " + utils::join(positiveQuals, ", "); + } + text << qualsString << " " << dmgType << " damage"; + } + text << ". HP: " << initHP << " -> " << c->getHP() << "." << std::endl; + utils::saveJson(*c, p); + return text.str(); + } + + std::string heal(std::vector<std::string> args) { + return healOrDamage(true, args, {}); + } + + std::string damage(std::vector<std::string> args, std::vector<std::string> flags) { + return healOrDamage(false, args, flags); + } + + std::string reset(std::vector<std::string> args) { + for(std::string s : args) { + fs::path p = getTruePath(s); + auto c = utils::instantiate<creature::Creature>(p); + c->longRest(); + utils::saveJson(*c, p); + } + return ""; + } + + std::string 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 = utils::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(utils::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 = rules::tryGetAbilityOrSkill<rules::Skill>(abilityOrSkill); + rules::Ability ability = rules::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, utils::parseInt(toSet)); + } else { + throw std::runtime_error("Subcommand 'set' expected an ability, skill, proficiency, or name field to set, but was given " + abilityOrSkill); + } + } + utils::saveJson(*c, p); + return ""; + } + + std::string add(std::vector<std::string> args) { + std::stringstream text; + fs::path p = getTruePath(args[0]); + args.erase(args.begin()); // remove path from args + auto c = utils::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 = utils::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()); + } + } + utils::saveJson(*c, p); + text << "Added the " << ent->getType() << " " << ent->getName() << " to " << c->getGivenName() << " the " << c->getName() << std::endl; + return text.str(); + } + + std::string del(std::vector<std::string> args) { + std::stringstream text; + fs::path p = getTruePath(args[0]); + args.erase(args.begin()); // remove path from args + auto c = utils::instantiate<creature::Creature>(p); + //Atempt to load the item if it's a path + std::string itemName = utils::join(args, " "); + try { + auto i = utils::instantiate<entry::Entry>(getTruePath(itemName)); + if(i) { + itemName = i->getName(); + } + } catch(std::exception& e) {} // eat. + 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; + } + } + } + utils::saveJson(*c, p); + if(removed) { + text << "Successfully removed the " << removed->Entry::getType() << " " << removed->getName() << std::endl; + } else { + text << "Could not find any inventory item nor spell by that name" << std::endl; + } + return text.str(); + } +} diff --git a/src/cmd_query.cc b/src/cmd_query.cc new file mode 100644 index 0000000..7d4e6d6 --- /dev/null +++ b/src/cmd_query.cc @@ -0,0 +1,49 @@ +#include "cmd.h" +#include "utils.h" +#include "creature.h" +#include "dice.h" +#include "weapon.h" +#include <sstream> + +namespace cmd { + std::string attacks(std::vector<std::string> args) { + std::stringstream text; + auto c = utils::instantiate<creature::Creature>(getTruePath(args[0])); + for(auto w : creature::getAttacks(*c)) { + text << w->getName() << std::endl; + } + return text.str(); + } + + std::string roll(std::vector<std::string> args) { + std::stringstream text; + auto c = utils::instantiate<creature::Creature>(getTruePath(args[0])); + args.erase(args.begin()); + std::string rollName = utils::join(args, " "); + utils::lower(rollName); + int rolled = dice::roll(20); + auto printResults = [&text](std::string name, std::string type, int rolled, int bonus) { + text << name << " " << type << ": " << rolled << " (d20) + " << bonus << " (" << name << " " << type << " bonus) = " << rolled + bonus << std::endl; + }; + // Search through skills, saves, and attacks to roll + rules::Skill skill = rules::tryGetAbilityOrSkill<rules::Skill>(rollName); + rules::Ability ability = rules::tryGetAbilityOrSkill<rules::Ability>(rollName); + if(skill) { + printResults(skill.getName(), "check", rolled, c->getSkillBonus(skill)); + } else if(ability) { + printResults(ability.getFull(), "save", rolled, c->getAbilitySaveBonus(ability)); + } else { + for(auto w : creature::getAttacks(*c)) { + if(w->getName() == rollName) { + text << 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); + text << " on hit: " << entry::formatDmg(*w, *c) << std::endl; + break; + } + } + } + return text.str(); + } +} diff --git a/src/cmd_usage.cc b/src/cmd_usage.cc new file mode 100644 index 0000000..03dae56 --- /dev/null +++ b/src/cmd_usage.cc @@ -0,0 +1,47 @@ +#include "cmd.h" +#include <sstream> +#include <string> + +namespace cmd { + std::string usage(const std::string& exename) { + std::stringstream text; + text << "Usage:" << std::endl; + std::string indOpt = " " + exename + " "; + std::string indDesc = " "; + text << indOpt << "[ls] [subfolder]" << std::endl; + text << indDesc << "List creatures and objects." << std::endl; + text << indOpt << "cp old-path new-path" << std::endl; + text << indDesc << "Copy old-path to new-path, instantiating it." << std::endl; + text << indOpt << "mkdir path" << std::endl; + text << indDesc << "Make a new directory for holding creatures and objects." << std::endl; + text << indOpt << "mv old-path new-path" << std::endl; + text << indDesc << "Move old-path to new-path." << std::endl; + text << indOpt << "rm path" << std::endl; + text << indDesc << "Remove existing creature, object, or directory." << std::endl; + text << indOpt << "attacks path" << std::endl; + text << indDesc << "List attacks available for a creature" << std::endl; + text << indOpt << "roll path name" << std::endl; + text << indDesc << "Roll a skill check, save, or attack." << std::endl; + text << indOpt << "damage path amount [type] [--magical,-m] [--silvered,-s] [--adamantine,-a]" << std::endl; + text << indDesc << "Damage creature by amount. Type defaults to \"force\"." << std::endl; + text << indOpt << "heal path amount" << std::endl; + text << indDesc << "Heal creature by amount." << std::endl; + text << indOpt << "reset path" << std::endl; + text << indDesc << "Reset creature to full health (as if completing a long rest)." << std::endl; + text << indOpt << "set path field value" << std::endl; + text << indDesc << "Set a field to a new value, where field is any of:" << std::endl; + text << indDesc << " ability (str, dex, con, int, wis, cha); value is new ability score" << std::endl; + text << indDesc << " skill (athletics, \"sleight of hand\", etc.); value is (none|proficient|expert)" << std::endl; + text << indDesc << " proficiency; value is new proficency bonus." << std::endl; + text << indDesc << " name; value is new given name." << std::endl; + text << indOpt << "edit path" << std::endl; + text << indDesc << "Edit notes associated with creature." << std::endl; + text << indOpt << "add path entry" << std::endl; + text << indDesc << "Add entry to creature, where entry is an item or spell." << std::endl; + text << indOpt << "del path entry" << std::endl; + text << indDesc << "Delete entry from creature, where entry is an item or spell." << std::endl; + text << indOpt << "help" << std::endl; + text << indDesc << "Show this help." << std::endl; + return text.str(); + } +} diff --git a/src/creature.cc b/src/creature.cc index 21943fe..dcd2324 100644 --- a/src/creature.cc +++ b/src/creature.cc @@ -25,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"])), 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"])) + : Entry(base), inventory(utils::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(utils::json2vec<rules::Ability>(data["saves"])), langs(data["langs"]), cr(data["cr"]), natArmorName(data["natural_armor"]["name"]), natArmorBonus(data["natural_armor"]["bonus"]), dmgImmunities(utils::json2vec<dmgType>(data["d_immunities"])), dmgResistances(utils::json2vec<dmgType>(data["d_resistances"])), dmgVulnerabilities(utils::json2vec<dmgType>(data["d_vulnerabilities"])), condImmunities(utils::json2vec<dmgType>(data["c_immunities"])), features(utils::json2ptrvec<entry::Feature>(data["features"])) { // Initialize names and hp if(((map<string, json>) data).contains("givenName")) { @@ -64,8 +64,8 @@ namespace creature { data["givenName"] = givenName; data["hpMax"] = hpMax; data["hp"] = hp; - data["inventory"] = ptrvec2json(inventory); - data["features"] = ptrvec2json(features); + data["inventory"] = utils::ptrvec2json(inventory); + data["features"] = utils::ptrvec2json(features); return data; } diff --git a/src/creature.h b/src/creature.h index cdbd2b9..0970f04 100644 --- a/src/creature.h +++ b/src/creature.h @@ -21,7 +21,7 @@ namespace creature { class dmgType : public Jsonable { public: - dmgType(const json& data) : type(data["type"]), qualifiers(json2vec<rules::Qualifier>(data["qualifiers"])) {} + dmgType(const json& data) : type(data["type"]), qualifiers(utils::json2vec<rules::Qualifier>(data["qualifiers"])) {} std::string type; std::vector<rules::Qualifier> qualifiers; std::string getText() const { diff --git a/src/dmtool.bash b/src/dmtool.bash index 41f70e9..ffc75f7 100644 --- a/src/dmtool.bash +++ b/src/dmtool.bash @@ -60,35 +60,80 @@ _dmtool_complete_entries () { fi } +_dmtool_skills() +{ + echo -e "athletics\nacrobatics\nstealth\narcana\nhistory\ninvestigation\nnature\nreligion\ninsight\nmedicine\nperception\nsurvival\ndeception\nintimidation\nperformance\npersuasion\nsleight of hand\nanimal handling" +} + +_dmtool_abilities() +{ + echo -e "str\ndex\ncon\nint\nwis\ncha" +} + +_dmtool_complete_skills_abilities() +{ + local IFS=$'\n' + COMPREPLY+=($(compgen -W "$(_dmtool_skills)" -- ${cur})) + COMPREPLY+=($(compgen -W "$(_dmtool_abilities)" -- ${cur})) +} + _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 + COMPREPLY=() + local cur="${COMP_WORDS[COMP_CWORD]}" + local commands="ls cp mkdir mv rm attacks roll damage heal reset set edit add del 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|add) + if [[ $COMP_CWORD -le 3 ]]; then + _dmtool_complete_entries + fi + ;; + attacks|roll|damage|heal|set|edit|del) + if [[ $COMP_CWORD -le 2 ]]; then + _dmtool_complete_entries + else + case "${COMP_WORDS[1]}" in + roll) + _dmtool_complete_skills_abilities + # Add in attacks + local IFS=$'\n' + opts="$(${COMP_WORDS[0]} attacks ${COMP_WORDS[2]})" + COMPREPLY+=($(compgen -W "$opts" -- ${cur})) + ;; + damage) + COMPREPLY+=($(compgen -W "--magical -m --silvered -s --adamantine -a" -- ${cur})) + if [[ $COMP_CWORD -eq 4 ]]; then + COMPREPLY+=($(compgen -W "slashing piercing bludgeoning poison acid fire cold radiant necrotic lightning thunder force psychic" -- ${cur})) + fi + ;; + set) + if [[ $COMP_CWORD -eq 3 ]]; then + _dmtool_complete_skills_abilities + COMPREPLY+=($(compgen -W "proficiency name" -- ${cur})) + elif [[ $COMP_CWORD -eq 4 ]]; then + local skills="$(_dmtool_skills)" + if [[ "$skills" =~ "$lastarg" ]]; then + COMPREPLY+=($(compgen -W "none proficient expert" -- ${cur})) 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 + fi + ;; + del) + #TODO: Add items and spells + _dmtool_complete_skills_abilities + ;; + esac + 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 ba47616..e9d8ed6 100644 --- a/src/dmtool.cc +++ b/src/dmtool.cc @@ -1,396 +1,15 @@ -#include "entry.h" -#include "settings.h" -#include "creature.h" -#include "dice.h" -#include "weapon.h" -#include "spell.h" -#include "spellcasting.h" +#include "cmd.h" +#include "utils.h" #include <iostream> -#include <vector> -#include <string> -#include <filesystem> -#include <system_error> #include <exception> -#include <memory> #include <algorithm> - -namespace fs = std::filesystem; - -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] [--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; - 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 << " 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 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; -} - -template<typename T> std::shared_ptr<T> instantiate(const fs::path& path) { - std::shared_ptr<entry::Entry> ent; - try { - 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()); - } else { - 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) { - utils::saveJson(*e, path); -} - -void print(const fs::path& path) { - std::cout << instantiate<entry::Entry>(path)->getText() << std::endl; -} - -fs::path getBaseDir() { - return settings::getString("savedir"); -} - -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; -} - -// Ensure the system is set up correctly -void initFS() { - fs::directory_entry de = fs::directory_entry(getBaseDir()); - if(! de.exists()) { - fs::create_directories(de); - } -} - -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 : fs::directory_iterator(truePath)) { - if(de.is_directory()) { - std::cout << de.path().filename().string() << "/" << std::endl; - } else { - std::cout << de.path().stem().string() << std::endl; - } - } - } - else { - std::cerr << "Unknown path " << p << std::endl; - } -} - -void list(std::vector<std::string> args) { - if(args.empty()) { - list(""); - } else { - for(std::string dir : args) { - list(dir); - } - } -} - -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<entry::Entry>(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) { - // Operate by intantiating and saving - // We do recursive! - cp(getTruePath(args[0]), getTruePath(args[1])); -} - -void mv(std::vector<std::string> args) { - 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)); - } -} - -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) { - auto c = instantiate<creature::Creature>(getTruePath(args[0])); - args.erase(args.begin()); - std::string rollName = utils::join(args, " "); - 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 - rules::Skill skill = tryGetAbilityOrSkill<rules::Skill>(rollName); - rules::Ability ability = tryGetAbilityOrSkill<rules::Ability>(rollName); - if(skill) { - printResults(skill.getName(), "check", rolled, c->getSkillBonus(skill)); - } else if(ability) { - printResults(ability.getFull(), "save", rolled, c->getAbilitySaveBonus(ability)); - } 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; - } - } - } -} - -void healOrDamage(bool heal, std::vector<std::string> 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 { - if(args.size() == 3) { - dmgType = args[2]; - } - c->applyDamage(amnt, dmgType, qualifiers); - } - 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) { - 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; - } -} +#include <map> const std::map<std::string, std::vector<int>> nargs({ {"cp", {2}}, {"mv", {2}}, - {"damage", {2, 3, 4, 5, 6}}, + {"attacks", {1}}, + {"damage", {2, 3}}, {"heal", {2}}, }); @@ -403,17 +22,31 @@ void checkArgs(std::string cmd, std::vector<std::string> args) { } } +// Removes flags from args (in-place) and returns vector of flags +std::vector<std::string> extractFlags(std::vector<std::string>& args) { + std::vector<std::string> ret; + auto it = args.begin(); + while(it != args.end()) { + if((*it)[0] == '-') { + while((*it)[0] == '-') { + (*it).erase((*it).begin()); + } + ret.push_back(*it); + args.erase(it); + } else { + it++; + } + } + return ret; +} + int main(int argc, char *argv[]) { std::string exename = argv[0]; std::vector<std::string> args(&argv[1], &argv[argc]); - try { - initFS(); - } catch(fs::filesystem_error& e) { - std::cerr << e.what() << std::endl; - return 1; - } + std::vector<std::string> flags = extractFlags(args); + cmd::mkdir({cmd::getTruePath("")}); if(args.empty()) { - list(args); + std::cout << cmd::list(args); return 0; } std::string cmd = args[0]; @@ -421,20 +54,21 @@ int main(int argc, char *argv[]) { args.erase(args.begin()); try { checkArgs(cmd, args); - 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") healOrDamage(false, args); - else if(cmd == "heal") healOrDamage(true, args); - 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); + if(cmd == "ls") std::cout << cmd::list(args); + else if(cmd == "cp") std::cout << cmd::cp(args); + else if(cmd == "mkdir") std::cout << cmd::mkdir(args); + else if(cmd == "mv") std::cout << cmd::mv(args); + else if(cmd == "rm") std::cout << cmd::rm(args); + else if(cmd == "attacks") std::cout << cmd::attacks(args); + else if(cmd == "roll") std::cout << cmd::roll(args); + else if(cmd == "damage") std::cout << cmd::damage(args, flags); + else if(cmd == "heal") std::cout << cmd::heal(args); + else if(cmd == "reset") std::cout << cmd::reset(args); + else if(cmd == "set") std::cout << cmd::set(args); + else if(cmd == "add") std::cout << cmd::add(args); + else if(cmd == "del") std::cout << cmd::del(args); + else if(cmd == "help") std::cout << cmd::usage(exename); + else std::cout << cmd::list(argsOrig); } catch(std::exception& e) { std::cerr << e.what() << std::endl; return 1; diff --git a/src/jsonable.h b/src/jsonable.h index 0385fb1..7411efa 100644 --- a/src/jsonable.h +++ b/src/jsonable.h @@ -1,8 +1,5 @@ #pragma once #include "json.hpp" -#include "utils.h" -#include <memory> -#include <vector> class Jsonable { public: @@ -10,33 +7,3 @@ class Jsonable { operator nlohmann::json() const {return toJson();} virtual ~Jsonable() {} }; - -template<typename T> std::vector<T> json2vec(const nlohmann::json& data) { - using std::begin; using std::end; - return std::vector<T>(begin(data), end(data)); -} - -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(std::shared_ptr<T>(new T(j, j))); - } - return ret; -} - -template<typename T> std::vector<std::shared_ptr<T>> json2ptrvec(const nlohmann::json& data) { - std::vector<std::shared_ptr<T>> ret; - for(nlohmann::json d : data) { - ret.push_back(T::create(d)); - } - 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.h b/src/rules.h index f41f7ad..c1b130d 100644 --- a/src/rules.h +++ b/src/rules.h @@ -143,4 +143,11 @@ namespace rules { 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); + + template<typename T> T tryGetAbilityOrSkill(std::string src) { + try { + return T::fromString(src); + } catch(std::exception& e) {} // eat. + return T(); + } } diff --git a/src/spellcasting.h b/src/spellcasting.h index 331a95f..6672dba 100644 --- a/src/spellcasting.h +++ b/src/spellcasting.h @@ -4,13 +4,14 @@ #include "spell.h" #include "jsonable.h" #include "rules.h" +#include "utils.h" #include <memory> typedef nlohmann::json json; namespace entry { struct SlotLevel : public Jsonable { - SlotLevel(const json& data) : numSlots(data["slots"]), spells(jsonList2ptrvec<Spell>("spells", data["spells"])) {} + SlotLevel(const json& data) : numSlots(data["slots"]), spells(utils::jsonList2ptrvec<Spell>("spells", data["spells"])) {} SlotLevel() : numSlots(0) {} virtual ~SlotLevel() {} int numSlots; @@ -31,7 +32,7 @@ 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(json2ptrvec<SlotLevel>(data["levels"])) {} + Spellcasting(const json& data, const json& base) : Feature(base), innate(data["innate"]), ability(rules::Ability(data["spellcasting_ability"])), spellsBySlot(utils::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() {} @@ -47,7 +48,7 @@ namespace entry { auto data = Feature::toJson(); data["innate"] = innate; data["spellcasting_ability"] = ability; - data["levels"] = ptrvec2json(spellsBySlot); + data["levels"] = utils::ptrvec2json(spellsBySlot); return data; } diff --git a/src/utils.cc b/src/utils.cc index 54fa38e..6f25c46 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -10,7 +10,9 @@ #include <map> #include <algorithm> -nlohmann::json utils::loadJson(const std::string& path) { +namespace fs = std::filesystem; + +nlohmann::json utils::loadJson(const fs::path& path) { std::ifstream f(path); nlohmann::json j; f >> j; @@ -35,7 +37,15 @@ nlohmann::json utils::findByName(const std::string& name) { throw std::invalid_argument("Could not find data matching: " + name); } -void utils::saveJson(const nlohmann::json& data, const std::string& path) { +int utils::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 utils::saveJson(const nlohmann::json& data, const fs::path& path) { std::ofstream f(path); f << std::setw(4) << data << std::endl; } @@ -47,7 +57,7 @@ std::vector<nlohmann::json> utils::loadAllJson(const std::string& directory) { return cache[directory]; } std::vector<nlohmann::json> ret; - for(auto path : std::filesystem::recursive_directory_iterator(directory)) { + for(auto path : fs::recursive_directory_iterator(directory)) { if(path.path().extension() == ".json") { ret.push_back(utils::loadJson(path.path())); } diff --git a/src/utils.h b/src/utils.h index bf789e1..dea052b 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,15 +1,17 @@ #pragma once #include "json.hpp" +#include "entry.h" #include <string> #include <vector> #include <map> #include <sstream> #include <memory> #include <stdexcept> +#include <filesystem> namespace utils { - nlohmann::json loadJson(const std::string& path); - + nlohmann::json loadJson(const std::filesystem::path& path); + // Recursively loads all .json files under directory // If called multiple times with same directory, returns a cached result. std::vector<nlohmann::json> loadAllJson(const std::string& directory); @@ -20,11 +22,31 @@ namespace utils { // 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); + void saveJson(const nlohmann::json& data, const std::filesystem::path& path); // converts in-place std::string lower(std::string& in); + template<typename T> std::shared_ptr<T> instantiate(const std::filesystem::path& path) { + std::shared_ptr<entry::Entry> ent; + try { + ent = entry::Entry::create(loadJson(path)); + } catch(std::exception& e) { + if(std::filesystem::directory_entry(path).exists()) { + throw std::runtime_error("Invalid json: " + path.string()); + } else { + 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); + 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)); @@ -78,4 +100,34 @@ namespace utils { std::string getCostString(int coppers); std::string toOrdinal(std::size_t number); + + template<typename T> std::vector<T> json2vec(const nlohmann::json& data) { + using std::begin; using std::end; + return std::vector<T>(begin(data), end(data)); + } + + 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(std::shared_ptr<T>(new T(j, j))); + } + return ret; + } + + template<typename T> std::vector<std::shared_ptr<T>> json2ptrvec(const nlohmann::json& data) { + std::vector<std::shared_ptr<T>> ret; + for(nlohmann::json d : data) { + ret.push_back(T::create(d)); + } + 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/weapon.h b/src/weapon.h index 9384f78..c1047bb 100644 --- a/src/weapon.h +++ b/src/weapon.h @@ -38,7 +38,7 @@ namespace entry { class Weapon : public Item, public Substantial { public: - 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"]) {} + Weapon(const nlohmann::json& data, const nlohmann::json& base) : Item(base), damage(utils::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::vector<Damage> getDamage(void) const {return damage;} std::set<std::string> getProperties(void) const {return properties;} |