#include "creature.h" #include "json.hpp" #include "dice.h" #include "rules.h" #include "feature.h" #include "weapon.h" #include "armor.h" #include "attack.h" #include "spellcasting.h" #include #include #include #include typedef nlohmann::json json; using namespace std; namespace creature { template map makeMap(map src) { map ret; for(auto& [abilityStr, val] : src) { ret.insert({T(abilityStr), val}); } return ret; } Creature::Creature(const json& data, const json& base) : Entry(base), inventory(utils::json2ptrvec(data["inventory"])), stats(makeMap(data["stats"])), skills(makeMap(data["skills"])), proficiency(data["prof"]), size(data["size"]), alignment(data["alignment"]), hdCount(data["hit_die_count"]), hdSides(data["hit_die_sides"]), speed(data["speed"]), saves(utils::json2vec(data["saves"])), langs(data["langs"]), cr(data["cr"]), natArmorName(data["natural_armor"]["name"]), natArmorBonus(data["natural_armor"]["bonus"]), dmgImmunities(utils::json2vec(data["d_immunities"])), dmgResistances(utils::json2vec(data["d_resistances"])), dmgVulnerabilities(utils::json2vec(data["d_vulnerabilities"])), condImmunities(utils::json2vec(data["c_immunities"])), features(utils::json2ptrvec(data["features"])) { // Initialize names and hp if(((map) data).contains("givenName")) { givenName = data["givenName"]; hpMax = data["hpMax"]; hp = data["hp"]; } else { givenName = "Jerry"; //TODO: Autogenerate hpMax = this->getBonus(rules::Ability::Con()) * hdCount; for(int i = 0; i < hdCount; i++) { hpMax += dice::roll(hdSides); } hp = hpMax; } } nlohmann::json Creature::toJson() const { nlohmann::json data = Entry::toJson(); data["size"] = size; data["alignment"] = alignment; data["hit_die_count"] = hdCount; data["hit_die_sides"] = hdSides; data["speed"] = speed; data["stats"] = stats; data["skills"] = skills; data["saves"] = saves; data["langs"] = langs; data["cr"] = cr; data["prof"] = proficiency; data["natural_armor"]["name"] = natArmorName; data["natural_armor"]["bonus"] = natArmorBonus; data["d_immunities"] = dmgImmunities; data["d_resistances"] = dmgResistances; data["d_vulnerabilities"] = dmgVulnerabilities; data["c_immunities"] = condImmunities; data["givenName"] = givenName; data["hpMax"] = hpMax; data["hp"] = hp; data["inventory"] = utils::ptrvec2json(inventory); data["features"] = utils::ptrvec2json(features); return data; } // True if type without matching qualifiers is in subdata bool conditionApplies(const string& type, const vector& qualifiers, const vector subdata) { bool applies = false; for(dmgType con : subdata) { if(con.type == type) { applies = true; for(auto qualifier : qualifiers) { if(find(con.qualifiers.begin(), con.qualifiers.end(), qualifier) != con.qualifiers.end()) { applies = false; } } } } return applies; } void Creature::applyDamage(int amount, const string& type, const vector& qualifiers) { if(! conditionApplies(type, qualifiers, dmgImmunities)) { if(conditionApplies(type, qualifiers, dmgResistances)) { hp -= amount / 2; } else if(conditionApplies(type, qualifiers, dmgVulnerabilities)) { hp -= amount * 2; } else { hp -= amount; } } } void Creature::applyHealing(int amount) { hp += amount; if(hp > hpMax) { hp = hpMax; } } int Creature::getSkillBonus(const rules::Skill& skill) const { int bonus = this->getBonus(skill.getAbility()); if(skills.contains(skill)) { bonus += skills.at(skill) * getProficiency(); } return bonus; } int Creature::getAbilitySaveBonus(const rules::Ability& ability) const { int bonus = this->getBonus(ability); if(find(saves.begin(), saves.end(), ability) != saves.end()) { bonus += getProficiency(); } return bonus; } void Creature::addInventoryItem(shared_ptr item) { inventory.push_back(item); } shared_ptr Creature::getSpellcasting() const { for(auto f : getFeatures()) { if(f->getType() == "spells") { return dynamic_pointer_cast(f); } } return shared_ptr(); } void Creature::addSpell(shared_ptr spell) { auto sc = getSpellcasting(); if(! sc) { throw runtime_error("Creature " + getName() + " has no spellcasting"); } std::size_t level = spell->getLevel(); while(sc->getSlotLevels().size() <= level) { sc->addSlotLevel(); } sc->getSlotLevels()[level]->spells.push_back(spell); } void Creature::removeSpell(shared_ptr spell) { auto sc = getSpellcasting(); if(sc) { if(sc->getSlotLevels().size() > (size_t) spell->getLevel()) { auto sl = sc->getSlotLevels()[spell->getLevel()]; for(auto it = sl->spells.begin(); it != sl->spells.end(); it++) { if(*it == spell) { sl->spells.erase(it); break; } } } } } void Creature::removeInventoryItem(shared_ptr item) { for(auto it = inventory.begin(); it != inventory.end(); it++) { if(*it == item) { inventory.erase(it); break; } } } map Creature::getSkills() const { map s; for(auto skill : skills) { s.insert({skill.first, getSkillBonus(skill.first)}); } return s; } map Creature::getSaves() const { map s; for(auto save : saves) { s.insert({save, this->getBonus(save) + getProficiency()}); } return s; } void Creature::setScore(const rules::Ability& ability, int score) { int initBonus = getBonus(ability); stats[ability] = score; if(ability == rules::Ability::Con()) { int delta = getBonus(ability) - initBonus; hpMax += delta * hdCount; hp += delta * hdCount; } } void Creature::setProfLevel(const rules::Skill& skill, int level) { skills[skill] = level; if(level == 0) { skills.erase(skill); } } void Creature::longRest() { hp = hpMax; } const int getAC(const Creature& c) { auto natArmor = c.getNaturalArmor(); if(! natArmor.first.empty()) { return natArmor.second; } int dex = c.getBonus(rules::Ability::Dex()); int baseBonus = 10 + dex; int miscBonus = 0; for(auto a : utils::castPtrs(c.getInventory())) { if(c.getScore(rules::Ability::Str()) < a->getStrRequirement()) { continue; } auto armorType = a->getArmorType(); if(armorType== "misc" || armorType == "shield") { miscBonus += a->getACBonus(); } else { baseBonus = a->getACBonus(); if(armorType == "light") { baseBonus += dex; } else if(armorType == "medium") { baseBonus += (dex > 2)? 2 : dex; } } } return baseBonus + miscBonus; } vector> getAttacks(const Creature& c) { vector> a = utils::castPtrs(c.getInventory()); for(auto attack : utils::castPtrs(c.getFeatures())) { a.push_back(shared_ptr(new entry::Weapon(attack->getWeapon()))); } return a; } rules::Ability getBestAbility(const std::vector& abilities, const Creature& c) { const rules::Ability* bestA = &abilities[0]; for(auto a : abilities) { if(c.getBonus(a) > c.getBonus(*bestA)) { bestA = &a; } } return *bestA; } template vector mapItems(const vector>& items) { vector out; for(auto i : items) { out.push_back(i->getName()); } return out; } vector dmgTypes2text(vector dmg) { vector ret; for(dmgType t : dmg) { ret.push_back(t.getText()); } return ret; } string formatDmgTypeVector(string title, vector dmg) { return title + ": " + utils::join(dmgTypes2text(dmg), "; "); } string genText(const Creature& c) { stringstream text; text << c.getGivenName() << " (" << c.getCreatureName() << "): " << c.getHP() << "/" << c.getHPMax() << " hp, " << getAC(c) << " ac"; if(! c.getNaturalArmor().first.empty()) { text << " (" << c.getNaturalArmor().first << ")"; } else { string armor = utils::join(mapItems(utils::castPtrs(c.getInventory())), ", "); if(! armor.empty()) { text << " (" << armor << ")"; } } text << ", speed " << c.getSpeed() << endl; text << "A cr " << c.getCR() << " " << c.getAlignment() << " " << c.getSize() << " " << c.getType() << "." << endl; text << "Stats:" << endl; using namespace rules; vector abilities {Ability::Str(), Ability::Dex(), Ability::Con(), Ability::Int(), Ability::Wis(), Ability::Cha()}; for(auto ability : abilities) { text << " " << setw(6) << std::left << ability.getAbbrev(); } text << endl; for(auto ability : abilities) { text << setw(7) << std::left << (to_string(c.getScore(ability)) + "(" + to_string(c.getBonus(ability)) + ")"); } text << endl; text << "Senses: "; if(! c.getSenses().empty()) { text << utils::join(c.getSenses(), ", ") << ". "; } text << "Passive Perception " << 10 + c.getSkillBonus(rules::Skill::Perception()) << endl; if(! c.getLanguages().empty()) { text << "Languages: " << c.getLanguages() << endl; } text << "Proficiency: " << c.getProficiency() << endl; if(! c.getSkills().empty()) { text << endl << "Skills:" << endl; for(auto skill : c.getSkills()) { text << " * " << skill.first.getName() << " (+" << skill.second << ")" << endl; } } if(! c.getSaves().empty()) { text << endl << "Saves:" << endl; for(auto save : c.getSaves()) { text << " * " << save.first.getAbbrev() << " (+" << save.second << ")" << endl; } } if(! c.getDmgImmunities().empty()) { text << formatDmgTypeVector("\nDamage Immunities", c.getDmgImmunities()) << endl; } if(! c.getDmgResistances().empty()) { text << formatDmgTypeVector("\nDamage Resistances", c.getDmgResistances()) << endl; } if(! c.getDmgVulnerabilities().empty()) { text << formatDmgTypeVector("\nDamage Vulnerabilities", c.getDmgVulnerabilities()) << endl; } if(! c.getCondImmunities().empty()) { text << formatDmgTypeVector("\nCondition Immunities", c.getCondImmunities()) << endl; } if(! c.getFeatures().empty()) { text << endl << "Features:" << endl; for(auto f: c.getFeatures()) { text << " * " << f->getText(c) << endl; } } if(! c.getInventory().empty()) { text << endl << "Inventory:" << endl; for(auto i : c.getInventory()) { text << " * " << i->getText(c) << endl; } } if(! c.Entry::getText().empty()) { text << endl << c.Entry::getText() << endl; } return text.str(); } }