diff options
author | Your Name <you@example.com> | 2021-12-25 16:52:34 -0500 |
---|---|---|
committer | Your Name <you@example.com> | 2021-12-25 16:52:34 -0500 |
commit | 52b91a1aca6640b1797a63afc8a9e9a778b0964a (patch) | |
tree | 0cfb8ebe6a0a511607b3acc6c67228b796cd9c90 | |
parent | d8d1056af0383f0ef67c6d70d1594bcb430d7281 (diff) | |
download | dmtool-52b91a1aca6640b1797a63afc8a9e9a778b0964a.tar.gz dmtool-52b91a1aca6640b1797a63afc8a9e9a778b0964a.tar.bz2 dmtool-52b91a1aca6640b1797a63afc8a9e9a778b0964a.zip |
Added capability to cause two npcs to attack each other
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | files/dmtool.bash | 14 | ||||
-rw-r--r-- | src/cmd.cc | 6 | ||||
-rw-r--r-- | src/cmd.h | 4 | ||||
-rw-r--r-- | src/cmd_manipulate.cc | 311 | ||||
-rw-r--r-- | src/cmd_query.cc | 11 | ||||
-rw-r--r-- | src/cmd_usage.cc | 2 | ||||
-rw-r--r-- | src/dmtool.cc | 2 | ||||
-rw-r--r-- | src/weapon.cc | 21 | ||||
-rw-r--r-- | src/weapon.h | 3 |
10 files changed, 233 insertions, 143 deletions
@@ -41,7 +41,7 @@ src/cmd_fsops.o: src/cmd_fsops.cc src/cmd.h src/utils.h src/entry.h \ src/cmd_manipulate.o: src/cmd_manipulate.cc src/cmd.h src/utils.h \ src/entry.h src/defines.h src/creature.h src/rules.h src/feature.h \ - src/item.h src/spellcasting.h src/spell.h src/settings.h + src/item.h src/spellcasting.h src/spell.h src/settings.h src/weapon.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` src/cmd_query.o: src/cmd_query.cc src/cmd.h src/utils.h src/entry.h \ diff --git a/files/dmtool.bash b/files/dmtool.bash index 8bbd673..43aba72 100644 --- a/files/dmtool.bash +++ b/files/dmtool.bash @@ -81,7 +81,7 @@ _dmtool() { COMPREPLY=() local cur="${COMP_WORDS[COMP_CWORD]}" - local commands="ls cp mkdir mv rm attacks roll damage heal reset set edit add del spellcasting help git" + local commands="ls cp mkdir mv rm attacks roll attack damage heal 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 +93,7 @@ _dmtool() _dmtool_complete_entries fi ;; - attacks|roll|damage|heal|set|edit|del|spellcasting) + attacks|roll|attack|damage|heal|set|edit|del|spellcasting) if [[ $COMP_CWORD -le 2 ]]; then _dmtool_complete_entries else @@ -105,6 +105,16 @@ _dmtool() opts="$(${COMP_WORDS[0]} attacks ${COMP_WORDS[2]})" COMPREPLY+=($(compgen -W "$opts" -- ${cur})) ;; + attack) + COMPREPLY+=($(compgen -W "-1 -2" -- ${cur})) + if [[ $COMP_CWORD -eq 3 ]]; then + local IFS=$'\n' + opts="$(${COMP_WORDS[0]} attacks ${COMP_WORDS[2]})" + COMPREPLY+=($(compgen -W "$opts" -- ${cur})) + elif [[ $COMP_CWORD -eq 4 ]]; then + _dmtool_complete_entries + fi + ;; damage) COMPREPLY+=($(compgen -W "--magical -m --silvered -s --adamantine -a" -- ${cur})) if [[ $COMP_CWORD -eq 4 ]]; then @@ -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(); + } } @@ -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; |