#include "entry.h" #include "settings.h" #include "creature.h" #include "dice.h" #include "weapon.h" #include "spell.h" #include "spellcasting.h" #include #include #include #include #include #include #include #include namespace fs = std::filesystem; void usage(const std::string& exename) { std::cout << "Usage:" << std::endl; std::string indOpt = " " + exename + " "; std::string indDesc = " "; std::cout << indOpt << "[ls] [subfolder]" << std::endl; std::cout << indDesc << "List creatures and objects." << std::endl; std::cout << indOpt << "cp old-path new-path" << std::endl; std::cout << indDesc << "Copy old-path to new-path, instantiating it." << std::endl; std::cout << indOpt << "mkdir path" << std::endl; std::cout << indDesc << "Make a new directory for holding creatures and objects." << std::endl; std::cout << indOpt << "mv old-path new-path" << std::endl; std::cout << indDesc << "Move old-path to new-path." << std::endl; std::cout << indOpt << "rm path" << std::endl; std::cout << indDesc << "Remove existing creature, object, or directory." << std::endl; std::cout << indOpt << "roll path name" << std::endl; std::cout << indDesc << "Roll a skill check, save, or attack." << std::endl; std::cout << indOpt << "damage path amount [type] [--magical,-m] [--silvered,-s] [--adamantine,-a]" << std::endl; std::cout << indDesc << "Damage creature by amount. Type defaults to \"force\"." << std::endl; std::cout << indOpt << "heal path amount" << std::endl; std::cout << indDesc << "Heal creature by amount." << std::endl; std::cout << indOpt << "reset path" << std::endl; std::cout << indDesc << "Reset creature to full health (as if completing a long rest)." << std::endl; std::cout << indOpt << "set path field value" << std::endl; std::cout << indDesc << "Set a field to a new value, where field is any of:" << std::endl; std::cout << indDesc << " ability (str, dex, con, int, wis, cha); value is new ability score" << std::endl; std::cout << indDesc << " skill (athletics, \"sleight of hand\", etc.); value is (none|proficient|expert)" << std::endl; std::cout << indDesc << " proficiency; value is new proficency bonus." << std::endl; std::cout << indDesc << " name; value is new given name." << std::endl; std::cout << indOpt << "edit path" << std::endl; std::cout << indDesc << "Edit notes associated with creature." << std::endl; std::cout << indOpt << "add path entry" << std::endl; std::cout << indDesc << "Add entry to creature, where entry is an item or spell." << std::endl; std::cout << indOpt << "del path entry" << std::endl; std::cout << indDesc << "Delete entry from creature, where entry is an item or spell." << std::endl; std::cout << indOpt << "help" << std::endl; std::cout << indDesc << "Show this help." << std::endl; } template std::shared_ptr instantiate(const fs::path& path) { std::shared_ptr ent; try { ent = entry::Entry::create(utils::loadJson(path)); } catch(std::exception& e) { if(fs::directory_entry(path).exists()) { throw std::runtime_error("Invalid json: " + path.string()); } else { throw std::runtime_error("No such file nor directory: " + path.string()); } } std::shared_ptr t = std::dynamic_pointer_cast(ent); if(! t) { throw std::runtime_error("Wrong instance type: " + ent->getType()); } return t; } int parseInt(const std::string& s) { try { return std::stoi(s); } catch(std::exception& e) { throw std::runtime_error("An integer was expected but " + s + " was given"); } } void save(const std::shared_ptr& e, const fs::path& path) { utils::saveJson(*e, path); } void print(const fs::path& path) { std::cout << instantiate(path)->getText() << std::endl; } fs::path getBaseDir() { return settings::getString("savedir"); } fs::path eraseRoot(fs::path& src) { if(src.empty()) return src; fs::path tmp; auto it = src.begin(); while(++it != src.end()) { tmp /= *it; } src = tmp; return src; } const std::vector virtPaths({"weapons", "armor", "spells", "creatures"}); fs::path getTruePath(fs::path virtPath) { fs::path p; if(std::find(virtPaths.begin(), virtPaths.end(), *virtPath.begin()) != virtPaths.end()) { p = settings::getString(*virtPath.begin()); eraseRoot(virtPath); } else { p = getBaseDir(); } p /= virtPath; if(fs::directory_entry(p.string() + ".json").is_regular_file()) return p.string() + ".json"; return p; } // Ensure the system is set up correctly void initFS() { fs::directory_entry de = fs::directory_entry(getBaseDir()); if(! de.exists()) { fs::create_directories(de); } } void list(const fs::path& p) { if(p.empty()) { // Print read-only dirs for(std::string name : virtPaths) { std::cout << name << "/ (read only)" << std::endl; } } fs::path truePath = getTruePath(p); if(fs::directory_entry(truePath).is_regular_file()) { print(truePath); } else if(fs::directory_entry(truePath).is_directory()) { for(fs::directory_entry de : fs::directory_iterator(truePath)) { if(de.is_directory()) { std::cout << de.path().filename().string() << "/" << std::endl; } else { std::cout << de.path().stem().string() << std::endl; } } } else { std::cerr << "Unknown path " << p << std::endl; } } void list(std::vector args) { if(args.empty()) { list(""); } else { for(std::string dir : args) { list(dir); } } } void mkdir(std::vector args) { for(std::string s : args) { fs::create_directories(getTruePath(s)); } } void cp(fs::path src, fs::path dest) { if(fs::directory_entry(src).is_regular_file()) { save(instantiate(src), dest); } else { mkdir({dest}); for(fs::directory_entry de : fs::directory_iterator(src)) { cp(de.path(), dest / de.path().filename()); } } } void cp(std::vector args) { // Operate by intantiating and saving // We do recursive! cp(getTruePath(args[0]), getTruePath(args[1])); } void mv(std::vector args) { fs::rename(getTruePath(args[0]), getTruePath(args[1])); } void rm(std::vector args) { for(std::string s : args) { fs::remove_all(getTruePath(s)); } } template T tryGetAbilityOrSkill(std::string src) { try { return T::fromString(src); } catch(std::exception& e) {} // eat. return T(); } void roll(std::vector args) { auto c = instantiate(getTruePath(args[0])); args.erase(args.begin()); std::string rollName = utils::join(args, " "); utils::lower(rollName); int rolled = dice::roll(20); auto printResults = [](std::string name, std::string type, int rolled, int bonus) { std::cout << name << " " << type << ": " << rolled << " (d20) + " << bonus << " (" << name << " " << type << " bonus) = " << rolled + bonus << std::endl; }; // Search through skills, saves, and attacks to roll rules::Skill skill = tryGetAbilityOrSkill(rollName); rules::Ability ability = tryGetAbilityOrSkill(rollName); if(skill) { printResults(skill.getName(), "check", rolled, c->getSkillBonus(skill)); } else if(ability) { printResults(ability.getFull(), "save", rolled, c->getAbilitySaveBonus(ability)); } else { for(auto w : creature::getAttacks(*c)) { if(w->getName() == rollName) { std::cout << w->getText(*c) << std::endl; int abilityBonus = c->getBonus(creature::getBestAbility(getAbilityOptions(*w), *c)); int bonus = abilityBonus + c->getProficiency(); printResults(w->getName(), "attack", rolled, bonus); std::cout << " on hit: " << entry::formatDmg(*w, *c) << std::endl; return; } } } } void healOrDamage(bool heal, std::vector args) { std::vector qualifiers; if(! heal) { auto it = args.begin(); while(it != args.end()) { if(*it == "-m" || *it == "--magical") { qualifiers.push_back(rules::Qualifier::Magical()); args.erase(it); } else if(*it == "-s" || *it == "--silvered") { qualifiers.push_back(rules::Qualifier::Silvered()); args.erase(it); } else if(*it == "-a" || *it == "--adamantine") { qualifiers.push_back(rules::Qualifier::Adamantine()); args.erase(it); } else { it++; } } } fs::path p = getTruePath(args[0]); auto c = instantiate(p); int amnt = 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); } std::cout << (heal? "Healing " : "Damaging ") << c->getGivenName() << " the " << c->getCreatureName() << " by " << amnt; if(! heal) { std::vector positiveQuals; for(auto qual : qualifiers) { positiveQuals.push_back(qual.getPositive()); } std::cout << " " << utils::join(positiveQuals, ", ") << " " << dmgType << " damage"; } std::cout << ". HP: " << initHP << " -> " << c->getHP() << "." << std::endl; save(c, p); } void reset(std::vector args) { for(std::string s : args) { fs::path p = getTruePath(s); auto c = instantiate(p); c->longRest(); save(c, p); } } void set(std::vector 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 = instantiate(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(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 = tryGetAbilityOrSkill(abilityOrSkill); rules::Ability ability = tryGetAbilityOrSkill(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, parseInt(toSet)); } else { throw std::runtime_error("Subcommand 'set' expected an ability, skill, proficiency, or name field to set, but was given " + abilityOrSkill); } } save(c, p); } void add(std::vector args) { fs::path p = getTruePath(args[0]); args.erase(args.begin()); // remove path from args auto c = instantiate(p); std::string addName = utils::join(args, " "); std::shared_ptr ent; fs::path path = getTruePath(addName); if(fs::directory_entry(path).exists()) { ent = instantiate(path); } else { ent = entry::Entry::create(utils::findByName(addName)); } // Determine if it is an item or a spell auto i = std::dynamic_pointer_cast(ent); if(i) { c->addInventoryItem(i); } else { auto s = std::dynamic_pointer_cast(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()); } } save(c, p); std::cout << "Added the " << ent->getType() << " " << ent->getName() << " to " << c->getGivenName() << " the " << c->getName() << std::endl; } void del(std::vector args) { fs::path p = getTruePath(args[0]); args.erase(args.begin()); // remove path from args auto c = instantiate(p); std::string itemName = utils::join(args, " "); utils::lower(itemName); // Loop through all of c's stuff, searching for itemName std::shared_ptr 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; } } } save(c, p); if(removed) { std::cout << "Successfully removed the " << removed->Entry::getType() << " " << removed->getName() << std::endl; } else { std::cout << "Could not find any inventory item nor spell by that name" << std::endl; } } const std::map> nargs({ {"cp", {2}}, {"mv", {2}}, {"damage", {2, 3, 4, 5, 6}}, {"heal", {2}}, }); void checkArgs(std::string cmd, std::vector args) { if(nargs.contains(cmd)) { auto& allowed = nargs.at(cmd); if(std::find(allowed.begin(), allowed.end(), args.size()) == allowed.end()) { throw std::runtime_error("Subcommand '" + cmd + "' expected " + utils::join(allowed, ", ") + " arguments but got " + std::to_string(args.size())); } } } int main(int argc, char *argv[]) { std::string exename = argv[0]; std::vector args(&argv[1], &argv[argc]); try { initFS(); } catch(fs::filesystem_error& e) { std::cerr << e.what() << std::endl; return 1; } if(args.empty()) { list(args); return 0; } std::string cmd = args[0]; std::vector argsOrig(args); args.erase(args.begin()); try { checkArgs(cmd, args); if(cmd == "ls") list(args); else if(cmd == "cp") cp(args); else if(cmd == "mkdir") mkdir(args); else if(cmd == "mv") mv(args); else if(cmd == "rm") rm(args); else if(cmd == "roll") roll(args); else if(cmd == "damage") healOrDamage(false, args); else if(cmd == "heal") healOrDamage(true, args); else if(cmd == "reset") reset(args); else if(cmd == "set") set(args); else if(cmd == "add") add(args); else if(cmd == "del") del(args); else if(cmd == "help") usage(exename); else list(argsOrig); } catch(std::exception& e) { std::cerr << e.what() << std::endl; return 1; } return 0; }