From 38e33d8756a5b652965be8ada478b5c4238b857c Mon Sep 17 00:00:00 2001
From: Your Name <you@example.com>
Date: Tue, 18 May 2021 16:26:49 -0400
Subject: Added spellcasting command

---
 src/cmd.h             |  1 +
 src/cmd_manipulate.cc | 42 ++++++++++++++++++++++++++++++++++++++++++
 src/cmd_usage.cc      |  5 +++++
 src/creature.cc       |  8 ++++++++
 src/creature.h        |  1 +
 src/dmtool.cc         |  2 ++
 src/entry.h           |  3 +++
 src/feature.h         |  2 ++
 src/spellcasting.h    |  2 ++
 9 files changed, 66 insertions(+)

(limited to 'src')

diff --git a/src/cmd.h b/src/cmd.h
index 92a3cc3..69c258b 100644
--- a/src/cmd.h
+++ b/src/cmd.h
@@ -24,6 +24,7 @@ namespace cmd {
     std::string add(std::vector<std::string> args);
     std::string del(std::vector<std::string> args);
     std::string edit(std::vector<std::string> args);
+    std::string spellcasting(std::vector<std::string> args);
 
     //Queries
     std::string attacks(std::vector<std::string> args);
diff --git a/src/cmd_manipulate.cc b/src/cmd_manipulate.cc
index 1300008..9fc019e 100644
--- a/src/cmd_manipulate.cc
+++ b/src/cmd_manipulate.cc
@@ -204,4 +204,46 @@ namespace cmd {
         utils::saveJson(*e, p);
         return "";
     }
+
+    std::string spellcasting(std::vector<std::string> args) {
+        std::stringstream text;
+        auto p = getTruePath(args[0]);
+        auto c = utils::instantiate<creature::Creature>(p);
+        auto subcommand = args[1];
+        if(subcommand != "init" && subcommand != "ability" && subcommand != "level") {
+            throw std::runtime_error("Unknown option \"" + subcommand + "\"");
+        }
+        if(subcommand == "init") {
+            c->addSpellcasting();
+        } else {
+            auto sc = c->getSpellcasting();
+            if(! sc) {
+                throw std::runtime_error("Creature " + c->getName() + " has no spellcasting");
+            }
+            text << "Added spellcasting to " << c->getName() << std::endl;
+            if(subcommand == "ability") {
+                if(args.size() != 3) {
+                    throw std::runtime_error("Subcommand \"spellcasting ability\" requires an additional parameter, but none was given");
+                }
+                sc->setAbility(args[2]);
+                text << "Set " << c->getName() << " spellcasting ability to " << args[2] << std::endl;
+            } else { // subcommand == "level"
+                if(args.size() != 4) {
+                    throw std::runtime_error("Subcommand \"spellcasting level\" requires more parameters");
+                }
+                int level = utils::parseInt(args[2]);
+                int slots = utils::parseInt(args[3]);
+                if(level <= 0 || slots < 0) {
+                    throw std::runtime_error("Spellcasting target out of range");
+                }
+                while(sc->getSlotLevels().size() <= (std::size_t) level) {
+                    sc->addSlotLevel();
+                }
+                sc->getSlotLevels()[level]->numSlots = slots;
+                text << "Gave " << c->getName() << " " << slots << " " << utils::toOrdinal(level) << " level spell slots" << std::endl;
+            }
+        }
+        utils::saveJson(*c, p);
+        return text.str();
+    }
 }
