#include "entry.h" #include "settings.h" #include "creature.h" #include "dice.h" #include "weapon.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]" << 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 << " name; value is new given name." << std::endl; std::cout << indOpt << "edit path" << std::endl; std::cout << indDesc << "Edit notes associated to 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 << "help" << std::endl; std::cout << indDesc << "Show this help." << std::endl; } std::shared_ptr instantiate(const fs::path& path) { try { return 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()); } } } 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)); } } void roll(std::vector args) { std::shared_ptr e = instantiate(getTruePath(args[0])); std::shared_ptr c = std::dynamic_pointer_cast(e); if(! c) { throw std::runtime_error("Subcommand 'roll' expected a creature but was given an instance of " + e->getType()); } args.erase(args.begin()); std::string rollName = utils::join(args, " "); std::transform(rollName.begin(), rollName.end(), rollName.begin(), ::tolower); 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 try { rules::Skill skill = rules::Skill::string2skill(rollName); printResults(skill.getName(), "check", rolled, c->getSkillBonus(skill)); return; } catch(std::exception& e) {} // eat. try { rules::Ability ability = rules::Ability::string2ability(rollName); printResults(ability.getFull(), "save", rolled, c->getAbilitySaveBonus(ability)); return; } catch(std::exception& e) {} // eat. 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 damage(std::vector args) { fs::path p = getTruePath(args[0]); std::shared_ptr e = instantiate(p); std::shared_ptr c = std::dynamic_pointer_cast(e); if(! c) { throw std::runtime_error("Subcommand 'damage' expected a creature but was given an instance of " + e->getType()); } int dmg = 0; try { dmg = std::stoi(args[1]); } catch(std::exception& e) { throw std::runtime_error("Subcommand 'damage' expected an integer but was given " + args[1]); } std::string dmgType = "force"; if(args.size() == 3) { dmgType = args[2]; } std::vector qualifiers; // TODO int initHP = c->getHP(); c->applyDamage(dmg, dmgType, qualifiers); std::cout << "Applying " << dmg << " " << dmgType << " damage to " << c->getGivenName() << " the " << c->getCreatureName() << ". HP: " << initHP << " -> " << c->getHP() << "." << std::endl; save(c, p); } void heal(std::vector args) { fs::path p = getTruePath(args[0]); std::shared_ptr e = instantiate(p); std::shared_ptr c = std::dynamic_pointer_cast(e); if(! c) { throw std::runtime_error("Subcommand 'heal' expected a creature but was given an instance of " + e->getType()); } int amnt = 0; try { amnt = std::stoi(args[1]); } catch(std::exception& e) { throw std::runtime_error("Subcommand 'heal' expected an integer but was given " + args[1]); } int initHP = c->getHP(); c->applyHealing(amnt); std::cout << "Healing " << c->getGivenName() << " the " << c->getCreatureName() << " by " << amnt << ". HP: " << initHP << " -> " << c->getHP() << "." << std::endl; save(c, p); } void reset(std::vector args) {} void set(std::vector args) {} void add(std::vector args) {} const std::map> nargs({ {"cp", {2}}, {"mv", {2}}, {"damage", {2, 3}}, {"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") damage(args); else if(cmd == "heal") heal(args); else if(cmd == "reset") reset(args); else if(cmd == "set") set(args); else if(cmd == "add") add(args); else if(cmd == "help") usage(exename); else list(argsOrig); } catch(std::exception& e) { std::cerr << e.what() << std::endl; return 1; } return 0; }