diff options
author | Your Name <you@example.com> | 2022-01-03 16:25:45 -0500 |
---|---|---|
committer | Your Name <you@example.com> | 2022-01-03 16:25:45 -0500 |
commit | 3f78a7e1647ba94129236bd2bf4fc855c109628a (patch) | |
tree | 0a1538d44715738c8cbbae00bf1995777227b963 | |
parent | d222c5a39943ed9f83f11d63a42bdff4978179af (diff) | |
download | dmtool-3f78a7e1647ba94129236bd2bf4fc855c109628a.tar.gz dmtool-3f78a7e1647ba94129236bd2bf4fc855c109628a.tar.bz2 dmtool-3f78a7e1647ba94129236bd2bf4fc855c109628a.zip |
Added command to force a saving throw
-rw-r--r-- | files/dmtool.bash | 28 | ||||
m--------- | parser/5thSRD | 0 | ||||
-rw-r--r-- | src/cmd.cc | 15 | ||||
-rw-r--r-- | src/cmd.h | 8 | ||||
-rw-r--r-- | src/cmd_manipulate.cc | 88 | ||||
-rw-r--r-- | src/cmd_usage.cc | 4 | ||||
-rw-r--r-- | src/creature.cc | 11 | ||||
-rw-r--r-- | src/creature.h | 2 | ||||
-rw-r--r-- | src/dmtool.cc | 25 |
9 files changed, 143 insertions, 38 deletions
diff --git a/files/dmtool.bash b/files/dmtool.bash index 43aba72..0d7116f 100644 --- a/files/dmtool.bash +++ b/files/dmtool.bash @@ -70,6 +70,11 @@ _dmtool_abilities() echo -e "str\ndex\ncon\nint\nwis\ncha" } +_dmtool_dmgtypes() +{ + echo -e "slashing\npiercing\nbludgeoning\npoison\nacid\nfire\ncold\nradiant\nnecrotic\nlightning\nthunder\nforce\npsychic" +} + _dmtool_complete_skills_abilities() { local IFS=$'\n' @@ -81,7 +86,7 @@ _dmtool() { COMPREPLY=() local cur="${COMP_WORDS[COMP_CWORD]}" - local commands="ls cp mkdir mv rm attacks roll attack damage heal reset set edit add del spellcasting help git" + local commands="ls cp mkdir mv rm attacks roll attack damage heal save reset set edit add del spellcasting help git" if [[ $COMP_CWORD -gt 1 ]]; then local lastarg="${COMP_WORDS[$COMP_CWORD-1]}" case "${COMP_WORDS[1]}" in @@ -93,7 +98,23 @@ _dmtool() _dmtool_complete_entries fi ;; - attacks|roll|attack|damage|heal|set|edit|del|spellcasting) + save) + if [[ $COMP_CWORD -eq 2 ]]; then + local IFS=$'\n' + COMPREPLY+=($(compgen -W "$(_dmtool_abilities)" -- ${cur})) + elif [[ "$lastarg" == "--type" ]]; then + local IFS=$'\n' + COMPREPLY+=($(compgen -W "$(_dmtool_dmgtypes)" -- ${cur})) + elif [[ "$lastarg" == "--damage" ]]; then + : + elif [[ $COMP_CWORD -eq 4 ]]; then + _dmtool_complete_entries + elif [[ $COMP_CWORD -ge 5 ]]; then + COMPREPLY+=($(compgen -W "--magical -m --silvered -s --adamantine -a --damage --type --halves" -- ${cur})) + _dmtool_complete_entries + fi + ;; + attacks|roll|attack|damage|heal|save|set|edit|del|spellcasting) if [[ $COMP_CWORD -le 2 ]]; then _dmtool_complete_entries else @@ -118,7 +139,8 @@ _dmtool() 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})) + local IFS=$'\n' + COMPREPLY+=($(compgen -W "$(_dmtool_dmgtypes)" -- ${cur})) fi ;; set) diff --git a/parser/5thSRD b/parser/5thSRD -Subproject 5b635b54e81f82396cb96d6bca3c97f296f1c64 +Subproject 5356ad7fc35f76891c64cd36159cc42a04c9c71 @@ -36,4 +36,19 @@ namespace cmd { text << name << " " << type << ": " << rolled << " (d20) + " << bonus << " (" << name << " " << type << " bonus) = " << rolled + bonus << std::endl; return text.str(); } + + std::vector<rules::Qualifier> parseQualifiers(std::map<std::string, std::string> flags) { + std::vector<rules::Qualifier> qualifiers; + for(auto flagPair : flags) { + auto flag = flagPair.first; + 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()); + } + } + return qualifiers; + } } @@ -1,7 +1,9 @@ #pragma once #include <vector> +#include <map> #include <string> #include <filesystem> +#include "rules.h" namespace cmd { // Corresponds to commands @@ -18,8 +20,9 @@ namespace cmd { // Manipulators std::string heal(std::vector<std::string> args); - std::string damage(std::vector<std::string> args, std::vector<std::string> flags); - std::string attack(std::vector<std::string> args, std::vector<std::string> flags); + std::string damage(std::vector<std::string> args, std::map<std::string, std::string> flags); + std::string attack(std::vector<std::string> args, std::map<std::string, std::string> flags); + std::string save(std::vector<std::string> args, std::map<std::string, 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); @@ -40,4 +43,5 @@ namespace cmd { // Helper functions std::string formatRoll(std::string name, std::string type, int rolled, int bonus); + std::vector<rules::Qualifier> parseQualifiers(std::map<std::string, std::string> flags); } diff --git a/src/cmd_manipulate.cc b/src/cmd_manipulate.cc index 294b600..aa6407d 100644 --- a/src/cmd_manipulate.cc +++ b/src/cmd_manipulate.cc @@ -17,16 +17,10 @@ namespace fs = std::filesystem; namespace cmd { - std::string healOrDamageProgrammatic(fs::path p, bool heal, int amnt, std::string dmgType, std::vector<rules::Qualifier> qualifiers) { + // Call after applying to format printing + std::string formatHealingDamage(const std::shared_ptr<creature::Creature>& c, int initHP, bool heal, int amnt, const std::string& dmgType, const std::vector<rules::Qualifier>& qualifiers) { std::stringstream text; - auto c = utils::instantiate<creature::Creature>(p); - int initHP = c->getHP(); - if(heal) { - c->applyHealing(amnt); - } else { - c->applyDamage(amnt, dmgType, qualifiers); - } - text << (heal? "Healing " : "Damaging ") << c->getGivenName() << " the " << c->getCreatureName() << " by " << amnt; + text << (heal? "Healing " : "Damaging ") << c->getGivenName() << " the " << c->getCreatureName() << " by " << amnt; if(! heal) { std::string qualsString; std::vector<std::string> positiveQuals; @@ -39,23 +33,23 @@ namespace cmd { text << qualsString << " " << dmgType << " damage"; } text << ". HP: " << initHP << " -> " << c->getHP() << "." << std::endl; - utils::saveJson(*c, p); return text.str(); } - std::string healOrDamage(bool heal, std::vector<std::string> args, std::vector<std::string> flags) { - 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()); - } - } - } + std::string healOrDamageProgrammatic(fs::path p, bool heal, int amnt, std::string dmgType, const std::vector<rules::Qualifier>& qualifiers) { + auto c = utils::instantiate<creature::Creature>(p); + int initHP = c->getHP(); + if(heal) { + c->applyHealing(amnt); + } else { + c->applyDamage(amnt, dmgType, qualifiers); + } + utils::saveJson(*c, p); + return formatHealingDamage(c, initHP, heal, amnt, dmgType, qualifiers); + } + + std::string healOrDamage(bool heal, std::vector<std::string> args, std::map<std::string, std::string> flags) { + auto qualifiers = parseQualifiers(flags); fs::path p = getTruePath(args[0]); int amnt = utils::parseInt(args[1]); std::string dmgType = "force"; @@ -65,10 +59,10 @@ namespace cmd { return healOrDamageProgrammatic(p, heal, amnt, dmgType, qualifiers); } - std::string attack(std::vector<std::string> args, std::vector<std::string> flags) { + std::string attack(std::vector<std::string> args, std::map<std::string, std::string> flags) { std::stringstream text; - bool is2h = std::find(flags.begin(), flags.end(), "2") != flags.end(); - bool is1h = std::find(flags.begin(), flags.end(), "1") != flags.end(); + bool is2h = flags.find("2") != flags.end(); + bool is1h = flags.find("1") != flags.end(); if(is2h and is1h) { text << "ERROR: Cannot be both 1 handed and 2 handed!" << std::endl; return text.str(); @@ -120,9 +114,49 @@ namespace cmd { return healOrDamage(true, args, {}); } - std::string damage(std::vector<std::string> args, std::vector<std::string> flags) { + std::string damage(std::vector<std::string> args, std::map<std::string, std::string> flags) { return healOrDamage(false, args, flags); } + + std::string save(std::vector<std::string> args, std::map<std::string, std::string> flags) { + if(args.size() < 3) { + throw std::runtime_error("Subcommand 'save' requires at least 3 arguments"); + } + std::stringstream text; + rules::Ability ability = rules::tryGetAbilityOrSkill<rules::Ability>(args[0]); + if(! ability) { + throw std::runtime_error("Requires a valid ability name but received \"" + args[0] + "\"."); + } + args.erase(args.begin()); + int DC = utils::parseInt(args[0]); + args.erase(args.begin()); + // Now iterate over the paths + for(std::string s : args) { + fs::path p = getTruePath(s); + auto c = utils::instantiate<creature::Creature>(p); + int initHP = c->getHP(); + int rolled = dice::roll(20); + int bonus = c->getAbilitySaveBonus(ability); + int damage = 0; + std::string type = "force"; + bool halves = flags.find("halves") != flags.end(); + if(flags.find("damage") != flags.end()) { + damage = utils::parseInt(flags.at("damage")); + if(flags.find("type") != flags.end()) type = flags.at("type"); + auto qualifiers = parseQualifiers(flags); + rolled = c->saveOrDamage(ability, DC, damage, type, qualifiers, halves); + rolled -= bonus; // It's combined in creature + } + bool passed = rolled + bonus >= DC; + text << c->getName() << " " << (passed? "PASS" : "FAIL") << ": "; + text << formatRoll(ability.getFull(), "save", rolled, bonus); + if(flags.find("damage") != flags.end() and (halves or ! passed)) { + text << formatHealingDamage(c, initHP, false, damage, type, parseQualifiers(flags)); + } + utils::saveJson(*c, p); + } + return text.str(); + } std::string reset(std::vector<std::string> args) { for(std::string s : args) { diff --git a/src/cmd_usage.cc b/src/cmd_usage.cc index 74cfe6b..44daca4 100644 --- a/src/cmd_usage.cc +++ b/src/cmd_usage.cc @@ -24,6 +24,10 @@ namespace cmd { text << indDesc << "Roll a skill check, save, or attack." << std::endl; text << indOpt << "attack path1 name path2 [-1] [-2]" << std::endl; text << indDesc << "Roll an attack for path1 attacking path2. Handedness defaults to 2h if versatile and not holding a shield, otherwise use -1 and -2." << std::endl; + text << indOpt << "save ability DC paths [--damage DMG] [--type TYPE] [--halves] [--magical,-m] [--silvered,-s] [--adamantine,-a]" << std::endl; + text << indDesc << "Force creatures at paths to make an ability save at DC. Default: report pass/fail." << std::endl; + text << indDesc << " If --damage, failed saves take TYPE damage (default force), passed saves negate." << std::endl; + text << indDesc << " If --halves, passed saves halve damage rather than negate." << 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; diff --git a/src/creature.cc b/src/creature.cc index f48888c..6491024 100644 --- a/src/creature.cc +++ b/src/creature.cc @@ -115,6 +115,17 @@ namespace creature { } } + int Creature::saveOrDamage(const rules::Ability& ability, int DC, int amount, const std::string& type, const std::vector<rules::Qualifier>& qualifiers, bool saveHalves) { + // TODO: Something about evasion + int rolled = dice::roll(20) + getAbilitySaveBonus(ability); + if(rolled >= DC and saveHalves) { + applyDamage(amount/2, type, qualifiers); + } else if(rolled < DC) { + applyDamage(amount, type, qualifiers); + } + return rolled; + } + void Creature::applyHealing(int amount) { data->hp += amount; if(data->hp > data->hpMax) { diff --git a/src/creature.h b/src/creature.h index d8dbf29..09760e3 100644 --- a/src/creature.h +++ b/src/creature.h @@ -91,6 +91,8 @@ namespace creature { void turnStart(void); void turnEnd(void); void applyDamage(int amount, const std::string& type, const std::vector<rules::Qualifier>& qualifiers); + // Returns the amount rolled + int saveOrDamage(const rules::Ability& ability, int DC, int amount, const std::string& type, const std::vector<rules::Qualifier>& qualifiers, bool saveHalves); void applyHealing(int amount); void opportunityAttack(const creature::Creature& other); diff --git a/src/dmtool.cc b/src/dmtool.cc index e84630b..6ae096f 100644 --- a/src/dmtool.cc +++ b/src/dmtool.cc @@ -24,16 +24,23 @@ 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; +// Removes flags from args (in-place) and returns map of options +std::map<std::string, std::string> extractFlags(std::vector<std::string>& args, const std::vector<std::string>& knownOptions = {}) { + std::map<std::string, 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); + if(it < args.end()-1) { + ret.emplace(*it, *(it+1)); + if(std::find(knownOptions.begin(), knownOptions.end(), *it) != knownOptions.end()) { + args.erase(it); + } + } else { + ret.emplace(*it, std::string()); + } args.erase(it); } else { it++; @@ -43,12 +50,12 @@ std::vector<std::string> extractFlags(std::vector<std::string>& args) { } int main(int argc, char *argv[]) { - // Full thing is surrounded in try/catch to pleasantly print out any errors + // Full thing is surrounded in try/catch to plesantly print out any errors try { std::string exename = argv[0]; std::vector<std::string> args(&argv[1], &argv[argc]); auto argsWithFlags = args; - std::vector<std::string> flags = extractFlags(args); + auto flags = extractFlags(args); cmd::mkdir({cmd::getTruePath("")}); if(args.empty()) { std::cout << cmd::list(args); @@ -68,6 +75,12 @@ int main(int argc, char *argv[]) { else if(cmd == "attack") std::cout << cmd::attack(args, flags); else if(cmd == "damage") std::cout << cmd::damage(args, flags); else if(cmd == "heal") std::cout << cmd::heal(args); + else if(cmd == "save") { + args = argsWithFlags; + extractFlags(args, {"damage", "type"}); + args.erase(args.begin()); + std::cout << cmd::save(args, flags); + } 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); |