diff --git a/src/cmd_usage.cc b/src/cmd_usage.cc
index 03dae56..53e9d01 100644
--- a/src/cmd_usage.cc
+++ b/src/cmd_usage.cc
@@ -40,6 +40,11 @@ namespace cmd {
         text << indDesc << "Add entry to creature, where entry is an item or spell." << std::endl;
         text << indOpt << "del path entry" << std::endl;
         text << indDesc << "Delete entry from creature, where entry is an item or spell." << std::endl;
+        text << indOpt << "spellcasting path SUBCOMMAND" << std::endl;
+        text << indDesc << "Manipulate creature's spellcasting feature, where subcommand is any of:" << std::endl;
+        text << indDesc << "  init; adds spellcasting to a creature which currently does not have spellcasting" << std::endl;
+        text << indDesc << "  ability value; sets the spellcasting ability, where value is the ability name" << std::endl;
+        text << indDesc << "  level l slots; sets the number of slots at spell level l to slots" << std::endl;
         text << indOpt << "help" << std::endl;
         text << indDesc << "Show this help." << std::endl;
         return text.str();
diff --git a/src/creature.cc b/src/creature.cc
index 9366bac..7060c10 100644
--- a/src/creature.cc
+++ b/src/creature.cc
@@ -80,6 +80,14 @@ namespace creature {
         inventory.push_back(item);
     }
 
+    void Creature::addSpellcasting() {
+        if(getSpellcasting()) {
+            throw runtime_error("Creature " + getName() + " already has spellcasting");
+        }
+        std::shared_ptr<entry::Feature> sc(new entry::Spellcasting());
+        features.push_back(sc);
+    }
+
     shared_ptr<entry::Spellcasting> Creature::getSpellcasting() const {
         for(auto f : getFeatures()) {
             if(f->getType() == "spells") {
diff --git a/src/creature.h b/src/creature.h
index c6697ce..b2a01e6 100644
--- a/src/creature.h
+++ b/src/creature.h
@@ -85,6 +85,7 @@ namespace creature {
             void setProfLevel(const rules::Skill& skill, int level);
             void setProficiency(int p) {prof = p;}
             void addInventoryItem(std::shared_ptr<entry::Item> item);
+            void addSpellcasting(void);
             void addSpell(std::shared_ptr<entry::Spell> spell);
             void removeSpell(std::shared_ptr<entry::Spell> spell);
             void removeInventoryItem(std::shared_ptr<entry::Item> item);
diff --git a/src/dmtool.cc b/src/dmtool.cc
index 3c5d3a1..f4fe19c 100644
--- a/src/dmtool.cc
+++ b/src/dmtool.cc
@@ -11,6 +11,7 @@ const std::map<std::string, std::vector<int>> nargs({
         {"attacks", {1}},
         {"damage", {2, 3}},
         {"heal", {2}},
+        {"spellcasting", {2, 3, 4}},
         });
 
 void checkArgs(std::string cmd, std::vector<std::string> args) {
@@ -69,6 +70,7 @@ int main(int argc, char *argv[]) {
         else if(cmd == "add") std::cout << cmd::add(args);
         else if(cmd == "del") std::cout << cmd::del(args);
         else if(cmd == "edit") std::cout << cmd::edit(args);
+        else if(cmd == "spellcasting") std::cout << cmd::spellcasting(args);
         else if(cmd == "help") std::cout << cmd::usage(exename);
         else std::cout << cmd::list(argsOrig);
     } catch(std::exception& e) {
diff --git a/src/entry.h b/src/entry.h
index 56fc884..9e00798 100644
--- a/src/entry.h
+++ b/src/entry.h
@@ -10,6 +10,9 @@ namespace creature {
 namespace entry {
     class Entry {
         public:
+            Entry() {}
+            // Also can be created programmatically
+            Entry(const std::string& entry, const std::string& name, const std::string& type, const std::string& text) : entry(entry), name(name), type(type), text(text) {}
             static std::shared_ptr<Entry> create(const nlohmann::json& data);
             virtual ~Entry() {}
 
diff --git a/src/feature.h b/src/feature.h
index 294cf1d..e3c8bdf 100644
--- a/src/feature.h
+++ b/src/feature.h
@@ -6,6 +6,8 @@
 namespace entry {
     class Feature : public Entry {
         public:
+            Feature() {}
+            Feature(const std::string& name, const std::string& type, const std::string& text) : Entry("feature", name, type, text) {}
             static std::shared_ptr<Feature> create(const nlohmann::json& data);
             virtual ~Feature() {}
     };
diff --git a/src/spellcasting.h b/src/spellcasting.h
index f5cebcb..aa3b3e8 100644
--- a/src/spellcasting.h
+++ b/src/spellcasting.h
@@ -28,8 +28,10 @@ namespace entry {
 
     class Spellcasting : public Feature {
         public:
+            Spellcasting() : Feature("spellcasting", "spells", ""), innate(false), spellcasting_ability("int") {}
             bool isInnate(void) const {return innate;}
             rules::Ability getAbility(void) const {return spellcasting_ability;}
+            void setAbility(const rules::Ability& ability) {spellcasting_ability = ability;}
             const std::vector<std::shared_ptr<SlotLevel>>& getSlotLevels(void) const {return levels;}
             void addSlotLevel(void) {levels.push_back(std::shared_ptr<SlotLevel>(new SlotLevel()));}
             std::vector<std::shared_ptr<Spell>> getSpells(void) const;
-- 
cgit v1.2.3