From d0e356d09e30a11c1e072415a5088f829d5c0a04 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 16 Jan 2022 21:32:01 -0500 Subject: Worked on features --- Makefile | 94 +++++++----- configure | 7 +- features/aberrant_ground.json | 30 +++- features/acid_absorption.json | 14 ++ features/adhesive.json | 18 +++ features/readme.md | 170 ++++++++++++++++++--- featuresNotes.txt | 3 +- propertiesNotes.txt | 6 +- src/attack.h | 2 +- src/cmd.cc | 54 ------- src/cmd.h | 47 ------ src/cmd/cmd.cc | 54 +++++++ src/cmd/cmd.h | 47 ++++++ src/cmd/cmd_fsops.cc | 87 +++++++++++ src/cmd/cmd_manipulate.cc | 348 ++++++++++++++++++++++++++++++++++++++++++ src/cmd/cmd_query.cc | 48 ++++++ src/cmd/cmd_usage.cc | 60 ++++++++ src/cmd_fsops.cc | 87 ----------- src/cmd_manipulate.cc | 348 ------------------------------------------ src/cmd_query.cc | 48 ------ src/cmd_usage.cc | 60 -------- src/creature.cc | 1 - src/creature.h | 2 +- src/dmtool.cc | 2 +- src/entry.cc | 2 +- src/feature.cc | 19 --- src/feature.h | 14 -- src/features/feature.cc | 19 +++ src/features/feature.h | 14 ++ src/rules.cc | 7 + src/rules.h | 44 ++++-- src/spellcasting.h | 2 +- src/utils.h | 2 +- 33 files changed, 995 insertions(+), 765 deletions(-) create mode 100644 features/acid_absorption.json create mode 100644 features/adhesive.json delete mode 100644 src/cmd.cc delete mode 100644 src/cmd.h create mode 100644 src/cmd/cmd.cc create mode 100644 src/cmd/cmd.h create mode 100644 src/cmd/cmd_fsops.cc create mode 100644 src/cmd/cmd_manipulate.cc create mode 100644 src/cmd/cmd_query.cc create mode 100644 src/cmd/cmd_usage.cc delete mode 100644 src/cmd_fsops.cc delete mode 100644 src/cmd_manipulate.cc delete mode 100644 src/cmd_query.cc delete mode 100644 src/cmd_usage.cc delete mode 100644 src/feature.cc delete mode 100644 src/feature.h create mode 100644 src/features/feature.cc create mode 100644 src/features/feature.h diff --git a/Makefile b/Makefile index 4196791..401c43b 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ CC=g++ LIBS=libconfuse nlohmann_json CFLAGS=-c -Wall -fPIC -std=c++20 LDFLAGS= -SOURCES=src/armor.cc src/cmd.cc src/cmd_fsops.cc src/cmd_manipulate.cc src/cmd_query.cc src/cmd_usage.cc src/creature.cc src/dice.cc src/dmtool.cc src/entry.cc src/feature.cc src/item.cc src/rules.cc src/settings.cc src/spellcasting.cc src/spell.cc src/utils.cc src/weapon.cc +SOURCES=src/dmtool.cc src/item.cc src/creature.cc src/spellcasting.cc src/weapon.cc src/dice.cc src/settings.cc src/spell.cc src/features/feature.cc src/utils.cc src/rules.cc src/armor.cc src/cmd/cmd.cc src/cmd/cmd_usage.cc src/cmd/cmd_query.cc src/cmd/cmd_fsops.cc src/cmd/cmd_manipulate.cc src/entry.cc OBJECTS=$(SOURCES:.cc=.o) LIBRARY= EXECUTABLE=dmtool @@ -28,79 +28,93 @@ $(EXECUTABLE): $(OBJECTS) $(CC) $(OBJECTS) -o $@ $(LDFLAGS) `pkg-config $(LIBS) --libs` -src/armor.o: src/armor.cc src/armor.h src/item.h src/entry.h \ - src/defines.h src/creature.h src/rules.h src/utils.h src/feature.h +src/dmtool.o: src/dmtool.cc src/cmd/cmd.h src/cmd/../rules.h \ + src/cmd/../utils.h src/cmd/../entry.h src/cmd/../defines.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/cmd.o: src/cmd.cc src/cmd.h src/settings.h +src/item.o: src/item.cc src/item.h src/entry.h src/defines.h src/weapon.h \ + src/rules.h src/utils.h src/armor.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/cmd_fsops.o: src/cmd_fsops.cc src/cmd.h src/utils.h src/entry.h \ - src/defines.h +src/creature.o: src/creature.cc src/creature.h src/rules.h src/utils.h \ + src/entry.h src/defines.h src/features/feature.h src/item.h src/dice.h \ + src/weapon.h src/armor.h src/attack.h src/spellcasting.h src/spell.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/cmd_manipulate.o: src/cmd_manipulate.cc src/cmd.h src/utils.h \ - src/entry.h src/defines.h src/creature.h src/rules.h src/feature.h \ - src/item.h src/spellcasting.h src/spell.h src/settings.h src/weapon.h +src/spellcasting.o: src/spellcasting.cc src/spellcasting.h \ + src/features/feature.h src/features/../entry.h src/features/../defines.h \ + src/spell.h src/rules.h src/utils.h src/defines.h src/creature.h \ + src/item.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/cmd_query.o: src/cmd_query.cc src/cmd.h src/utils.h src/entry.h \ - src/defines.h src/creature.h src/rules.h src/feature.h src/item.h \ - src/dice.h src/weapon.h +src/weapon.o: src/weapon.cc src/weapon.h src/item.h src/entry.h \ + src/defines.h src/rules.h src/utils.h src/creature.h \ + src/features/feature.h src/dice.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/cmd_usage.o: src/cmd_usage.cc src/cmd.h +src/dice.o: src/dice.cc src/dice.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/creature.o: src/creature.cc src/creature.h src/rules.h src/utils.h \ - src/entry.h src/defines.h src/feature.h src/item.h src/dice.h \ - src/weapon.h src/armor.h src/attack.h src/spellcasting.h src/spell.h +src/settings.o: src/settings.cc src/settings.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/dice.o: src/dice.cc src/dice.h +src/spell.o: src/spell.cc src/spell.h src/entry.h src/defines.h \ + src/utils.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/dmtool.o: src/dmtool.cc src/cmd.h src/utils.h src/entry.h \ - src/defines.h +src/features/feature.o: src/features/feature.cc src/features/feature.h \ + src/features/../entry.h src/features/../defines.h \ + src/features/../spellcasting.h src/features/../spell.h \ + src/features/../rules.h src/features/../utils.h src/features/../attack.h \ + src/features/../weapon.h src/features/../item.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/entry.o: src/entry.cc src/entry.h src/defines.h src/utils.h \ - src/feature.h src/item.h src/spell.h src/creature.h src/rules.h +src/utils.o: src/utils.cc src/utils.h src/entry.h src/defines.h \ + src/settings.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/feature.o: src/feature.cc src/feature.h src/entry.h src/defines.h \ - src/spellcasting.h src/spell.h src/rules.h src/utils.h src/attack.h \ - src/weapon.h src/item.h +src/rules.o: src/rules.cc src/rules.h src/utils.h src/entry.h \ + src/defines.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/item.o: src/item.cc src/item.h src/entry.h src/defines.h src/weapon.h \ - src/rules.h src/utils.h src/armor.h +src/armor.o: src/armor.cc src/armor.h src/item.h src/entry.h \ + src/defines.h src/creature.h src/rules.h src/utils.h \ + src/features/feature.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/rules.o: src/rules.cc src/rules.h src/utils.h src/entry.h \ - src/defines.h +src/cmd/cmd.o: src/cmd/cmd.cc src/cmd/cmd.h src/cmd/../rules.h \ + src/cmd/../utils.h src/cmd/../entry.h src/cmd/../defines.h \ + src/cmd/../settings.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/settings.o: src/settings.cc src/settings.h +src/cmd/cmd_usage.o: src/cmd/cmd_usage.cc src/cmd/cmd.h \ + src/cmd/../rules.h src/cmd/../utils.h src/cmd/../entry.h \ + src/cmd/../defines.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/spellcasting.o: src/spellcasting.cc src/spellcasting.h src/feature.h \ - src/entry.h src/defines.h src/spell.h src/rules.h src/utils.h \ - src/creature.h src/item.h +src/cmd/cmd_query.o: src/cmd/cmd_query.cc src/cmd/cmd.h \ + src/cmd/../rules.h src/cmd/../utils.h src/cmd/../entry.h \ + src/cmd/../defines.h src/cmd/../creature.h src/cmd/../features/feature.h \ + src/cmd/../item.h src/cmd/../dice.h src/cmd/../weapon.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/spell.o: src/spell.cc src/spell.h src/entry.h src/defines.h \ - src/utils.h +src/cmd/cmd_fsops.o: src/cmd/cmd_fsops.cc src/cmd/cmd.h \ + src/cmd/../rules.h src/cmd/../utils.h src/cmd/../entry.h \ + src/cmd/../defines.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/utils.o: src/utils.cc src/utils.h src/entry.h src/defines.h \ - src/settings.h +src/cmd/cmd_manipulate.o: src/cmd/cmd_manipulate.cc src/cmd/cmd.h \ + src/cmd/../rules.h src/cmd/../utils.h src/cmd/../entry.h \ + src/cmd/../defines.h src/cmd/../creature.h src/cmd/../features/feature.h \ + src/cmd/../item.h src/cmd/../spellcasting.h src/cmd/../spell.h \ + src/cmd/../settings.h src/cmd/../weapon.h src/cmd/../dice.h \ + src/cmd/../armor.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` -src/weapon.o: src/weapon.cc src/weapon.h src/item.h src/entry.h \ - src/defines.h src/rules.h src/utils.h src/creature.h src/feature.h \ - src/dice.h +src/entry.o: src/entry.cc src/entry.h src/defines.h src/utils.h \ + src/features/feature.h src/item.h src/spell.h src/creature.h src/rules.h $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` clean: - rm -f src/*.o $(LIBRARY) $(EXECUTABLE) + rm -f $(LIBRARY) $(EXECUTABLE) + find . | grep .o$ | xargs rm -f diff --git a/configure b/configure index 179ee83..b6488b4 100755 --- a/configure +++ b/configure @@ -34,7 +34,7 @@ install: \$(EXECUTABLE) parsed # Below here shouldn't need editing -SOURCES=`ls $SOURCE_DIR/*.cc | tr '\n' ' '` +SOURCES=`find $SOURCE_DIR | grep .cc$ | tr '\n' ' '` PKG_CONFIG_CFLAGS= PKG_CONFIG_LIBS= @@ -63,7 +63,7 @@ if [ -n "$EXECUTABLE" ]; then ALL="$ALL \$(EXECUTABLE)" fi -ORULES=$(for cc in `ls $SOURCE_DIR/*.cc`; do g++ -MM -MT `cut -d'.' -f-1 <<< $cc`.o $cc; echo -e "\t"'$(CC) $(CFLAGS) $< -o $@ '"$PKG_CONFIG_CFLAGS\n"; done) +ORULES=$(for cc in `find $SOURCE_DIR | grep .cc$`; do g++ -MM -MT `cut -d'.' -f-1 <<< $cc`.o $cc; echo -e "\t"'$(CC) $(CFLAGS) $< -o $@ '"$PKG_CONFIG_CFLAGS\n"; done) cat << EOF > Makefile CC=g++ @@ -84,5 +84,6 @@ $EXTRAS$INSTALL$LIBRULE$EXERULE $ORULES clean: - rm -f $SOURCE_DIR/*.o \$(LIBRARY) \$(EXECUTABLE) + rm -f \$(LIBRARY) \$(EXECUTABLE) + find . | grep .o\$ | xargs rm -f EOF diff --git a/features/aberrant_ground.json b/features/aberrant_ground.json index a7f99de..4e72225 100644 --- a/features/aberrant_ground.json +++ b/features/aberrant_ground.json @@ -1,3 +1,27 @@ -[ - -] +{ + "name": "aberrant ground", + "text": "The ground in a 10-foot radius around the {NAME} is doughlike difficult terrain. Each creature that starts its turn in that area must succeed on a DC {(8,PROF,STR,+,+)} Strength saving throw or have its speed reduced to 0 until the start of its next turn.", + "subfeatures": [ + { + "trigger": [ "N end of turn", "self" ], + "conditions": [], + "effects": [{ + "target": "self", + "effect": [ "Terrain in X ft radius is difficult", 10 ], + "duration": "Start of next turn" + }] + }, + { + "trigger": [ "N start of turn", "creature" ], + "conditions": [ + [ "N is within radius X of N", "creature", 10, "self" ], + [ "N succeeds dc X ABILITY save by at least X", "creature", "(8,PROF,STR,+,+)", "STR", 0 ] + ], + "effects": [{ + "target": "creature", + "effect": [ "Set movement speed to X", 0 ], + "duration": "Start of next turn" + }] + } + ] +} diff --git a/features/acid_absorption.json b/features/acid_absorption.json new file mode 100644 index 0000000..52eb2b7 --- /dev/null +++ b/features/acid_absorption.json @@ -0,0 +1,14 @@ +{ + "name": "acid absorption", + "text": "Whenever the {NAME} is subjected to acid damage, it takes no damage and instead regains a number of hit points equal to the acid damage dealt.", + "subfeatures": [ + { + "trigger": [ "N takes damage", "self" ], + "conditions": [[ "Damage received was type TYPE", "acid" ]], + "effects": [{ + "target": "self", + "effect": [ "Regain X hp", "V0" ] + }] + } + ] +} diff --git a/features/adhesive.json b/features/adhesive.json new file mode 100644 index 0000000..19c39e9 --- /dev/null +++ b/features/adhesive.json @@ -0,0 +1,18 @@ +{ + "name": "adhesive", + "text": "(Object form only) The {NAME} adheres to anything that touches it. A Huge or smaller creature adhered to the {NAME} is also grappled by it (escape DC {(8,STR,PROF,+,+)}). Ability checks made to escape this grapple have disadvantage.", + "subfeatures": [ + { + "trigger": [ "N touches N", "creature", "self" ], + "conditions": [ + [ "N has property PROPERTY", "self", "Form", "Object" ], + [ "not N has property Property", "other", "Size", "Gargantuan" ] + ] + "effects": [ + "target": "creature", + "effect": [ "Acquire condition CONDITION", "grappled" ] + ] + } + ] + +} diff --git a/features/readme.md b/features/readme.md index 6c90a31..ebc16cc 100644 --- a/features/readme.md +++ b/features/readme.md @@ -2,9 +2,32 @@ Many features are complex. Here we lay out the format used when writing a feature. +The overall structure of a feature is a dictionary as follows: + + * "name": NAME + * "text": TEXT + * "subfeatures": SUBFEATURES + +In addition to the hardcoded data provided in the feature, certain variables may be given at runtime. + ## Variables -Many features are very similar between creatures except for certain variables. The feature reads, as parameters, NAME as the creature's name, and V1, V2, ... VN for each numeric variable that is a parameter to the feature. +Many features are very similar between creatures except for certain variables. When instantiated programatically, the feature is given the following: + + * NAME: The name of the creature with this feature + * V0, V1, ... VN: Zero-indexed list of integer variables + +Certain portions of features accept these as parameters, either inline as in TEXT, or in the place of hardcoded variables for conditions or effects. + +In addition to these, certain attributes of the creature having this feature may be probed such as STR, DEX, CON, INT, WIS, CHA, or PROF. + +Finally, simple math can be provided in postfix notation surounded by parentheses. For example, to set a save DC equal to 8+int+prof: (8,INT,PROF,+,+). + +## TEXT + +This is explanatory text. It accepts embedded variables in the form {VARNAME}. For example, the text "The {NAME}'s weapon attacks are magical. When the {NAME} hits with any weapon, the weapon deals an extra {V0}d{V1} radiant damage.", when applied to a "deva" with runtime variables (4, 8), we get: + +"The deva's weapon attacks are magical. When the deva hits with any weapon, the weapon deals an extra 4d8 radiant damage." ## Subfeatures @@ -12,30 +35,16 @@ Each feature consists of a list of "subfeatures", which are treated as independe Each subfeature is a dictionary with the following elements: - * "name": NAME - * "text": TEXT - * "nouns": NOUNS * "trigger": TRIGGER * "conditions": CONDITIONS * "effects": EFFECTS - * "duration": DURATION * "recharge": RECHARGE -We detail the values below. - -### NAME - -This is the name of the feature - -### TEXT +Several of the elements accept various nouns. -This is explanatory text. It accepts embedded variables in the form {VARNAME}. For example, the text "The {NAME}'s weapon attacks are magical. When the {NAME} hits with any weapon, the weapon deals an extra {V1}d{V2} radiant damage.", when applied with (deva, 4, 8), we get: +## NOUNS -"The deva's weapon attacks are magical. When the deva hits with any weapon, the weapon deals an extra 4d8 radiant damage." - -### NOUNS - -The nouns involved are any entities this feature affects. NOUNS is a list containing any of: +Nouns embedded into subfeature elements may be any of: * "self" * "creature" @@ -45,18 +54,19 @@ The nouns involved are any entities this feature affects. NOUNS is a list contai * "point in space" * NAME -Where NAME is interpreted by the game as a specific creature or object. These are N1, N2, etc. for all N in the array. Index starts at 1. +Where NAME is interpreted by the game as a specific creature or object with a matching name. ### TRIGGER -TRIGGER is any of the following: +TRIGGER is a list containing any of the following followed by nouns corresponding to instances of N: - * "passive" + * "on init" * "action" * "bonus action" * "free action" - * "{} start of turn" - * "N takes damage" + * "N start of turn" + * "N end of turn" + * "N takes damage" (pushes damage amount to variables list) * "N moves" * "N makes check against N" * "N forces N to save" @@ -65,3 +75,117 @@ TRIGGER is any of the following: * "N attacks N" * "N hits N" * "N touches N" + +### CONDITIONS + +CONDITIONS is a list of lists, all of which must evaluate True for effects to be applied. Each condition list is formatted [ CONDITION, variables ] where CONDITION is any of the following, prepend "not " to invert: + + * "N has property PROPERTY" + * "N is hostile to N" + * "N is allied to N" + * "N wishes it to be so" + * "N succeeds dc X ABILITY save by at least X" + * "N fails dc X ABILITY save by at least X" + * "N is within radius X of N" + * "N can see N" + * "N has completed >= X turns" + * "N is at full hp" + * "N has >= X hp" + * "Damage received >= X" + * "Damage received was a critical hit" + * "Damage received was type TYPE" + * "N has condition CONDITION" + * "N has condition CONDITION given by N" + * "N and N are on same plane of existence" + * "N is on plane PLANE" + * "N moved X distance straight toward N" + * "N has X movement remaining" + * "N made attack ATTACK" + * "N has underside exposed" + * "First time N uses feature on N during turn" + * "N is flying" + * "N is under effects of spell SPELL" + * "N has spell from school SCHOOL cast on it" + * "N is on terrain TERRAIN" + * "N is inside an object" + * "N is in lighting LIGHTING (sunlight, dim light, darkness, magical darkness)" + * "N can understand >= X languages" + * "N and N share a language" + * "N has >= X heads" + * "N has X spell slots remaining" + * "N had advantage on most recent die roll" + * "N had disadvantage on most recent die roll" + * "N used a weapon on most recent attack" + * "N is surprised" + * "N is bound by N" + * "N is aware of N" + * "N and N are in the same web" + +### EFFECTS + +EFFECTS is a list of dictionaries containing the following: + + * "target": NOUN + * "effect": [EFFECT, variables] + * "duration": DURATION + +Where EFFECT is any of the following (UNDER CONSTRUCTION DENOTED BY x): + + * Terrain in X ft radius is difficult + * Set movement speed to X +x * Increase movement speed by X +x * Increase jump distance by X +x * Increase jump height by X + * Acquire condition CONDITION +x * End condition CONDITION +x * Acquire/override property PROPERTY +x * Death +x * Surprised +x * Pushed X feet in DIRECTION direction +x * Immune to feature FEATURE +x * Immune to spells with trait SPELL\_TRAIT +x * Disadvantage on rolls ROLL\_CLASS +x * Advantage on rolls ROLL\_CLASS +x * Automatic fail on rolls ROLL\_CLASS +x * Automatic succeed on rolls ROLL\_CLASS +x * Automatic critical hit +x * Move X distance +x * Deal X damage +x * Disable other feature +x * Know distance/direction to creature/object within X distance +x * Reduce incoming damage by X + * Regain X hp +x * Weapon/armor corrodes +x * Ignite flammable objects +x * Expend spell slot +x * Special movement +x * Plane shift +x * Grant spell effect +x * Transparency (perpetual hidenness?) +x * Prescribed actions (randomized), any of: +x - Nothing +x - Moves in random direction +x - Attacks random creature within reach +x * Telepathic command +x * Learns creature's desires +x * Spell storing +x * Creates X diameter tunnel + +The duration of an effect may be omitted for instantaneous effects and is any of the following: + + * "End of turn" + * "Start of next turn" + * "End of next turn" + * Until action is taken to end +x * TIME??? +x * Repeated saves??? +x * Repeated saves at advantage/disadvantage with condition??? +x * Until certain spell is cast??? +x * Until other effect expires??? + +### RECHARGE + +The recharge of a feature may be omitted to indicate that it is unlimited. Otherwise, it is a list of the following two items: + + * Integer indicating number of times the feature can be used + * Any of "turn", "long rest", "short rest" indicating when spent uses are restored diff --git a/featuresNotes.txt b/featuresNotes.txt index 733d356..2424e81 100644 --- a/featuresNotes.txt +++ b/featuresNotes.txt @@ -30,7 +30,8 @@ Conditions on some number of Nouns (N) (can be negated): * N is hostile to N * N is allied to N * N wishes it to be so - * N fails/succeeds roll by at least X (0 is straight fail/success) + * N succeeds roll by at least X + * N fails roll by at least X * N is within radius of N * N can see N * N in antimagic field diff --git a/propertiesNotes.txt b/propertiesNotes.txt index fcc083a..ad1fca8 100644 --- a/propertiesNotes.txt +++ b/propertiesNotes.txt @@ -14,7 +14,7 @@ Other properties: * Weapons are magical (default=False) * Weapons deal +X damage (default=0) * Weapons deal +XdY damage (default=0,0) - * Weapons deal +Xd{WEAPON_DIE_SIZE} damage (default=0) + * Weapons deal +X weapon damage die damage (default=0) * Carrying capacity is X (default=calc based on size) * Berserk (default=False) * Is telepathic (default=False) @@ -31,6 +31,7 @@ Other properties: * Immune to spell tag TAG * Does not provoke opportunity attacks (default=False) * Ignores difficult terrain (default=False) + * Can pass through creatures/objects as difficult terrain (default=False) * Ignores webs (default=False) * Reactions/round (default=1) * Sheds bright light radius X and dim light radius Y (default=0,0) @@ -41,5 +42,8 @@ Other properties: * Can spider climb (default=False) * Is hot (water boils/evaporates) (default=False) * Form (default=True Form) + * Size (default=read from statblock) * Can smell (default=True) * Alignment (default=NN) + * Is in antimagic field (default=False) + * Is alive (default=True) diff --git a/src/attack.h b/src/attack.h index 2e4125b..e00eb25 100644 --- a/src/attack.h +++ b/src/attack.h @@ -1,5 +1,5 @@ #pragma once -#include "feature.h" +#include "features/feature.h" #include "weapon.h" namespace creature { diff --git a/src/cmd.cc b/src/cmd.cc deleted file mode 100644 index d84c55b..0000000 --- a/src/cmd.cc +++ /dev/null @@ -1,54 +0,0 @@ -#include "cmd.h" -#include "settings.h" -#include -#include -#include -#include - -namespace cmd { - std::vector getVirtDirs() { - return {"weapons", "armor", "spells", "creatures"}; - } - - // Not idempotent: only do once! - std::filesystem::path getTruePath(std::filesystem::path virtPath) { - std::filesystem::path p; - auto virtPaths = getVirtDirs(); - if(std::find(virtPaths.begin(), virtPaths.end(), *virtPath.begin()) != virtPaths.end()) { - p = settings::getString(*virtPath.begin()); - // Erase root (part to be replaced by virtPaths) - std::filesystem::path tmp; - auto it = virtPath.begin(); - while(++it != virtPath.end()) { - tmp /= *it; - } - virtPath = tmp; - } else { - p = settings::getString("savedir"); - } - p /= virtPath; - if(std::filesystem::directory_entry(p.string() + ".json").is_regular_file()) return p.string() + ".json"; - return p; - } - - std::string formatRoll(std::string name, std::string type, int rolled, int bonus) { - std::stringstream text; - text << name << " " << type << ": " << rolled << " (d20) + " << bonus << " (" << name << " " << type << " bonus) = " << rolled + bonus << std::endl; - return text.str(); - } - - std::vector parseQualifiers(std::map flags) { - std::vector qualifiers; - for(auto flagPair : flags) { - auto flag = flagPair.first; - if(flag == "m" || flag == "magical") { - qualifiers.push_back(rules::Qualifier::Magical()); - } else if(flag == "s" || flag == "silvered") { - qualifiers.push_back(rules::Qualifier::Silvered()); - } else if(flag == "a" || flag == "adamantine") { - qualifiers.push_back(rules::Qualifier::Adamantine()); - } - } - return qualifiers; - } -} diff --git a/src/cmd.h b/src/cmd.h deleted file mode 100644 index 978f6db..0000000 --- a/src/cmd.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include "rules.h" - -namespace cmd { - // Corresponds to commands - - // Usage gets a category of its own - std::string usage(const std::string& exename); - - // Filesystem operations - std::string list(std::vector args); - std::string mkdir(std::vector args); - std::string cp(std::vector args); - std::string mv(std::vector args); - std::string rm(std::vector args); - - // Manipulators - std::string heal(std::vector args); - std::string damage(std::vector args, std::map flags); - std::string attack(std::vector args, std::map flags); - std::string save(std::vector args, std::map flags); - std::string reset(std::vector args); - std::string set(std::vector args); - std::string add(std::vector args); - std::string del(std::vector args); - std::string edit(std::vector args); - std::string spellcasting(std::vector args); - std::string git(std::vector args); - - //Queries - std::string attacks(std::vector args); - std::string roll(std::vector args); - - // Command-centric helpers - - // Not idempotent: only do once! - std::filesystem::path getTruePath(std::filesystem::path virtPath); - std::vector getVirtDirs(void); - - // Helper functions - std::string formatRoll(std::string name, std::string type, int rolled, int bonus); - std::vector parseQualifiers(std::map flags); -} diff --git a/src/cmd/cmd.cc b/src/cmd/cmd.cc new file mode 100644 index 0000000..a1fd760 --- /dev/null +++ b/src/cmd/cmd.cc @@ -0,0 +1,54 @@ +#include "cmd.h" +#include "../settings.h" +#include +#include +#include +#include + +namespace cmd { + std::vector getVirtDirs() { + return {"weapons", "armor", "spells", "creatures"}; + } + + // Not idempotent: only do once! + std::filesystem::path getTruePath(std::filesystem::path virtPath) { + std::filesystem::path p; + auto virtPaths = getVirtDirs(); + if(std::find(virtPaths.begin(), virtPaths.end(), *virtPath.begin()) != virtPaths.end()) { + p = settings::getString(*virtPath.begin()); + // Erase root (part to be replaced by virtPaths) + std::filesystem::path tmp; + auto it = virtPath.begin(); + while(++it != virtPath.end()) { + tmp /= *it; + } + virtPath = tmp; + } else { + p = settings::getString("savedir"); + } + p /= virtPath; + if(std::filesystem::directory_entry(p.string() + ".json").is_regular_file()) return p.string() + ".json"; + return p; + } + + std::string formatRoll(std::string name, std::string type, int rolled, int bonus) { + std::stringstream text; + text << name << " " << type << ": " << rolled << " (d20) + " << bonus << " (" << name << " " << type << " bonus) = " << rolled + bonus << std::endl; + return text.str(); + } + + std::vector parseQualifiers(std::map flags) { + std::vector qualifiers; + for(auto flagPair : flags) { + auto flag = flagPair.first; + if(flag == "m" || flag == "magical") { + qualifiers.push_back(rules::Qualifier::Magical()); + } else if(flag == "s" || flag == "silvered") { + qualifiers.push_back(rules::Qualifier::Silvered()); + } else if(flag == "a" || flag == "adamantine") { + qualifiers.push_back(rules::Qualifier::Adamantine()); + } + } + return qualifiers; + } +} diff --git a/src/cmd/cmd.h b/src/cmd/cmd.h new file mode 100644 index 0000000..ff96b32 --- /dev/null +++ b/src/cmd/cmd.h @@ -0,0 +1,47 @@ +#pragma once +#include +#include +#include +#include +#include "../rules.h" + +namespace cmd { + // Corresponds to commands + + // Usage gets a category of its own + std::string usage(const std::string& exename); + + // Filesystem operations + std::string list(std::vector args); + std::string mkdir(std::vector args); + std::string cp(std::vector args); + std::string mv(std::vector args); + std::string rm(std::vector args); + + // Manipulators + std::string heal(std::vector args); + std::string damage(std::vector args, std::map flags); + std::string attack(std::vector args, std::map flags); + std::string save(std::vector args, std::map flags); + std::string reset(std::vector args); + std::string set(std::vector args); + std::string add(std::vector args); + std::string del(std::vector args); + std::string edit(std::vector args); + std::string spellcasting(std::vector args); + std::string git(std::vector args); + + //Queries + std::string attacks(std::vector args); + std::string roll(std::vector args); + + // Command-centric helpers + + // Not idempotent: only do once! + std::filesystem::path getTruePath(std::filesystem::path virtPath); + std::vector getVirtDirs(void); + + // Helper functions + std::string formatRoll(std::string name, std::string type, int rolled, int bonus); + std::vector parseQualifiers(std::map flags); +} diff --git a/src/cmd/cmd_fsops.cc b/src/cmd/cmd_fsops.cc new file mode 100644 index 0000000..e638b96 --- /dev/null +++ b/src/cmd/cmd_fsops.cc @@ -0,0 +1,87 @@ +#include "cmd.h" +#include "../utils.h" +#include "../entry.h" +#include +#include + +namespace fs = std::filesystem; + +namespace cmd { + std::string list(const fs::path& p) { + std::stringstream text; + if(p.empty()) { + // Print read-only dirs + for(std::string name : getVirtDirs()) { + text << name << "/ (read only)" << std::endl; + } + } + fs::path truePath = getTruePath(p); + if(fs::directory_entry(truePath).is_regular_file()) { + text << utils::instantiate(truePath)->getText() << std::endl; + } + else if(fs::directory_entry(truePath).is_directory()) { + for(fs::directory_entry de : fs::directory_iterator(truePath)) { + 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; + } + } + } + } + else { + text << "Unknown path " << p << std::endl; + } + return text.str(); + } + + std::string list(std::vector args) { + std::stringstream text; + if(args.empty()) { + text << list(""); + } else { + for(std::string dir : args) { + text << list(dir); + } + } + return text.str(); + } + + std::string mkdir(std::vector args) { + for(std::string s : args) { + fs::create_directories(getTruePath(s)); + } + return ""; + } + + void cp(fs::path src, fs::path dest) { + if(fs::directory_entry(src).is_regular_file()) { + utils::saveJson(utils::instantiate(src)->serialize(), dest); + } else { + mkdir({dest}); + for(fs::directory_entry de : fs::directory_iterator(src)) { + cp(de.path(), dest / de.path().filename()); + } + } + } + + std::string cp(std::vector args) { + // Operate by intantiating and saving + // We do recursive! + cp(getTruePath(args[0]), getTruePath(args[1])); + return ""; + } + + std::string mv(std::vector args) { + fs::rename(getTruePath(args[0]), getTruePath(args[1])); + return ""; + } + + std::string rm(std::vector args) { + for(std::string s : args) { + fs::remove_all(getTruePath(s)); + } + return ""; + } +} diff --git a/src/cmd/cmd_manipulate.cc b/src/cmd/cmd_manipulate.cc new file mode 100644 index 0000000..18df098 --- /dev/null +++ b/src/cmd/cmd_manipulate.cc @@ -0,0 +1,348 @@ +#include "cmd.h" +#include "../utils.h" +#include "../creature.h" +#include "../item.h" +#include "../spellcasting.h" +#include "../settings.h" +#include "../weapon.h" +#include "../dice.h" +#include "../armor.h" +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace cmd { + // Call after applying to format printing + std::string formatHealingDamage(const std::shared_ptr& c, int initHP, bool heal, int amnt, const std::string& dmgType, const std::vector& qualifiers) { + std::stringstream text; + text << (heal? "Healing " : "Damaging ") << c->getGivenName() << " the " << c->getCreatureName() << " by " << amnt; + if(! heal) { + std::string qualsString; + std::vector positiveQuals; + for(auto qual : qualifiers) { + positiveQuals.push_back(qual.getPositive()); + } + if(! positiveQuals.empty()) { + qualsString = " " + utils::join(positiveQuals, ", "); + } + text << qualsString << " " << dmgType << " damage"; + } + text << ". HP: " << initHP << " -> " << c->getHP() << "." << std::endl; + return text.str(); + } + + std::string healOrDamageProgrammatic(fs::path p, bool heal, int amnt, std::string dmgType, const std::vector& qualifiers) { + auto c = utils::instantiate(p); + int initHP = c->getHP(); + if(heal) { + c->applyHealing(amnt); + } else { + c->applyDamage(amnt, dmgType, qualifiers); + } + utils::saveJson(*c, p); + return formatHealingDamage(c, initHP, heal, amnt, dmgType, qualifiers); + } + + std::string healOrDamage(bool heal, std::vector args, std::map flags) { + auto qualifiers = parseQualifiers(flags); + fs::path p = getTruePath(args[0]); + int amnt = utils::parseInt(args[1]); + std::string dmgType = "force"; + if(args.size() == 3) { + dmgType = args[2]; + } + return healOrDamageProgrammatic(p, heal, amnt, dmgType, qualifiers); + } + + std::string attack(std::vector args, std::map flags) { + std::stringstream text; + bool is2h = flags.find("2") != flags.end(); + bool is1h = flags.find("1") != flags.end(); + if(is2h and is1h) { + text << "ERROR: Cannot be both 1 handed and 2 handed!" << std::endl; + return text.str(); + } + auto c1 = utils::instantiate(getTruePath(args[0])); + args.erase(args.begin()); + fs::path p2 = getTruePath(args.back()); + auto c2 = utils::instantiate(p2); + args.erase(args.end()-1); + std::string attackName = utils::join(args, " "); + utils::lower(attackName); + std::shared_ptr w; + for(auto weap : creature::getAttacks(*c1)) { + if(weap->getName() == attackName) { + w = weap; + break; + } + } + text << w->getText(*c1) << std::endl; + int rolled = dice::roll(20); + int bonus = w->getToHitBonus(*c1); + text << formatRoll(w->getName(), "attack", rolled, bonus); + int ac = creature::getAC(*c2); + if(rolled + bonus >= ac) { + text << " Hit (" << (rolled + bonus) << " to hit >= " << ac << " ac): "; + bool wants2h = true; + for(auto a : utils::castPtrs(c1->getInventory())) { + if(a->getArmorType() == "shield") { + wants2h = false; + } + } + if(is2h) { + wants2h = true; + } else if(is1h) { + wants2h = false; + } + auto dmg = entry::rollDmg(*w, wants2h); + text << entry::formatDmg(*w, *c1, dmg) << std::endl; + for(auto d : dmg) { + text << " " << healOrDamageProgrammatic(p2, false, d.rolled, d.dmg_type, {}); + } + } else { + text << " Miss (" << (rolled + bonus) << " to hit < " << ac << " ac)" << std::endl; + } + return text.str(); + } + + std::string heal(std::vector args) { + return healOrDamage(true, args, {}); + } + + std::string damage(std::vector args, std::map flags) { + return healOrDamage(false, args, flags); + } + + std::string save(std::vector args, std::map flags) { + if(args.size() < 3) { + throw std::runtime_error("Subcommand 'save' requires at least 3 arguments"); + } + std::stringstream text; + rules::Ability ability = rules::tryGetAbilityOrSkill(args[0]); + if(! ability) { + throw std::runtime_error("Requires a valid ability name but received \"" + args[0] + "\"."); + } + args.erase(args.begin()); + int DC = utils::parseInt(args[0]); + args.erase(args.begin()); + // Now iterate over the paths + for(std::string s : args) { + fs::path p = getTruePath(s); + auto c = utils::instantiate(p); + int initHP = c->getHP(); + int rolled = dice::roll(20); + int bonus = c->getAbilitySaveBonus(ability); + int damage = 0; + std::string type = "force"; + bool halves = flags.find("halves") != flags.end(); + if(flags.find("damage") != flags.end()) { + damage = utils::parseInt(flags.at("damage")); + if(flags.find("type") != flags.end()) type = flags.at("type"); + auto qualifiers = parseQualifiers(flags); + rolled = c->saveOrDamage(ability, DC, damage, type, qualifiers, halves); + rolled -= bonus; // It's combined in creature + } + bool passed = rolled + bonus >= DC; + text << c->getName() << " " << (passed? "PASS" : "FAIL") << ": "; + text << formatRoll(ability.getFull(), "save", rolled, bonus); + if(flags.find("damage") != flags.end() and (halves or ! passed)) { + text << formatHealingDamage(c, initHP, false, damage, type, parseQualifiers(flags)); + } + utils::saveJson(*c, p); + } + return text.str(); + } + + std::string reset(std::vector args) { + for(std::string s : args) { + fs::path p = getTruePath(s); + auto c = utils::instantiate(p); + c->longRest(); + utils::saveJson(*c, p); + } + return ""; + } + + std::string 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 = utils::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(utils::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 = rules::tryGetAbilityOrSkill(abilityOrSkill); + rules::Ability ability = rules::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, utils::parseInt(toSet)); + } else { + throw std::runtime_error("Subcommand 'set' expected an ability, skill, proficiency, or name field to set, but was given " + abilityOrSkill); + } + } + utils::saveJson(*c, p); + return ""; + } + + std::string add(std::vector args) { + std::stringstream text; + fs::path p = getTruePath(args[0]); + args.erase(args.begin()); // remove path from args + auto c = utils::instantiate(p); + std::string addName = utils::join(args, " "); + std::shared_ptr ent; + fs::path path = getTruePath(addName); + if(fs::directory_entry(path).exists()) { + ent = utils::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()); + } + } + utils::saveJson(*c, p); + text << "Added the " << ent->getType() << " " << ent->getName() << " to " << c->getGivenName() << " the " << c->getName() << std::endl; + return text.str(); + } + + std::string del(std::vector args) { + std::stringstream text; + fs::path p = getTruePath(args[0]); + args.erase(args.begin()); // remove path from args + auto c = utils::instantiate(p); + //Atempt to load the item if it's a path + std::string itemName = utils::join(args, " "); + try { + auto i = utils::instantiate(getTruePath(itemName)); + if(i) { + itemName = i->getName(); + } + } catch(std::exception& e) {} // eat. + 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; + } + } + } + utils::saveJson(*c, p); + if(removed) { + text << "Successfully removed the " << removed->Entry::getType() << " " << removed->getName() << std::endl; + } else { + text << "Could not find any inventory item nor spell by that name" << std::endl; + } + return text.str(); + } + + std::string edit(std::vector args) { + auto p = getTruePath(args[0]); + auto e = utils::instantiate(p); + auto editor = settings::getString("editor"); + // General workflow: copy notes (text) from e to a temp file, edit it, then copy back. + fs::path tmp("/tmp/dmtool.tmp"); + std::ofstream out(tmp); + out << e->Entry::getText(); + out.close(); + std::system((editor + " " + tmp.string()).c_str()); + std::ifstream in(tmp); + std::string newText(std::istreambuf_iterator{in}, {}); + e->setText(newText); + utils::saveJson(e->serialize(), p); + return ""; + } + + std::string spellcasting(std::vector args) { + std::stringstream text; + auto p = getTruePath(args[0]); + auto c = utils::instantiate(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(); + } + + std::string git(std::vector args) { + std::string root = getTruePath("").string(); + std::system(("cd " + root + " && " + utils::join(args, " ")).c_str()); + return ""; + } +} diff --git a/src/cmd/cmd_query.cc b/src/cmd/cmd_query.cc new file mode 100644 index 0000000..9c2dae6 --- /dev/null +++ b/src/cmd/cmd_query.cc @@ -0,0 +1,48 @@ +#include "cmd.h" +#include "../utils.h" +#include "../creature.h" +#include "../dice.h" +#include "../weapon.h" +#include + +namespace cmd { + std::string attacks(std::vector args) { + std::stringstream text; + auto c = utils::instantiate(getTruePath(args[0])); + for(auto w : creature::getAttacks(*c)) { + text << w->getName() << std::endl; + } + return text.str(); + } + + std::string roll(std::vector args) { + std::stringstream text; + auto c = utils::instantiate(getTruePath(args[0])); + args.erase(args.begin()); + std::string rollName = utils::join(args, " "); + utils::lower(rollName); + int rolled = dice::roll(20); + // Search through skills, saves, and attacks to roll + if(rollName == "init" or rollName == "initiative") { + text << formatRoll("Initiative", "check", rolled, c->getInitiative()); + } + rules::Skill skill = rules::tryGetAbilityOrSkill(rollName); + rules::Ability ability = rules::tryGetAbilityOrSkill(rollName); + if(skill) { + text << formatRoll(skill.getName(), "check", rolled, c->getSkillBonus(skill)); + } else if(ability) { + text << formatRoll(ability.getFull(), "save", rolled, c->getAbilitySaveBonus(ability)); + } else { + for(auto w : creature::getAttacks(*c)) { + if(w->getName() == rollName) { + text << w->getText(*c) << std::endl; + int bonus = w->getToHitBonus(*c); + text << formatRoll(w->getName(), "attack", rolled, bonus); + text << " on hit: " << entry::formatDmg(*w, *c) << std::endl; + break; + } + } + } + return text.str(); + } +} diff --git a/src/cmd/cmd_usage.cc b/src/cmd/cmd_usage.cc new file mode 100644 index 0000000..44daca4 --- /dev/null +++ b/src/cmd/cmd_usage.cc @@ -0,0 +1,60 @@ +#include "cmd.h" +#include +#include + +namespace cmd { + std::string usage(const std::string& exename) { + std::stringstream text; + text << "Usage:" << std::endl; + std::string indOpt = " " + exename + " "; + std::string indDesc = " "; + text << indOpt << "[ls] [subfolder]" << std::endl; + text << indDesc << "List creatures and objects." << std::endl; + text << indOpt << "cp old-path new-path" << std::endl; + text << indDesc << "Copy old-path to new-path, instantiating it." << std::endl; + text << indOpt << "mkdir path" << std::endl; + text << indDesc << "Make a new directory for holding creatures and objects." << std::endl; + text << indOpt << "mv old-path new-path" << std::endl; + text << indDesc << "Move old-path to new-path." << std::endl; + text << indOpt << "rm path" << std::endl; + text << indDesc << "Remove existing creature, object, or directory." << std::endl; + text << indOpt << "attacks path" << std::endl; + text << indDesc << "List attacks available for a creature" << std::endl; + text << indOpt << "roll path name" << std::endl; + text << indDesc << "Roll a skill check, save, or attack." << std::endl; + text << indOpt << "attack path1 name path2 [-1] [-2]" << std::endl; + text << indDesc << "Roll an attack for path1 attacking path2. Handedness defaults to 2h if versatile and not holding a shield, otherwise use -1 and -2." << std::endl; + text << indOpt << "save ability DC paths [--damage DMG] [--type TYPE] [--halves] [--magical,-m] [--silvered,-s] [--adamantine,-a]" << std::endl; + text << indDesc << "Force creatures at paths to make an ability save at DC. Default: report pass/fail." << std::endl; + text << indDesc << " If --damage, failed saves take TYPE damage (default force), passed saves negate." << std::endl; + text << indDesc << " If --halves, passed saves halve damage rather than negate." << std::endl; + text << indOpt << "damage path amount [type] [--magical,-m] [--silvered,-s] [--adamantine,-a]" << std::endl; + text << indDesc << "Damage creature by amount. Type defaults to \"force\"." << std::endl; + text << indOpt << "heal path amount" << std::endl; + text << indDesc << "Heal creature by amount." << std::endl; + text << indOpt << "reset path" << std::endl; + text << indDesc << "Reset creature to full health (as if completing a long rest)." << std::endl; + text << indOpt << "set path field value" << std::endl; + text << indDesc << "Set a field to a new value, where field is any of:" << std::endl; + text << indDesc << " ability (str, dex, con, int, wis, cha); value is new ability score" << std::endl; + text << indDesc << " skill (athletics, \"sleight of hand\", etc.); value is (none|proficient|expert)" << std::endl; + text << indDesc << " proficiency; value is new proficency bonus." << std::endl; + text << indDesc << " name; value is new given name." << std::endl; + text << indOpt << "edit path" << std::endl; + text << indDesc << "Edit notes associated with creature." << std::endl; + text << indOpt << "add path entry" << std::endl; + 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 << "git [command]" << std::endl; + text << indDesc << "Execute a git command within the dmtool folder." << std::endl; + text << indOpt << "help" << std::endl; + text << indDesc << "Show this help." << std::endl; + return text.str(); + } +} diff --git a/src/cmd_fsops.cc b/src/cmd_fsops.cc deleted file mode 100644 index ac4bdef..0000000 --- a/src/cmd_fsops.cc +++ /dev/null @@ -1,87 +0,0 @@ -#include "cmd.h" -#include "utils.h" -#include "entry.h" -#include -#include - -namespace fs = std::filesystem; - -namespace cmd { - std::string list(const fs::path& p) { - std::stringstream text; - if(p.empty()) { - // Print read-only dirs - for(std::string name : getVirtDirs()) { - text << name << "/ (read only)" << std::endl; - } - } - fs::path truePath = getTruePath(p); - if(fs::directory_entry(truePath).is_regular_file()) { - text << utils::instantiate(truePath)->getText() << std::endl; - } - else if(fs::directory_entry(truePath).is_directory()) { - for(fs::directory_entry de : fs::directory_iterator(truePath)) { - 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; - } - } - } - } - else { - text << "Unknown path " << p << std::endl; - } - return text.str(); - } - - std::string list(std::vector args) { - std::stringstream text; - if(args.empty()) { - text << list(""); - } else { - for(std::string dir : args) { - text << list(dir); - } - } - return text.str(); - } - - std::string mkdir(std::vector args) { - for(std::string s : args) { - fs::create_directories(getTruePath(s)); - } - return ""; - } - - void cp(fs::path src, fs::path dest) { - if(fs::directory_entry(src).is_regular_file()) { - utils::saveJson(utils::instantiate(src)->serialize(), dest); - } else { - mkdir({dest}); - for(fs::directory_entry de : fs::directory_iterator(src)) { - cp(de.path(), dest / de.path().filename()); - } - } - } - - std::string cp(std::vector args) { - // Operate by intantiating and saving - // We do recursive! - cp(getTruePath(args[0]), getTruePath(args[1])); - return ""; - } - - std::string mv(std::vector args) { - fs::rename(getTruePath(args[0]), getTruePath(args[1])); - return ""; - } - - std::string rm(std::vector args) { - for(std::string s : args) { - fs::remove_all(getTruePath(s)); - } - return ""; - } -} diff --git a/src/cmd_manipulate.cc b/src/cmd_manipulate.cc deleted file mode 100644 index aa6407d..0000000 --- a/src/cmd_manipulate.cc +++ /dev/null @@ -1,348 +0,0 @@ -#include "cmd.h" -#include "utils.h" -#include "creature.h" -#include "item.h" -#include "spellcasting.h" -#include "settings.h" -#include "weapon.h" -#include "dice.h" -#include "armor.h" -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -namespace cmd { - // Call after applying to format printing - std::string formatHealingDamage(const std::shared_ptr& c, int initHP, bool heal, int amnt, const std::string& dmgType, const std::vector& qualifiers) { - std::stringstream text; - text << (heal? "Healing " : "Damaging ") << c->getGivenName() << " the " << c->getCreatureName() << " by " << amnt; - if(! heal) { - std::string qualsString; - std::vector positiveQuals; - for(auto qual : qualifiers) { - positiveQuals.push_back(qual.getPositive()); - } - if(! positiveQuals.empty()) { - qualsString = " " + utils::join(positiveQuals, ", "); - } - text << qualsString << " " << dmgType << " damage"; - } - text << ". HP: " << initHP << " -> " << c->getHP() << "." << std::endl; - return text.str(); - } - - std::string healOrDamageProgrammatic(fs::path p, bool heal, int amnt, std::string dmgType, const std::vector& qualifiers) { - auto c = utils::instantiate(p); - int initHP = c->getHP(); - if(heal) { - c->applyHealing(amnt); - } else { - c->applyDamage(amnt, dmgType, qualifiers); - } - utils::saveJson(*c, p); - return formatHealingDamage(c, initHP, heal, amnt, dmgType, qualifiers); - } - - std::string healOrDamage(bool heal, std::vector args, std::map flags) { - auto qualifiers = parseQualifiers(flags); - fs::path p = getTruePath(args[0]); - int amnt = utils::parseInt(args[1]); - std::string dmgType = "force"; - if(args.size() == 3) { - dmgType = args[2]; - } - return healOrDamageProgrammatic(p, heal, amnt, dmgType, qualifiers); - } - - std::string attack(std::vector args, std::map flags) { - std::stringstream text; - bool is2h = flags.find("2") != flags.end(); - bool is1h = flags.find("1") != flags.end(); - if(is2h and is1h) { - text << "ERROR: Cannot be both 1 handed and 2 handed!" << std::endl; - return text.str(); - } - auto c1 = utils::instantiate(getTruePath(args[0])); - args.erase(args.begin()); - fs::path p2 = getTruePath(args.back()); - auto c2 = utils::instantiate(p2); - args.erase(args.end()-1); - std::string attackName = utils::join(args, " "); - utils::lower(attackName); - std::shared_ptr w; - for(auto weap : creature::getAttacks(*c1)) { - if(weap->getName() == attackName) { - w = weap; - break; - } - } - text << w->getText(*c1) << std::endl; - int rolled = dice::roll(20); - int bonus = w->getToHitBonus(*c1); - text << formatRoll(w->getName(), "attack", rolled, bonus); - int ac = creature::getAC(*c2); - if(rolled + bonus >= ac) { - text << " Hit (" << (rolled + bonus) << " to hit >= " << ac << " ac): "; - bool wants2h = true; - for(auto a : utils::castPtrs(c1->getInventory())) { - if(a->getArmorType() == "shield") { - wants2h = false; - } - } - if(is2h) { - wants2h = true; - } else if(is1h) { - wants2h = false; - } - auto dmg = entry::rollDmg(*w, wants2h); - text << entry::formatDmg(*w, *c1, dmg) << std::endl; - for(auto d : dmg) { - text << " " << healOrDamageProgrammatic(p2, false, d.rolled, d.dmg_type, {}); - } - } else { - text << " Miss (" << (rolled + bonus) << " to hit < " << ac << " ac)" << std::endl; - } - return text.str(); - } - - std::string heal(std::vector args) { - return healOrDamage(true, args, {}); - } - - std::string damage(std::vector args, std::map flags) { - return healOrDamage(false, args, flags); - } - - std::string save(std::vector args, std::map flags) { - if(args.size() < 3) { - throw std::runtime_error("Subcommand 'save' requires at least 3 arguments"); - } - std::stringstream text; - rules::Ability ability = rules::tryGetAbilityOrSkill(args[0]); - if(! ability) { - throw std::runtime_error("Requires a valid ability name but received \"" + args[0] + "\"."); - } - args.erase(args.begin()); - int DC = utils::parseInt(args[0]); - args.erase(args.begin()); - // Now iterate over the paths - for(std::string s : args) { - fs::path p = getTruePath(s); - auto c = utils::instantiate(p); - int initHP = c->getHP(); - int rolled = dice::roll(20); - int bonus = c->getAbilitySaveBonus(ability); - int damage = 0; - std::string type = "force"; - bool halves = flags.find("halves") != flags.end(); - if(flags.find("damage") != flags.end()) { - damage = utils::parseInt(flags.at("damage")); - if(flags.find("type") != flags.end()) type = flags.at("type"); - auto qualifiers = parseQualifiers(flags); - rolled = c->saveOrDamage(ability, DC, damage, type, qualifiers, halves); - rolled -= bonus; // It's combined in creature - } - bool passed = rolled + bonus >= DC; - text << c->getName() << " " << (passed? "PASS" : "FAIL") << ": "; - text << formatRoll(ability.getFull(), "save", rolled, bonus); - if(flags.find("damage") != flags.end() and (halves or ! passed)) { - text << formatHealingDamage(c, initHP, false, damage, type, parseQualifiers(flags)); - } - utils::saveJson(*c, p); - } - return text.str(); - } - - std::string reset(std::vector args) { - for(std::string s : args) { - fs::path p = getTruePath(s); - auto c = utils::instantiate(p); - c->longRest(); - utils::saveJson(*c, p); - } - return ""; - } - - std::string 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 = utils::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(utils::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 = rules::tryGetAbilityOrSkill(abilityOrSkill); - rules::Ability ability = rules::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, utils::parseInt(toSet)); - } else { - throw std::runtime_error("Subcommand 'set' expected an ability, skill, proficiency, or name field to set, but was given " + abilityOrSkill); - } - } - utils::saveJson(*c, p); - return ""; - } - - std::string add(std::vector args) { - std::stringstream text; - fs::path p = getTruePath(args[0]); - args.erase(args.begin()); // remove path from args - auto c = utils::instantiate(p); - std::string addName = utils::join(args, " "); - std::shared_ptr ent; - fs::path path = getTruePath(addName); - if(fs::directory_entry(path).exists()) { - ent = utils::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()); - } - } - utils::saveJson(*c, p); - text << "Added the " << ent->getType() << " " << ent->getName() << " to " << c->getGivenName() << " the " << c->getName() << std::endl; - return text.str(); - } - - std::string del(std::vector args) { - std::stringstream text; - fs::path p = getTruePath(args[0]); - args.erase(args.begin()); // remove path from args - auto c = utils::instantiate(p); - //Atempt to load the item if it's a path - std::string itemName = utils::join(args, " "); - try { - auto i = utils::instantiate(getTruePath(itemName)); - if(i) { - itemName = i->getName(); - } - } catch(std::exception& e) {} // eat. - 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; - } - } - } - utils::saveJson(*c, p); - if(removed) { - text << "Successfully removed the " << removed->Entry::getType() << " " << removed->getName() << std::endl; - } else { - text << "Could not find any inventory item nor spell by that name" << std::endl; - } - return text.str(); - } - - std::string edit(std::vector args) { - auto p = getTruePath(args[0]); - auto e = utils::instantiate(p); - auto editor = settings::getString("editor"); - // General workflow: copy notes (text) from e to a temp file, edit it, then copy back. - fs::path tmp("/tmp/dmtool.tmp"); - std::ofstream out(tmp); - out << e->Entry::getText(); - out.close(); - std::system((editor + " " + tmp.string()).c_str()); - std::ifstream in(tmp); - std::string newText(std::istreambuf_iterator{in}, {}); - e->setText(newText); - utils::saveJson(e->serialize(), p); - return ""; - } - - std::string spellcasting(std::vector args) { - std::stringstream text; - auto p = getTruePath(args[0]); - auto c = utils::instantiate(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(); - } - - std::string git(std::vector args) { - std::string root = getTruePath("").string(); - std::system(("cd " + root + " && " + utils::join(args, " ")).c_str()); - return ""; - } -} diff --git a/src/cmd_query.cc b/src/cmd_query.cc deleted file mode 100644 index e13876f..0000000 --- a/src/cmd_query.cc +++ /dev/null @@ -1,48 +0,0 @@ -#include "cmd.h" -#include "utils.h" -#include "creature.h" -#include "dice.h" -#include "weapon.h" -#include - -namespace cmd { - std::string attacks(std::vector args) { - std::stringstream text; - auto c = utils::instantiate(getTruePath(args[0])); - for(auto w : creature::getAttacks(*c)) { - text << w->getName() << std::endl; - } - return text.str(); - } - - std::string roll(std::vector args) { - std::stringstream text; - auto c = utils::instantiate(getTruePath(args[0])); - args.erase(args.begin()); - std::string rollName = utils::join(args, " "); - utils::lower(rollName); - int rolled = dice::roll(20); - // Search through skills, saves, and attacks to roll - if(rollName == "init" or rollName == "initiative") { - text << formatRoll("Initiative", "check", rolled, c->getInitiative()); - } - rules::Skill skill = rules::tryGetAbilityOrSkill(rollName); - rules::Ability ability = rules::tryGetAbilityOrSkill(rollName); - if(skill) { - text << formatRoll(skill.getName(), "check", rolled, c->getSkillBonus(skill)); - } else if(ability) { - text << formatRoll(ability.getFull(), "save", rolled, c->getAbilitySaveBonus(ability)); - } else { - for(auto w : creature::getAttacks(*c)) { - if(w->getName() == rollName) { - text << w->getText(*c) << std::endl; - int bonus = w->getToHitBonus(*c); - text << formatRoll(w->getName(), "attack", rolled, bonus); - text << " on hit: " << entry::formatDmg(*w, *c) << std::endl; - break; - } - } - } - return text.str(); - } -} diff --git a/src/cmd_usage.cc b/src/cmd_usage.cc deleted file mode 100644 index 44daca4..0000000 --- a/src/cmd_usage.cc +++ /dev/null @@ -1,60 +0,0 @@ -#include "cmd.h" -#include -#include - -namespace cmd { - std::string usage(const std::string& exename) { - std::stringstream text; - text << "Usage:" << std::endl; - std::string indOpt = " " + exename + " "; - std::string indDesc = " "; - text << indOpt << "[ls] [subfolder]" << std::endl; - text << indDesc << "List creatures and objects." << std::endl; - text << indOpt << "cp old-path new-path" << std::endl; - text << indDesc << "Copy old-path to new-path, instantiating it." << std::endl; - text << indOpt << "mkdir path" << std::endl; - text << indDesc << "Make a new directory for holding creatures and objects." << std::endl; - text << indOpt << "mv old-path new-path" << std::endl; - text << indDesc << "Move old-path to new-path." << std::endl; - text << indOpt << "rm path" << std::endl; - text << indDesc << "Remove existing creature, object, or directory." << std::endl; - text << indOpt << "attacks path" << std::endl; - text << indDesc << "List attacks available for a creature" << std::endl; - text << indOpt << "roll path name" << std::endl; - text << indDesc << "Roll a skill check, save, or attack." << std::endl; - text << indOpt << "attack path1 name path2 [-1] [-2]" << std::endl; - text << indDesc << "Roll an attack for path1 attacking path2. Handedness defaults to 2h if versatile and not holding a shield, otherwise use -1 and -2." << std::endl; - text << indOpt << "save ability DC paths [--damage DMG] [--type TYPE] [--halves] [--magical,-m] [--silvered,-s] [--adamantine,-a]" << std::endl; - text << indDesc << "Force creatures at paths to make an ability save at DC. Default: report pass/fail." << std::endl; - text << indDesc << " If --damage, failed saves take TYPE damage (default force), passed saves negate." << std::endl; - text << indDesc << " If --halves, passed saves halve damage rather than negate." << std::endl; - text << indOpt << "damage path amount [type] [--magical,-m] [--silvered,-s] [--adamantine,-a]" << std::endl; - text << indDesc << "Damage creature by amount. Type defaults to \"force\"." << std::endl; - text << indOpt << "heal path amount" << std::endl; - text << indDesc << "Heal creature by amount." << std::endl; - text << indOpt << "reset path" << std::endl; - text << indDesc << "Reset creature to full health (as if completing a long rest)." << std::endl; - text << indOpt << "set path field value" << std::endl; - text << indDesc << "Set a field to a new value, where field is any of:" << std::endl; - text << indDesc << " ability (str, dex, con, int, wis, cha); value is new ability score" << std::endl; - text << indDesc << " skill (athletics, \"sleight of hand\", etc.); value is (none|proficient|expert)" << std::endl; - text << indDesc << " proficiency; value is new proficency bonus." << std::endl; - text << indDesc << " name; value is new given name." << std::endl; - text << indOpt << "edit path" << std::endl; - text << indDesc << "Edit notes associated with creature." << std::endl; - text << indOpt << "add path entry" << std::endl; - 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 << "git [command]" << std::endl; - text << indDesc << "Execute a git command within the dmtool folder." << 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 6491024..ea8ef30 100644 --- a/src/creature.cc +++ b/src/creature.cc @@ -1,7 +1,6 @@ #include "creature.h" #include "dice.h" #include "rules.h" -#include "feature.h" #include "weapon.h" #include "armor.h" #include "attack.h" diff --git a/src/creature.h b/src/creature.h index 09760e3..de93b0e 100644 --- a/src/creature.h +++ b/src/creature.h @@ -2,7 +2,7 @@ #include "rules.h" #include "utils.h" #include "entry.h" -#include "feature.h" +#include "features/feature.h" #include "item.h" #include diff --git a/src/dmtool.cc b/src/dmtool.cc index 6ae096f..3aec8b2 100644 --- a/src/dmtool.cc +++ b/src/dmtool.cc @@ -1,4 +1,4 @@ -#include "cmd.h" +#include "cmd/cmd.h" #include "utils.h" #include #include diff --git a/src/entry.cc b/src/entry.cc index 663a40a..9c9b3da 100644 --- a/src/entry.cc +++ b/src/entry.cc @@ -1,6 +1,6 @@ #include "entry.h" #include "utils.h" -#include "feature.h" +#include "features/feature.h" #include "item.h" #include "spell.h" #include "creature.h" diff --git a/src/feature.cc b/src/feature.cc deleted file mode 100644 index 941de4e..0000000 --- a/src/feature.cc +++ /dev/null @@ -1,19 +0,0 @@ -#include "feature.h" -#include "spellcasting.h" -#include "attack.h" -#include "utils.h" -#include -#include - -using namespace std; - -namespace entry { - shared_ptr Feature::create(const nlohmann::json& data) { - if(data["type"] == "spells") { - return utils::loadDFromJson(data); - } else if(data["type"] == "attack") { - return utils::loadDFromJson(data); - } - return shared_ptr(new Feature(data)); - } -} diff --git a/src/feature.h b/src/feature.h deleted file mode 100644 index e3c8bdf..0000000 --- a/src/feature.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#include -#include "entry.h" -#include - -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 create(const nlohmann::json& data); - virtual ~Feature() {} - }; -} diff --git a/src/features/feature.cc b/src/features/feature.cc new file mode 100644 index 0000000..0fd2d6c --- /dev/null +++ b/src/features/feature.cc @@ -0,0 +1,19 @@ +#include "feature.h" +#include "../spellcasting.h" +#include "../attack.h" +#include "../utils.h" +#include +#include + +using namespace std; + +namespace entry { + shared_ptr Feature::create(const nlohmann::json& data) { + if(data["type"] == "spells") { + return utils::loadDFromJson(data); + } else if(data["type"] == "attack") { + return utils::loadDFromJson(data); + } + return shared_ptr(new Feature(data)); + } +} diff --git a/src/features/feature.h b/src/features/feature.h new file mode 100644 index 0000000..209a30a --- /dev/null +++ b/src/features/feature.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include "../entry.h" +#include + +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 create(const nlohmann::json& data); + virtual ~Feature() {} + }; +} diff --git a/src/rules.cc b/src/rules.cc index 96d9a51..900d023 100644 --- a/src/rules.cc +++ b/src/rules.cc @@ -37,6 +37,8 @@ namespace rules { {"non-adamantine", "adamantine"} }; + const std::set Condition::conditions {"blinded", "charmed", "deafened", "frightened", "grappled", "incapacitated", "invisible", "paralyzed", "petrified", "poisoned", "prone", "restrained", "stunned", "unconscious", "exhausted1", "exhausted2", "exhausted3", "exhausted4", "exhausted5", "exhausted6"}; + std::ostream& operator<<(std::ostream& os, const Ability& a) { os << std::string(a); return os; @@ -51,4 +53,9 @@ namespace rules { os << std::string(q); return os; } + + std::ostream& operator<<(std::ostream& os, const Condition& c) { + os << std::string(c); + return os; + } } diff --git a/src/rules.h b/src/rules.h index 2894947..30464a8 100644 --- a/src/rules.h +++ b/src/rules.h @@ -2,6 +2,7 @@ #include "utils.h" #include #include +#include #include #include #include @@ -18,6 +19,10 @@ namespace rules { ri.payload = j; } std::string getPayload(void) const {return payload;} + bool operator==(const RuleItem& rhs) const {return getPayload() == rhs.getPayload();} + bool operator<(const RuleItem& rhs) const {return getPayload() < rhs.getPayload();} + operator std::string() const {return getPayload();} + operator bool() const {return ! getPayload().empty();} protected: std::string payload; }; @@ -26,10 +31,6 @@ namespace rules { public: std::string getFull() const {return abilities.at(getAbbrev());} std::string getAbbrev() const {return getPayload();} - operator std::string() const {return getAbbrev();} - bool operator<(const Ability& rhs) const {return getAbbrev() < rhs.getAbbrev();} - bool operator==(const Ability& rhs) const {return getAbbrev() == rhs.getAbbrev();} - operator bool() const {return ! getAbbrev().empty();} Ability() {} Ability(const std::string& abbrev) { @@ -67,10 +68,6 @@ namespace rules { public: std::string getName() const {return getPayload();} Ability getAbility() const {return Ability(skill2ability.at(getName()));} - operator std::string() const {return getName();} - bool operator<(const Skill& rhs) const {return getName() < rhs.getName();} - bool operator==(const Skill& rhs) const {return getName() == rhs.getName();} - operator bool() const {return ! getName().empty();} virtual ~Skill() {} @@ -128,9 +125,7 @@ namespace rules { } std::string getNegative() const {return getPayload();} std::string getPositive() const {return negative2positive.at(getNegative());} - operator std::string() const {return getNegative();} virtual ~Qualifier() {} - bool operator==(const Qualifier& rhs) const {return getNegative() == rhs.getNegative();} static Qualifier Magical() {return Qualifier("nonmagical");} static Qualifier Silvered() {return Qualifier("non-silvered");} @@ -140,9 +135,38 @@ namespace rules { static const std::map negative2positive; }; + class Condition : public RuleItem { + public: + Condition() {} + Condition(std::string name) { + utils::lower(name); + if(conditions.count(name) == 0) { + throw std::invalid_argument("No such condition: " + name); + } + payload = name; + } + + virtual ~Condition() {} + + void incExhaustion() { + if(payload.find("exhausted") == std::string::npos) { + throw std::invalid_argument("Cannot increment exhaustion on condition " + payload); + } + int exhLvl = utils::parseInt(std::string(1, payload.back())); + if(exhLvl >= 6) { + throw std::invalid_argument("Cannot increment exhaustion beyond level 6"); + } + payload.back() = '0'+exhLvl+1; + } + + private: + static const std::set conditions; + }; + std::ostream& operator<<(std::ostream& os, const Ability& a); std::ostream& operator<<(std::ostream& os, const Skill& s); std::ostream& operator<<(std::ostream& os, const Qualifier& q); + std::ostream& operator<<(std::ostream& os, const Condition& c); template T tryGetAbilityOrSkill(std::string src) { try { diff --git a/src/spellcasting.h b/src/spellcasting.h index 902f860..46d4ba8 100644 --- a/src/spellcasting.h +++ b/src/spellcasting.h @@ -1,5 +1,5 @@ #pragma once -#include "feature.h" +#include "features/feature.h" #include "spell.h" #include "rules.h" #include "utils.h" diff --git a/src/utils.h b/src/utils.h index 77096e7..2998704 100644 --- a/src/utils.h +++ b/src/utils.h @@ -98,7 +98,7 @@ namespace utils { template std::vector> castPtrs(std::vector> from) { std::vector> Ts; for(std::shared_ptr f : from) { - std::shared_ptr t = dynamic_pointer_cast(f); + std::shared_ptr t = std::dynamic_pointer_cast(f); if(t) { Ts.push_back(t); } -- cgit v1.2.3