Skip to content

Commit ffa7074

Browse files
committed
✨ 🐛 Mythic Actions; Gear attributes; Trait entries in statblocks
Resolves #757 Resolves #756 Resolves #568
1 parent c50691c commit ffa7074

File tree

15 files changed

+445
-136
lines changed

15 files changed

+445
-136
lines changed

docs/templates/dnd5e/QuteMonster/README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Extension of [Tools5eQuteBase](../Tools5eQuteBase.md).
66

77
## Attributes
88

9-
[5eInitiativeYaml](#5einitiativeyaml), [5eInitiativeYamlNoSource](#5einitiativeyamlnosource), [5eStatblockYaml](#5estatblockyaml), [5eStatblockYamlNoSource](#5estatblockyamlnosource), [ac](#ac), [acHp](#achp), [acText](#actext), [action](#action), [alignment](#alignment), [bonusAction](#bonusaction), [books](#books), [conditionImmune](#conditionimmune), [cr](#cr), [description](#description), [environment](#environment), [fluffImages](#fluffimages), [fullType](#fulltype), [hasImages](#hasimages), [hasMoreImages](#hasmoreimages), [hasSections](#hassections), [hitDice](#hitdice), [hp](#hp), [hpText](#hptext), [immune](#immune), [immuneResist](#immuneresist), [initiative](#initiative), [isNpc](#isnpc), [labeledSource](#labeledsource), [languages](#languages), [legendary](#legendary), [legendaryGroup](#legendarygroup), [legendaryGroupLink](#legendarygrouplink), [name](#name), [passive](#passive), [pb](#pb), [rawSpellcasting](#rawspellcasting), [reaction](#reaction), [reprintOf](#reprintof), [resist](#resist), [savesSkills](#savesskills), [savingThrows](#savingthrows), [scores](#scores), [senses](#senses), [showAllImages](#showallimages), [showMoreImages](#showmoreimages), [showPortraitImage](#showportraitimage), [size](#size), [skills](#skills), [source](#source), [sourceAndPage](#sourceandpage), [sourcesWithFootnote](#sourceswithfootnote), [speed](#speed), [spellcasting](#spellcasting), [subtype](#subtype), [tags](#tags), [text](#text), [token](#token), [trait](#trait), [type](#type), [vaultPath](#vaultpath), [vulnerable](#vulnerable)
9+
[5eInitiativeYaml](#5einitiativeyaml), [5eInitiativeYamlNoSource](#5einitiativeyamlnosource), [5eStatblockYaml](#5estatblockyaml), [5eStatblockYamlNoSource](#5estatblockyamlnosource), [ac](#ac), [acHp](#achp), [acText](#actext), [action](#action), [alignment](#alignment), [allTraits](#alltraits), [bonusAction](#bonusaction), [books](#books), [conditionImmune](#conditionimmune), [cr](#cr), [description](#description), [environment](#environment), [fluffImages](#fluffimages), [fullType](#fulltype), [gear](#gear), [hasImages](#hasimages), [hasMoreImages](#hasmoreimages), [hasSections](#hassections), [hitDice](#hitdice), [hp](#hp), [hpText](#hptext), [immune](#immune), [immuneResist](#immuneresist), [initiative](#initiative), [isNpc](#isnpc), [labeledSource](#labeledsource), [languages](#languages), [legendary](#legendary), [legendaryGroup](#legendarygroup), [legendaryGroupLink](#legendarygrouplink), [name](#name), [passive](#passive), [pb](#pb), [rawSpellcasting](#rawspellcasting), [reaction](#reaction), [reprintOf](#reprintof), [resist](#resist), [savesSkills](#savesskills), [savingThrows](#savingthrows), [scores](#scores), [senses](#senses), [showAllImages](#showallimages), [showMoreImages](#showmoreimages), [showPortraitImage](#showportraitimage), [size](#size), [skills](#skills), [source](#source), [sourceAndPage](#sourceandpage), [sourcesWithFootnote](#sourceswithfootnote), [speed](#speed), [spellcasting](#spellcasting), [subtype](#subtype), [tags](#tags), [text](#text), [token](#token), [trait](#trait), [type](#type), [vaultPath](#vaultpath), [vulnerable](#vulnerable)
1010

1111
### 5eInitiativeYaml
1212

@@ -58,6 +58,10 @@ Creature actions as a list of [NamedText](../../NamedText.md)
5858

5959
Creature alignment
6060

61+
### allTraits
62+
63+
Creature traits as [Traits](Traits.md)
64+
6165
### bonusAction
6266

6367
Creature bonus actions as a list of [NamedText](../../NamedText.md)
@@ -90,6 +94,10 @@ List of images as [ImageRef](../../ImageRef.md) (optional)
9094

9195
Creature type (lowercase) and subtype if present: `{resource.type} ({resource.subtype})`
9296

97+
### gear
98+
99+
Creature gear as list of item links
100+
93101
### hasImages
94102

95103
Return true if any images are present
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# TraitDescription
2+
3+
5eTools creature trait description.
4+
5+
## Attributes
6+
7+
[description](#description), [present](#present), [title](#title), [traits](#traits)
8+
9+
### description
10+
11+
Formatted text describing the collection of traits
12+
13+
### present
14+
15+
16+
### title
17+
18+
Title of the trait description
19+
20+
### traits
21+
22+
Traits as a list of [NamedText](../../NamedText.md)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Traits
2+
3+
5eTools creature traits.
4+
5+
## Attributes
6+
7+
[actions](#actions), [bonusActions](#bonusactions), [lairActions](#lairactions), [legendaryActions](#legendaryactions), [legendaryGroupLink](#legendarygrouplink), [mythicActions](#mythicactions), [reactions](#reactions), [regionalEffects](#regionaleffects), [traits](#traits)
8+
9+
### actions
10+
11+
Creature actions as a list of [NamedText](../../NamedText.md)
12+
13+
### bonusActions
14+
15+
Creature bonus actions as a list of [NamedText](../../NamedText.md)
16+
17+
### lairActions
18+
19+
Creature lair actions as a list of [NamedText](../../NamedText.md)
20+
21+
### legendaryActions
22+
23+
Creature legendary traits as a list of [NamedText](../../NamedText.md)
24+
25+
### legendaryGroupLink
26+
27+
Link to the legendary group, if present
28+
29+
### mythicActions
30+
31+
Creature mythic traits as a list of [NamedText](../../NamedText.md)
32+
33+
### reactions
34+
35+
Creature reactions as a list of [NamedText](../../NamedText.md)
36+
37+
### regionalEffects
38+
39+
Creature regional effects as a list of [NamedText](../../NamedText.md)
40+
41+
### traits
42+
43+
Creature traits as a list of [NamedText](../../NamedText.md)

examples/templates/tools5e/images-monster2md.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ title: {resource.name}{#if resource.token}
5151
- **Damage Immunities** {resource.immune}
5252
{/if}{#if resource.conditionImmune}
5353
- **Condition Immunities** {resource.conditionImmune}
54+
{/if}{#if resource.gear}
55+
- **Gear** {resource.gear.join(", ")}
5456
{/if}
5557
- **Languages** {#if resource.languages }{resource.languages}{#else}—{/if}
5658
- **Challenge** {resource.cr}

examples/templates/tools5e/monster2md-2024.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ title: {resource.name}{#if resource.token}
4949
- **Proficiency Bonus** {resource.pb}
5050
- **Saving Throws** {#if resource.savingThrows }{resource.savingThrows}{#else}⏤{/if}
5151
- **Skills** {#if resource.skills }{resource.skills}{#else}⏤{/if}
52-
- **Senses** {#if resource.senses }{resource.senses}, {/if}passive Perception {resource.passive}
5352
{#if resource.vulnerable }
5453
- **Damage Vulnerabilities** {resource.vulnerable}
5554
{/if}{#if resource.resist}
@@ -58,7 +57,10 @@ title: {resource.name}{#if resource.token}
5857
- **Damage Immunities** {resource.immune}
5958
{/if}{#if resource.conditionImmune}
6059
- **Condition Immunities** {resource.conditionImmune}
60+
{/if}{#if resource.gear}
61+
- **Gear** {resource.gear.join(", ")}
6162
{/if}
63+
- **Senses** {#if resource.senses }{resource.senses}, {/if}passive Perception {resource.passive}
6264
- **Languages** {#if resource.languages }{resource.languages}{#else}—{/if}
6365
- **Challenge** {resource.cr}
6466
{#if resource.trait}

examples/templates/tools5e/monster2md-scores.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ title: {resource.name}{#if resource.token}
5151
- **Damage Immunities** {resource.immune}
5252
{/if}{#if resource.conditionImmune}
5353
- **Condition Immunities** {resource.conditionImmune}
54+
{/if}{#if resource.gear}
55+
- **Gear** {resource.gear.join(", ")}
5456
{/if}
5557
- **Languages** {#if resource.languages }{resource.languages}{#else}—{/if}
5658
- **Challenge** {resource.cr}

examples/templates/tools5e/monster2md-yamlStatblock-header.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ title: {resource.name}{#if resource.token}
4444
- **Damage Immunities** {resource.immune}
4545
{/if}{#if resource.conditionImmune}
4646
- **Condition Immunities** {resource.conditionImmune}
47+
{/if}{#if resource.gear}
48+
- **Gear** {resource.gear.join(", ")}
4749
{/if}
4850
- **Languages** {#if resource.languages }{resource.languages}{#else}—{/if}
4951
- **Challenge** {resource.cr}

src/main/java/dev/ebullient/convert/tools/dnd5e/Json2QuteCommon.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -870,10 +870,19 @@ void collectTraits(List<NamedText> traits, JsonNode array) {
870870
tui().warnf(Msg.UNKNOWN, "Unknown %s for %s: %s", array, sources.getKey(), array.toPrettyString());
871871
return;
872872
}
873-
for (JsonNode e : iterableElements(array)) {
874-
String name = SourceField.name.replaceTextFrom(e, this)
875-
.replaceAll(":$", "");
876-
addNamedTrait(traits, name, e);
873+
if (streamOf(array).allMatch(e -> e.isObject() && SourceField.name.existsIn(e))) {
874+
for (JsonNode e : iterableElements(array)) {
875+
String name = SourceField.name.replaceTextFrom(e, this)
876+
.replaceAll(":$", "");
877+
addNamedTrait(traits, name, e);
878+
}
879+
} else {
880+
// no names, just text
881+
List<String> text = new ArrayList<>();
882+
appendToText(text, array, null);
883+
if (!text.isEmpty()) {
884+
traits.add(new NamedText("", text, List.of()));
885+
}
877886
}
878887
} finally {
879888
parseState().pop(pushed);

src/main/java/dev/ebullient/convert/tools/dnd5e/Json2QuteLegendaryGroup.java

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,14 @@
22

33
import java.util.ArrayList;
44
import java.util.List;
5-
import java.util.Map.Entry;
6-
import java.util.regex.Pattern;
75

86
import com.fasterxml.jackson.databind.JsonNode;
97

8+
import dev.ebullient.convert.tools.JsonNodeReader;
109
import dev.ebullient.convert.tools.Tags;
11-
import dev.ebullient.convert.tools.ToolsIndex.TtrpgValue;
1210
import dev.ebullient.convert.tools.dnd5e.qute.Tools5eQuteNote;
1311

1412
public class Json2QuteLegendaryGroup extends Json2QuteCommon {
15-
static final Pattern UPPERCASE_LETTER = Pattern.compile("([A-Z]|\\d+)");
16-
static final List<String> LEGENDARY_IGNORE_LIST = List.of("name", "source", "page",
17-
TtrpgValue.indexInputType.name(), TtrpgValue.indexKey.name(), "_copy", "_meta",
18-
"additionalSources", "_rawName", "_isCopy", "_copiedFrom", "isHomebrew");
1913

2014
Json2QuteLegendaryGroup(Tools5eIndex index, Tools5eIndexType type, JsonNode jsonNode) {
2115
super(index, type, jsonNode);
@@ -27,26 +21,9 @@ protected Tools5eQuteNote buildQuteNote() {
2721
tags.add("monster", "legendary-group");
2822

2923
List<String> text = new ArrayList<>();
30-
for (Entry<String, JsonNode> field : iterableFields(rootNode)) {
31-
String fieldName = field.getKey();
32-
if (LEGENDARY_IGNORE_LIST.contains(fieldName)) {
33-
continue;
34-
}
35-
boolean pushed = parseState().push(field.getValue());
36-
try {
37-
fieldName = fieldName.substring(0, 1).toUpperCase()
38-
+ UPPERCASE_LETTER.matcher(fieldName.substring(1))
39-
.replaceAll(matchResult -> " " + (matchResult.group(1).toLowerCase()));
40-
41-
maybeAddBlankLine(text);
42-
text.add("## " + fieldName);
43-
text.add(getSourceText(parseState()));
44-
text.add("");
45-
appendToText(text, field.getValue(), "###");
46-
} finally {
47-
parseState().pop(pushed);
48-
}
49-
}
24+
appendSectionText(text, LeGroupFields.lairActions, "Lair Actions");
25+
appendSectionText(text, LeGroupFields.regionalEffects, "Regional Effects");
26+
appendSectionText(text, LeGroupFields.mythicEncounter, "As a Mythic Encounter");
5027

5128
return new Tools5eQuteNote(sources,
5229
sources.getName(),
@@ -56,4 +33,27 @@ protected Tools5eQuteNote buildQuteNote() {
5633
.withTargetFile(linkifier().getTargetFileName(getName(), sources))
5734
.withTargetPath(linkifier().getRelativePath(type));
5835
}
36+
37+
void appendSectionText(List<String> text, LeGroupFields field, String header) {
38+
JsonNode node = field.getFrom(rootNode);
39+
if (node == null || node.isMissingNode() || node.isNull()) {
40+
return;
41+
}
42+
boolean pushed = parseState().push(node);
43+
try {
44+
maybeAddBlankLine(text);
45+
text.add("## " + header);
46+
text.add(getSourceText(parseState()));
47+
text.add("");
48+
appendToText(text, node, "###");
49+
} finally {
50+
parseState().pop(pushed);
51+
}
52+
}
53+
54+
enum LeGroupFields implements JsonNodeReader {
55+
lairActions,
56+
mythicEncounter,
57+
regionalEffects,
58+
}
5959
}

0 commit comments

Comments
 (0)