aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYour Name <you@example.com>2022-01-03 16:25:45 -0500
committerYour Name <you@example.com>2022-01-03 16:25:45 -0500
commit3f78a7e1647ba94129236bd2bf4fc855c109628a (patch)
tree0a1538d44715738c8cbbae00bf1995777227b963
parentd222c5a39943ed9f83f11d63a42bdff4978179af (diff)
downloaddmtool-3f78a7e1647ba94129236bd2bf4fc855c109628a.tar.gz
dmtool-3f78a7e1647ba94129236bd2bf4fc855c109628a.tar.bz2
dmtool-3f78a7e1647ba94129236bd2bf4fc855c109628a.zip
Added command to force a saving throw
-rw-r--r--files/dmtool.bash28
m---------parser/5thSRD0
-rw-r--r--src/cmd.cc15
-rw-r--r--src/cmd.h8
-rw-r--r--src/cmd_manipulate.cc88
-rw-r--r--src/cmd_usage.cc4
-rw-r--r--src/creature.cc11
-rw-r--r--src/creature.h2
-rw-r--r--src/dmtool.cc25
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
diff --git a/src/cmd.cc b/src/cmd.cc
index 7029f0c..d84c55b 100644
--- a/src/cmd.cc
+++ b/src/cmd.cc
@@ -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;
+ }
}
diff --git a/src/cmd.h b/src/cmd.h
index 41973ad..978f6db 100644
--- a/src/cmd.h
+++ b/src/cmd.h
@@ -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);