Skip to content

Commit

Permalink
Add bestiary support (#423)
Browse files Browse the repository at this point in the history
* ✨ 🐉 Pf2e Bestiary

* Add ability to export creatures with the CLI tool - just the name and level for now
* Add defenses export to Pf2e creature support
* Add support for perception to pf2e creature
* Put AC note text in "notes" through tokenization

Previously only note text in the "note" key was tokenized. This makes tests fail in creature stat blocks, which use e.g.
ac:
  "notes": "{@spell mage armor}"

* Add a creature2md template file
* Add tests for the creature type
* Add `std` as an enum type rather than using the string directly.
* Add support for parsing languages for creatures.

Added a `CreatureLanguages` type as an intermediate object to hold the
language data. Not sure whether this is the desired style for these helper
classes, or whether I should be prefacing the names with `Qute` and putting
them in e.g. `QuteCreature` instead.

* Move CreatureLanguages into QuteCreature, and fix up generated
documentation.

Add support for `MarkdownDoclet` to correctly format and parse record
components. This was actually a pain to figure out, as it turns out that
these aren't accessible as an Element and instead have to be parsed through
the @param tags of the class comment.

* Add support for skills to the creature importer
* Collapse spaces in docstrings

Prevents docstring spacing from rendering weird in
markdown (such as indentation in @param tags)

* Add a ^statblock block tag to the default creature template

Also fix some import order formatting that I accidentally changed earlier.

* Add support for parsing languages for creatures.

Add support for `MarkdownDoclet` to correctly format and parse record
components. This was actually a pain to figure out, as it turns out that
these aren't accessible as an Element and instead have to be parsed through
the @param tags of the class comment.

* Use admonition block in the creature2md template
  • Loading branch information
miscoined authored Apr 29, 2024
1 parent a956d21 commit d2696de
Show file tree
Hide file tree
Showing 17 changed files with 557 additions and 29 deletions.
68 changes: 68 additions & 0 deletions docs/templates/pf2e/QuteCreature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# QuteCreature

Pf2eTools Creature attributes (`creature2md.txt`)

Use `%%--` to mark the end of the preamble (frontmatter and other leading content only appropriate to the standalone case).

Extension of [Pf2eQuteBase](Pf2eQuteBase.md)

## Attributes

[aliases](#aliases), [defenses](#defenses), [description](#description), [hasSections](#hassections), [labeledSource](#labeledsource), [level](#level), [name](#name), [perception](#perception), [source](#source), [sourceAndPage](#sourceandpage), [tags](#tags), [text](#text), [traits](#traits), [vaultPath](#vaultpath)


### aliases

Aliases for this note (optional)

### defenses

Defenses (AC, saves, etc) as [QuteDataDefenses](QuteDataDefenses.md)

### description

Short creature description (optional)

### hasSections

True if the content (text) contains sections

### labeledSource

Formatted string describing the content's source(s): `_Source: <sources>_`

### level

Creature level (number, optional)

### name

Note name

### perception

Creature perception (number, optional)

### source

String describing the content's source(s)

### sourceAndPage

Book sources as list of [SourceAndPage](../SourceAndPage.md)

### tags

Collected tags for inclusion in frontmatter

### text

Formatted text. For most templates, this is the bulk of the content.

### traits

Collection of traits (decorated links, optional)

### vaultPath

Path to this note in the vault
1 change: 1 addition & 0 deletions docs/templates/pf2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [QuteArchetype](QuteArchetype.md): Pf2eTools Archetype attributes (`archetype2md.txt`)
- [QuteBackground](QuteBackground.md): Pf2eTools Background attributes (`background2md.txt`)
- [QuteBook](QuteBook/README.md): Pf2eTools Book attributes (`book2md.txt`)
- [QuteCreature](QuteCreature.md): Pf2eTools Creature attributes (`creature2md.txt`)
- [QuteDataActivity](QuteDataActivity.md): Pf2eTools activity attributes
- [QuteDataArmorClass](QuteDataArmorClass.md): Pf2eTools armor class attributes
- [QuteDataDefenses](QuteDataDefenses/README.md): Pf2eTools Armor class, Saving Throws, and other attributes describing defenses
Expand Down
36 changes: 22 additions & 14 deletions src/main/java/dev/ebullient/convert/io/MarkdownDoclet.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,7 @@
import java.util.stream.Collectors;

import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.QualifiedNameable;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
Expand All @@ -36,6 +30,7 @@
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.LinkTree;
import com.sun.source.doctree.LiteralTree;
import com.sun.source.doctree.ParamTree;
import com.sun.source.doctree.TextTree;
import com.sun.source.util.DocTrees;

Expand Down Expand Up @@ -196,11 +191,24 @@ protected void writeReferenceFile(DocTrees docTrees, TypeElement t) throws IOExc
.collect(Collectors.joining(", ")));
aggregator.add("\n\n");

for (Map.Entry<String, Element> entry : members.entrySet()) {
aggregator.add("\n\n### " + entry.getKey() + "\n\n");
aggregator.addFullBody(docTrees.getDocCommentTree(entry.getValue()));
if (t.getKind() == ElementKind.RECORD) {
// If it's a record, then we can't retrieve the attributes as Elements, so we have to parse them from
// the comment tree instead.
docTrees.getDocCommentTree(t)
.getBlockTags().stream()
.filter(e -> e.getKind() == DocTree.Kind.PARAM)
.map(param -> (ParamTree) param)
.forEach(param -> {
aggregator.add("\n\n### " + param.getName() + "\n\n");
aggregator.addAll(param.getDescription());
});
} else {
for (Map.Entry<String, Element> entry : members.entrySet()) {
aggregator.add("\n\n### " + entry.getKey() + "\n\n");
aggregator.addFullBody(docTrees.getDocCommentTree(entry.getValue()));
}
}
out.println(aggregator.toString());
out.println(aggregator);
}
}

