aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYour Name <you@example.com>2021-06-12 15:32:53 -0400
committerYour Name <you@example.com>2021-06-12 15:32:53 -0400
commit01293baa64fa905c5763020bd6c0b4903d41fc78 (patch)
tree4c49a63852fd84ead388a8fd092d64d2df7f9e1b /src
parentb27700a7e0b281ece3dea23060c17e0cae28715d (diff)
downloaddmtool-01293baa64fa905c5763020bd6c0b4903d41fc78.tar.gz
dmtool-01293baa64fa905c5763020bd6c0b4903d41fc78.tar.bz2
dmtool-01293baa64fa905c5763020bd6c0b4903d41fc78.zip
Verified some creature attacks
Diffstat (limited to 'src')
-rw-r--r--src/cmd_fsops.cc10
-rw-r--r--src/creature.cc28
-rw-r--r--src/item.cc8
-rw-r--r--src/utils.h18
-rw-r--r--src/weapon.cc66
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();
}
}