diff options
author | Your Name <you@example.com> | 2021-06-12 15:32:53 -0400 |
---|---|---|
committer | Your Name <you@example.com> | 2021-06-12 15:32:53 -0400 |
commit | 01293baa64fa905c5763020bd6c0b4903d41fc78 (patch) | |
tree | 4c49a63852fd84ead388a8fd092d64d2df7f9e1b /src | |
parent | b27700a7e0b281ece3dea23060c17e0cae28715d (diff) | |
download | dmtool-01293baa64fa905c5763020bd6c0b4903d41fc78.tar.gz dmtool-01293baa64fa905c5763020bd6c0b4903d41fc78.tar.bz2 dmtool-01293baa64fa905c5763020bd6c0b4903d41fc78.zip |
Verified some creature attacks
Diffstat (limited to 'src')
-rw-r--r-- | src/cmd_fsops.cc | 10 | ||||
-rw-r--r-- | src/creature.cc | 28 | ||||
-rw-r--r-- | src/item.cc | 8 | ||||
-rw-r--r-- | src/utils.h | 18 | ||||
-rw-r--r-- | src/weapon.cc | 66 |
5 files changed, 104 insertions, 26 deletions
diff --git a/src/cmd_fsops.cc b/src/cmd_fsops.cc index 5133313..ac4bdef 100644 --- a/src/cmd_fsops.cc +++ b/src/cmd_fsops.cc @@ -21,10 +21,12 @@ namespace cmd { } 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; + if(de.path().filename().string()[0] != '.') { + if(de.is_directory()) { + text << de.path().filename().string() << "/" << std::endl; + } else { + text << de.path().stem().string() << std::endl; + } } } } diff --git a/src/creature.cc b/src/creature.cc index 5130362..6069285 100644 --- a/src/creature.cc +++ b/src/creature.cc @@ -78,6 +78,10 @@ namespace creature { for(int i = 0; i < data->hit_die_count; i++) { data->hpMax += dice::roll(data->hit_die_sides); } + // If less than zero (caused by negative con bonus), set to minimum of 1 + if(data->hpMax <= 0) { + data->hpMax = 1; + } data->hp = data->hpMax; } } @@ -227,10 +231,20 @@ namespace creature { data->hp = data->hpMax; } + int getShieldBonus(const Creature& c) { + for(auto a : utils::castPtrs<entry::Item, entry::Armor>(c.getInventory())) { + if(a->getArmorType() == "shield") { + return a->getACBonus(); + } + } + return 0; + } + const int getAC(const Creature& c) { auto natArmor = c.getNaturalArmor(); if(! natArmor.name.empty()) { - return natArmor.bonus; + // Shields stack with nat armor (see lizardfolk) + return natArmor.bonus + getShieldBonus(c); } int dex = c.getBonus(rules::Ability::Dex()); int baseBonus = 10 + dex; @@ -240,7 +254,7 @@ namespace creature { continue; } auto armorType = a->getArmorType(); - if(armorType== "misc" || armorType == "shield") { + if(armorType == "misc" || armorType == "shield") { miscBonus += a->getACBonus(); } else { baseBonus = a->getACBonus(); @@ -296,7 +310,11 @@ namespace creature { std::stringstream text; text << getGivenName() << " (" << getCreatureName() << "): " << getHP() << "/" << getHPMax() << " hp, " << getAC(*this) << " ac"; if(! getNaturalArmor().name.empty()) { - text << " (" << getNaturalArmor().name << ")"; + text << " (" << getNaturalArmor().name; + if(getShieldBonus(*this) != 0) { + text << ", shield"; + } + text << ")"; } else { std::string armor = utils::join(mapItems(utils::castPtrs<entry::Item, entry::Armor>(getInventory())), ", "); if(! armor.empty()) { @@ -318,9 +336,9 @@ namespace creature { text << std::endl; text << "Senses: "; if(! getSenses().empty()) { - text << utils::join(getSenses(), ", ") << ". "; + text << utils::join(getSenses(), ", ") << ", "; } - text << "Passive Perception " << 10 + getSkillBonus(rules::Skill::Perception()) << std::endl; + text << "Passive Perception " << 10 + getSkillBonus(rules::Skill::Perception()) + (data->observant? 5 : 0) << std::endl; if(! getLanguages().empty()) { text << "Languages: " << getLanguages() << std::endl; } diff --git a/src/item.cc b/src/item.cc index 3bb9895..5ecdb0c 100644 --- a/src/item.cc +++ b/src/item.cc @@ -11,8 +11,12 @@ using namespace std; namespace entry { shared_ptr<Item> Item::create(const nlohmann::json& data) { - if(data["type"] == "weapons") { - return utils::loadDFromJson<Item, Weapon>(data); + if(data["type"] == "weapons" || data["type"] == "spell attack") { + auto w = utils::loadDFromJson<Item, Weapon>(data); + if(! data["text"].empty()) { + w->Entry::setText(data["text"]); + } + return w; } else if(data["type"] == "armor") { return utils::loadDFromJson<Item, Armor>(data); } diff --git a/src/utils.h b/src/utils.h index 005e5be..77096e7 100644 --- a/src/utils.h +++ b/src/utils.h @@ -7,6 +7,7 @@ #include <map> #include <sstream> #include <memory> +#include <optional> #include <stdexcept> #include <filesystem> @@ -23,6 +24,23 @@ namespace nlohmann { opt = std::shared_ptr<T>(T::create(j)); } }; + + template <typename T> struct adl_serializer<std::optional<T>> { + static void to_json(json& j, const std::optional<T>& opt) { + if(opt) { + j = *opt; + } else { + j = nullptr; + } + } + static void from_json(const json& j, std::optional<T>& opt) { + if(j.is_null()) { + opt = std::optional<T>(); + } else { + opt = j.get<T>(); + } + } + }; } namespace utils { diff --git a/src/weapon.cc b/src/weapon.cc index 053dbc1..cdf4657 100644 --- a/src/weapon.cc +++ b/src/weapon.cc @@ -5,6 +5,7 @@ #include <string> #include <sstream> #include <algorithm> +#include <optional> using namespace std; @@ -17,9 +18,12 @@ namespace entry { int reach; int cost; double weight; + std::optional<int> toHitOverride; + std::optional<int> dmgBonusOverride; + std::optional<rules::Ability> abilityOverride; }; - NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(weaponImpl, damage, properties, weapon_type, range, reach, cost, weight); + NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(weaponImpl, damage, properties, weapon_type, range, reach, cost, weight, toHitOverride, dmgBonusOverride, abilityOverride); NLOHMANN_FRIEND_DEFS(Item, Weapon, data); @@ -36,7 +40,7 @@ namespace entry { string getTextHelper(const Weapon& w, string toHitBonus, string damageBonus) { stringstream text; text << "+" << toHitBonus << " to hit, "; - if(w.getReach() > 0) { + if(w.getReach() > 0 or w.getRange().second == 0) { text << "reach " << w.getReach() << " ft."; if(w.getRange().second > 0) { text << " or "; @@ -49,23 +53,43 @@ namespace entry { auto dmgs = w.getDamage(); for(size_t i = 0; i < dmgs.size(); i++) { const Damage& d = dmgs[i]; - text << d.dmg_die_count << "d" << d.dmg_die_sides; - if(i == 0) { + text << d.dmg_die_count; + if(d.dmg_die_sides > 1) { + text << "d" << d.dmg_die_sides; + } + if((i == 0 or dmgs[i].is_or) and w.getType() == "weapons" and d.dmg_die_sides != 1) { if(w.getProperties().count("versatile")) { text << " (or " << d.dmg_die_count << "d" << d.dmg_die_sides + 2 << " if two-handed)"; } - text << " + " << damageBonus; + try { + int dmgBonusInt = stoi(damageBonus); + if(dmgBonusInt > 0) { + text << " + " << dmgBonusInt; + } else if(dmgBonusInt < 0) { + text << " - " << dmgBonusInt * -1; + } // Else it's zero + } catch(exception& e) { + text << " + " << damageBonus; + } } text << " " << d.dmg_type << " damage"; if(i < dmgs.size()-1) { - if(d.is_or) { + if(dmgs[i+1].is_or) { text << " or "; } else { text << " plus "; } } } - text << "."; + if(w.Entry::getText().empty()) { + text << "."; + } else { + char first = w.Entry::getText()[0]; + if('a' <= first and 'z' >= first) { + text << ' '; + } + text << w.Entry::getText(); + } auto props = w.getProperties(); // We don't care about finesse nor versatile because they're already handled props.erase("finesse"); @@ -73,10 +97,9 @@ namespace entry { if(! props.empty()) { text << " Additional properties: " << utils::join(props, ", ") << "."; } - if(! w.Entry::getText().empty()) { - text << " " << w.Entry::getText(); + if(! w.Substantial::getText().empty()) { + text << " " << w.Substantial::getText(); } - text << " " << w.Substantial::getText(); return text.str(); } @@ -103,7 +126,7 @@ namespace entry { vector<Damage> dmgsVersatile = rollDmg(w, true); int abilityBonus = c.getBonus(creature::getBestAbility(getAbilityOptions(w), c)); for(size_t i = 0; i < dmgsNoVersatile.size(); i++) { - if(i == 0) { + if(i == 0 and w.getType() == "weapons" and dmgsNoVersatile[0].dmg_die_sides != 1) { text << dmgsNoVersatile[i].rolled + abilityBonus; if(w.getProperties().count("versatile")) { text << " (or " << dmgsVersatile[i].rolled + abilityBonus << " if two-handed)"; @@ -136,19 +159,25 @@ namespace entry { if(w.getRange().second > 0) { return {rules::Ability::Dex()}; } - cerr << "Error processing weapon!" << endl; + //cerr << "Error processing weapon: " << w.getName() << "!" << endl; + // Default to str return {rules::Ability::Str()}; } string Weapon::getText() const { auto abilities = getAbilityOptions(*this); string abilityString; - if(abilities.size() == 1) { + if(data->dmgBonusOverride) { + abilityString = to_string(*data->dmgBonusOverride); + } else if(data->abilityOverride) { + abilityString = data->abilityOverride->getAbbrev(); + } else if(abilities.size() == 1) { abilityString = string(abilities[0]); } else { abilityString = "max(" + utils::join(abilities, ", ") + ")"; } - return getTextHelper(*this, "(" + abilityString + " + prof)", abilityString); + string toHitString = data->toHitOverride ? to_string(*data->toHitOverride) : "(" + abilityString + " + prof)"; + return getTextHelper(*this, toHitString, abilityString); } @@ -157,7 +186,14 @@ namespace entry { text << getName() << " (" << getType() << "): "; // Determine best ability bonus int abilityBonus = c.getBonus(creature::getBestAbility(getAbilityOptions(*this), c)); - text << getTextHelper(*this, to_string(abilityBonus + c.getProficiency()), to_string(abilityBonus)); + if(data->abilityOverride) { + abilityBonus = c.getBonus(*data->abilityOverride); + } + string toHitString = data->toHitOverride ? to_string(*data->toHitOverride) : to_string(abilityBonus + c.getProficiency()); + if(data->dmgBonusOverride) { + abilityBonus = *data->dmgBonusOverride; + } + text << getTextHelper(*this, toHitString, to_string(abilityBonus)); return text.str(); } } |