Expand All @@ -218,11 +226,11 @@ protected void processElement(DocTrees docTrees, Map<String, Element> members, E
if (e.getAnnotation(Deprecated.class) != null) {
return;
}
} else if (!kind.isField()) {
} else if (!kind.isField() && kind != ElementKind.RECORD_COMPONENT) {
return;
}

if (e.getKind() == ElementKind.METHOD) {
if (kind == ElementKind.METHOD) {
name = name.replaceFirst("(get|is)", "");
name = name.substring(0, 1).toLowerCase() + name.substring(1);
}
Expand Down Expand Up @@ -409,7 +417,7 @@ void add(DocTree docTree) {

void add(String text) {
if (htmlEntity.isEmpty()) {
content.add(text);
content.add(text.replaceAll(" +", " "));
} else {
htmlEntity.peek().add(text);
}
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/dev/ebullient/convert/tools/pf2e/Json2QuteBase.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package dev.ebullient.convert.tools.pf2e;

import java.util.List;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.JsonNode;

import dev.ebullient.convert.tools.JsonNodeReader;
import dev.ebullient.convert.tools.pf2e.qute.Pf2eQuteBase;
import dev.ebullient.convert.tools.pf2e.qute.Pf2eQuteNote;

Expand Down Expand Up @@ -32,6 +36,12 @@ public Pf2eSources getSources() {
return sources;
}

List<String> toAlignments(JsonNode alignNode, JsonNodeReader alignmentField) {
return alignmentField.getListOfStrings(alignNode, tui()).stream()
.map(a -> a.length() > 2 ? a : linkify(Pf2eIndexType.trait, a.toUpperCase()))
.collect(Collectors.toList());
}

public Pf2eQuteBase build() {
boolean pushed = parseState().push(getSources(), rootNode);
try {
Expand Down
159 changes: 159 additions & 0 deletions src/main/java/dev/ebullient/convert/tools/pf2e/Json2QuteCreature.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package dev.ebullient.convert.tools.pf2e;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

import com.fasterxml.jackson.databind.JsonNode;

import dev.ebullient.convert.tools.JsonNodeReader;
import dev.ebullient.convert.tools.Tags;
import dev.ebullient.convert.tools.pf2e.qute.QuteCreature;
import dev.ebullient.convert.tools.pf2e.qute.QuteDataDefenses;

public class Json2QuteCreature extends Json2QuteBase {

public Json2QuteCreature(Pf2eIndex index, JsonNode rootNode) {
super(index, Pf2eIndexType.creature, rootNode);
}

@Override
protected QuteCreature buildQuteResource() {
List<String> text = new ArrayList<>();
Tags tags = new Tags(sources);

appendToText(text, SourceField.entries.getFrom(rootNode), "##");

Collection<String> traits = collectTraitsFrom(rootNode, tags);
if (Pf2eCreature.alignment.existsIn(rootNode)) {
traits.addAll(toAlignments(rootNode, Pf2eCreature.alignment));
}
Optional<Integer> level = Pf2eCreature.level.getIntFrom(rootNode);

return new QuteCreature(sources, text, tags,
traits,
Field.alias.replaceTextFromList(rootNode, this),
Pf2eCreature.description.replaceTextFrom(rootNode, this),
level.orElse(null),
getPerception(),
buildDefenses(),
Pf2eCreatureLanguages.createCreatureLanguages(Pf2eCreature.languages.getFrom(rootNode), this),
buildSkills());
}

/**
* Example JSON input:
*
* <pre>
* "perception": {
* "std": 6
* }
* </pre>
*/
private Integer getPerception() {
JsonNode perceptionNode = Pf2eCreature.perception.getFrom(rootNode);
if (perceptionNode == null || !perceptionNode.isObject()) {
return null;
}
return Pf2eCreature.std.getIntOrThrow(perceptionNode);
}

/**
* Example JSON input:
*
* <pre>
* "defenses": { ... }
* </pre>
*/
private QuteDataDefenses buildDefenses() {
JsonNode defenseNode = Pf2eCreature.defenses.getFrom(rootNode);
if (defenseNode == null || !defenseNode.isObject()) {
return null;
}
return Pf2eDefenses.createInlineDefenses(defenseNode, this);
}

/**
* Example JSON input:
*
* <pre>
* "skills": {
* "athletics": 30,
* "stealth": {
* "std": 36,
* "in forests": 42,
* "note": "additional note"
* },
* "notes": [
* "some note"
* ]
* }
* </pre>
*/
private QuteCreature.CreatureSkills buildSkills() {
JsonNode skillsNode = Pf2eCreature.skills.getFrom(rootNode);
if (skillsNode == null || !skillsNode.isObject()) {
return null;
}
return new QuteCreature.CreatureSkills(
skillsNode.properties().stream()
.filter(e -> !e.getKey().equals(Pf2eCreature.notes.name()))
.map(e -> Pf2eTypeReader.Pf2eSkillBonus.createSkillBonus(e.getKey(), e.getValue(), this))
.toList(),
Pf2eCreature.notes.replaceTextFromList(rootNode, this));
}

/**
* Example JSON input:
*
* <pre>
* "languages": {
* "languages": ["Common", "Sylvan"],
* "abilities": ["{&#64;ability telepathy} 100 feet"],
* "notes": ["some other notes"],
* }
* </pre>
*/
enum Pf2eCreatureLanguages implements JsonNodeReader {
languages,
abilities,
notes;

static QuteCreature.CreatureLanguages createCreatureLanguages(JsonNode node, Pf2eTypeReader convert) {
if (node == null) {
return null;
}
return new QuteCreature.CreatureLanguages(
languages.getListOfStrings(node, convert.tui()),
abilities.getListOfStrings(node, convert.tui()).stream().map(convert::replaceText).toList(),
notes.getListOfStrings(node, convert.tui()).stream().map(convert::replaceText).toList());
}
}

enum Pf2eCreature implements JsonNodeReader {
abilities,
abilityMods,
alignment,
attacks,
defenses,
description,
hasImages,
inflicts,
isNpc,
items,
languages,
level,
notes,
perception,
rarity,
rituals,
senses,
size,
skills,
speed,
spellcasting,
std,
traits,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,6 @@ private String findRange(JsonNode actionNode) {
return replaceText(rangeString);
}

private List<String> toAlignments(JsonNode alignNode, Pf2eDeity alignmentField) {
return alignmentField.getListOfStrings(alignNode, tui()).stream()
.map(a -> a.length() > 2 ? a : linkify(Pf2eIndexType.trait, a.toUpperCase()))
.collect(Collectors.toList());
}

String commandmentToString(List<String> edictOrAnathema) {
if (edictOrAnathema.stream().anyMatch(x -> x.contains(","))) {
return String.join("; ", edictOrAnathema);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public Pf2eIndex importTree(String filename, JsonNode node) {
Pf2eIndexType.action.withArrayFrom(node, this::addToIndex);
Pf2eIndexType.archetype.withArrayFrom(node, this::addToIndex);
Pf2eIndexType.background.withArrayFrom(node, this::addToIndex);
Pf2eIndexType.creature.withArrayFrom(node, this::addToIndex);
Pf2eIndexType.curse.withArrayFrom(node, this::addToIndex);
Pf2eIndexType.condition.withArrayFrom(node, this::addToIndex);
Pf2eIndexType.deity.withArrayFrom(node, this::addToIndex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ public Pf2eQuteBase convertJson2QuteBase(Pf2eIndex index, JsonNode node) {
return new Json2QuteSpell(index, node).build();
case trait:
return new Json2QuteTrait(index, node).build();
case creature:
return new Json2QuteCreature(index, node).build();
default:
return null;
}
Expand Down
Loading

0 comments on commit d2696de

Please sign in to comment.