aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYour Name <you@example.com>2021-12-25 16:52:34 -0500
committerYour Name <you@example.com>2021-12-25 16:52:34 -0500
commit52b91a1aca6640b1797a63afc8a9e9a778b0964a (patch)
tree0cfb8ebe6a0a511607b3acc6c67228b796cd9c90 /src
parentd8d1056af0383f0ef67c6d70d1594bcb430d7281 (diff)
downloaddmtool-52b91a1aca6640b1797a63afc8a9e9a778b0964a.tar.gz
dmtool-52b91a1aca6640b1797a63afc8a9e9a778b0964a.tar.bz2
dmtool-52b91a1aca6640b1797a63afc8a9e9a778b0964a.zip
Added capability to cause two npcs to attack each other
Diffstat (limited to 'src')
-rw-r--r--src/cmd.cc6
-rw-r--r--src/cmd.h4
-rw-r--r--src/cmd_manipulate.cc311
-rw-r--r--src/cmd_query.cc11
-rw-r--r--src/cmd_usage.cc2
-rw-r--r--src/dmtool.cc2
-rw-r--r--src/weapon.cc21
-rw-r--r--src/weapon.h3
8 files changed, 220 insertions, 140 deletions
diff --git a/src/cmd.cc b/src/cmd.cc
index 7be5e01..7029f0c 100644
--- a/src/cmd.cc
+++ b/src/cmd.cc
@@ -30,4 +30,10 @@ namespace cmd {
if(std::filesystem::directory_entry(p.string() + ".json").is_regular_file()) return p.string() + ".json";
return p;
}
+
+ std::string formatRoll(std::string name, std::string type, int rolled, int bonus) {
+ std::stringstream text;
+ text << name << " " << type << ": " << rolled << " (d20) + " << bonus << " (" << name << " " << type << " bonus) = " << rolled + bonus << std::endl;
+ return text.str();
+ }
}
diff --git a/src/cmd.h b/src/cmd.h
index b5d3d72..41973ad 100644
--- a/src/cmd.h
+++ b/src/cmd.h
@@ -19,6 +19,7 @@ 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 reset(std::vector<std::string> args);
std::string set(std::vector<std::string> args);
std::string add(std::vector<std::string> args);
@@ -36,4 +37,7 @@ namespace cmd {
// Not idempotent: only do once!
std::filesystem::path getTruePath(std::filesystem::path virtPath);
std::vector<std::string> getVirtDirs(void);
+
+ // Helper functions
+ std::string formatRoll(std::string name, std::string type, int rolled, int bonus);
}
diff --git a/src/cmd_manipulate.cc b/src/cmd_manipulate.cc
index b221c1f..294b600 100644
--- a/src/cmd_manipulate.cc
+++ b/src/cmd_manipulate.cc
@@ -4,42 +4,28 @@
#include "item.h"
#include "spellcasting.h"
#include "settings.h"
+#include "weapon.h"
+#include "dice.h"
+#include "armor.h"
#include <sstream>
#include <memory>
#include <cstdlib>
#include <fstream>
#include <iterator>
+#include <algorithm>
namespace fs = std::filesystem;
namespace cmd {
- std::string healOrDamage(bool heal, std::vector<std::string> args, std::vector<std::string> flags) {
+ std::string healOrDamageProgrammatic(fs::path p, bool heal, int amnt, std::string dmgType, std::vector<rules::Qualifier> qualifiers) {
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;
@@ -57,6 +43,79 @@ namespace cmd {
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());
+ }
+ }
+ }
+ fs::path p = getTruePath(args[0]);
+ int amnt = utils::parseInt(args[1]);
+ std::string dmgType = "force";
+ if(args.size() == 3) {
+ dmgType = args[2];
+ }
+ return healOrDamageProgrammatic(p, heal, amnt, dmgType, qualifiers);
+ }
+
+ std::string attack(std::vector<std::string> args, std::vector<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();
+ if(is2h and is1h) {
+ text << "ERROR: Cannot be both 1 handed and 2 handed!" << std::endl;
+ return text.str();
+ }
+ auto c1 = utils::instantiate<creature::Creature>(getTruePath(args[0]));
+ args.erase(args.begin());
+ fs::path p2 = getTruePath(args.back());
+ auto c2 = utils::instantiate<creature::Creature>(p2);
+ args.erase(args.end()-1);
+ std::string attackName = utils::join(args, " ");
+ utils::lower(attackName);
+ std::shared_ptr<entry::Weapon> w;
+ for(auto weap : creature::getAttacks(*c1)) {
+ if(weap->getName() == attackName) {
+ w = weap;
+ break;
+ }
+ }
+ text << w->getText(*c1) << std::endl;
+ int rolled = dice::roll(20);
+ int bonus = w->getToHitBonus(*c1);
+ text << formatRoll(w->getName(), "attack", rolled, bonus);
+ int ac = creature::getAC(*c2);
+ if(rolled + bonus >= ac) {
+ text << " Hit (" << (rolled + bonus) << " to hit >= " << ac << " ac): ";
+ bool wants2h = true;
+ for(auto a : utils::castPtrs<entry::Item, entry::Armor>(c1->getInventory())) {
+ if(a->getArmorType() == "shield") {
+ wants2h = false;
+ }
+ }
+ if(is2h) {
+ wants2h = true;
+ } else if(is1h) {
+ wants2h = false;
+ }
+ auto dmg = entry::rollDmg(*w, wants2h);
+ text << entry::formatDmg(*w, *c1, dmg) << std::endl;
+ for(auto d : dmg) {
+ text << " " << healOrDamageProgrammatic(p2, false, d.rolled, d.dmg_type, {});
+ }
+ } else {
+ text << " Miss (" << (rolled + bonus) << " to hit < " << ac << " ac)" << std::endl;
+ }
+ return text.str();
+ }
+
std::string heal(std::vector<std::string> args) {
return healOrDamage(true, args, {});
}
@@ -66,130 +125,130 @@ namespace cmd {
}
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);
- }
+ 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);
+ 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;
+ 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;
- }
+ 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();
}
std::string edit(std::vector<std::string> args) {
- auto p = getTruePath(args[0]);
+ auto p = getTruePath(args[0]);
auto e = utils::instantiate<entry::Entry>(p);
auto editor = settings::getString("editor");
// General workflow: copy notes (text) from e to a temp file, edit it, then copy back.
diff --git a/src/cmd_query.cc b/src/cmd_query.cc
index 5172cda..e13876f 100644
--- a/src/cmd_query.cc
+++ b/src/cmd_query.cc
@@ -22,25 +22,22 @@ namespace cmd {
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
if(rollName == "init" or rollName == "initiative") {
- printResults("Initiative", "check", rolled, c->getInitiative());
+ text << formatRoll("Initiative", "check", rolled, c->getInitiative());
}
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));
+ text << formatRoll(skill.getName(), "check", rolled, c->getSkillBonus(skill));
} else if(ability) {
- printResults(ability.getFull(), "save", rolled, c->getAbilitySaveBonus(ability));
+ text << formatRoll(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 bonus = w->getToHitBonus(*c);
- printResults(w->getName(), "attack", rolled, bonus);
+ text << formatRoll(w->getName(), "attack", rolled, bonus);
text << " on hit: " << entry::formatDmg(*w, *c) << std::endl;
break;
}
diff --git a/src/cmd_usage.cc b/src/cmd_usage.cc
index 6219996..74cfe6b 100644
--- a/src/cmd_usage.cc
+++ b/src/cmd_usage.cc
@@ -22,6 +22,8 @@ namespace cmd {
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 << "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 << "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/dmtool.cc b/src/dmtool.cc
index 90f7813..e84630b 100644
--- a/src/dmtool.cc
+++ b/src/dmtool.cc
@@ -9,6 +9,7 @@ const std::map<std::string, std::vector<int>> nargs({
{"cp", {2}},
{"mv", {2}},
{"attacks", {1}},
+ {"attack", {3}},
{"damage", {2, 3}},
{"heal", {2}},
{"spellcasting", {2, 3, 4}},
@@ -64,6 +65,7 @@ int main(int argc, char *argv[]) {
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 == "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 == "reset") std::cout << cmd::reset(args);
diff --git a/src/weapon.cc b/src/weapon.cc
index 7ae597f..e5f04f2 100644
--- a/src/weapon.cc
+++ b/src/weapon.cc
@@ -110,27 +110,24 @@ namespace entry {
bool first = true;
for(Damage& d : dmgs) {
d.rolled = 0;
- int sides = d.dmg_die_sides;
if(first && versatile && w.getProperties().count("versatile")) {
- sides += 2;
+ d.dmg_die_sides += 2;
}
first = false;
for(int i = 0; i < d.dmg_die_count; i++) {
- d.rolled += dice::roll(sides);
+ d.rolled += dice::roll(d.dmg_die_sides);
}
}
return dmgs;
}
- string formatDmg(const Weapon& w, const creature::Creature& c) {
+ string formatDmgHelper(const Weapon& w, const creature::Creature& c, const std::vector<Damage>& dmgsNoVersatile, const std::vector<Damage>& dmgsVersatile) {
stringstream text;
- vector<Damage> dmgsNoVersatile = rollDmg(w, false);
- vector<Damage> dmgsVersatile = rollDmg(w, true);
int abilityBonus = w.getDamageBonus(c);
for(size_t i = 0; i < dmgsNoVersatile.size(); i++) {
if(i == 0 and w.getType() == "weapons" and dmgsNoVersatile[0].dmg_die_sides != 1) {
text << dmgsNoVersatile[i].rolled + abilityBonus;
- if(w.getProperties().count("versatile")) {
+ if(w.getProperties().count("versatile") and ! dmgsVersatile.empty()) {
text << " (or " << dmgsVersatile[i].rolled + abilityBonus << " if two-handed)";
}
} else {
@@ -148,6 +145,16 @@ namespace entry {
return text.str();
}
+ string formatDmg(const Weapon& w, const creature::Creature& c) {
+ vector<Damage> dmgsNoVersatile = rollDmg(w, false);
+ vector<Damage> dmgsVersatile = rollDmg(w, true);
+ return formatDmgHelper(w, c, dmgsNoVersatile, dmgsVersatile);
+ }
+
+ string formatDmg(const Weapon& w, const creature::Creature& c, const std::vector<Damage>& dmgs) {
+ return formatDmgHelper(w, c, dmgs, {});
+ }
+
vector<rules::Ability> getAbilityOptions(const Weapon& w) {
// Do finesse
if(w.getProperties().count("finesse")) {
diff --git a/src/weapon.h b/src/weapon.h
index fc58262..49de396 100644
--- a/src/weapon.h
+++ b/src/weapon.h
@@ -15,7 +15,10 @@ namespace entry {
std::vector<rules::Ability> getAbilityOptions(const Weapon& w);
std::vector<Damage> rollDmg(const Weapon& w, bool versatile=false);
+ // Prints for both versatile options (if applicable)
std::string formatDmg(const Weapon& w, const creature::Creature& c);
+ // Prints for just the dmg provided
+ std::string formatDmg(const Weapon& w, const creature::Creature& c, const std::vector<Damage>& dmg);
struct Damage {
int dmg_die_count;