From 7bbe18fbb7078ff3200c656a0be29a797d4e5ba2 Mon Sep 17 00:00:00 2001 From: miscoined Date: Fri, 7 Jun 2024 12:53:56 +1000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20rituals=20to=20pf2e=20creatur?= =?UTF-8?q?es=20(#228)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/ebullient/convert/StringUtil.java | 2 +- .../convert/tools/pf2e/Json2QuteCreature.java | 46 +++++++++++--- .../convert/tools/pf2e/qute/QuteCreature.java | 62 ++++++++++++++----- .../templates/toolsPf2e/creature2md.txt | 2 + 4 files changed, 87 insertions(+), 25 deletions(-) diff --git a/src/main/java/dev/ebullient/convert/StringUtil.java b/src/main/java/dev/ebullient/convert/StringUtil.java index 9165ff215..6d0f64122 100644 --- a/src/main/java/dev/ebullient/convert/StringUtil.java +++ b/src/main/java/dev/ebullient/convert/StringUtil.java @@ -30,7 +30,7 @@ public class StringUtil { * If {@code o} is null, then return an empty string. */ public static String format(String formatString, Object val) { - return val == null ? "" : formatString.formatted(val); + return val == null || (val instanceof String && ((String) val).isBlank()) ? "" : formatString.formatted(val); } /** diff --git a/src/main/java/dev/ebullient/convert/tools/pf2e/Json2QuteCreature.java b/src/main/java/dev/ebullient/convert/tools/pf2e/Json2QuteCreature.java index 6c885a80b..59aabde8e 100644 --- a/src/main/java/dev/ebullient/convert/tools/pf2e/Json2QuteCreature.java +++ b/src/main/java/dev/ebullient/convert/tools/pf2e/Json2QuteCreature.java @@ -7,8 +7,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.Stream; import com.fasterxml.jackson.databind.JsonNode; @@ -107,7 +107,8 @@ private static QuteCreature create(JsonNode node, JsonSource convert) { speed.getSpeedFrom(node, convert), attacks.getAttacksFrom(node, convert), abilities.getCreatureAbilitiesFrom(node, convert), - spellcasting.getSpellcastingFrom(node, convert)); + spellcasting.getSpellcastingFrom(node, convert), + rituals.getRitualsFrom(node, convert)); } private QuteCreature.CreatureSkills getSkillsFrom(JsonNode source, JsonSource convert) { @@ -182,21 +183,32 @@ private List getSpellcastingFrom(JsonNode sou .toList(); } + private List getRitualsFrom(JsonNode source, JsonSource convert) { + return streamFrom(source) + .map(n -> Pf2eCreatureSpellcasting.getRitual(n, convert)) + .filter(rituals -> !rituals.ranks().isEmpty()) + .toList(); + } + enum Pf2eCreatureSpellcasting implements Pf2eJsonNodeReader { /** e.g. {@code "Champion Devotion Spells"} */ name, - /** Required - one of {@code "Innate"}, {@code "Prepared"}, {@code "Spontaneous"}, or {@code "Focus"} */ - type, /** e.g. {@code "see soul spells below"} */ note, - /** Integer - number of focus points available */ - fp, /** Required - one of {@code "arcane"}, {@code "divine"}, {@code "occult"}, or {@code "primal"} */ tradition, - /** Integer - The spell attack bonus */ - attack, /** Required - DC for spell effects */ DC, + + /** Rituals only. Array of ritual references - see {@link Pf2eCreatureSpellReference} */ + rituals, + + /** Required - one of {@code "Innate"}, {@code "Prepared"}, {@code "Spontaneous"}, or {@code "Focus"} */ + type, + /** Integer - number of focus points available */ + fp, + /** Integer - The spell attack bonus */ + attack, /** Used within {@link #entry} only, as a key for a block. */ constant, /** @@ -222,6 +234,20 @@ enum Pf2eCreatureSpellcasting implements Pf2eJsonNodeReader { */ spells; + private static QuteCreature.CreatureRitualCasting getRitual(JsonNode source, JsonSource convert) { + return new QuteCreature.CreatureRitualCasting( + tradition.getEnumValueFrom(source, QuteCreature.SpellcastingTradition.class), + DC.getIntFrom(source).orElse(null), + rituals.streamFrom(source) + .collect(Collectors.toMap( + n -> level.getIntFrom(n).orElse(null), + n -> Stream.of(Pf2eCreatureSpellReference.getSpellReference(n, convert)), + Stream::concat)) + .entrySet().stream() + .map(e -> new QuteCreature.CreatureSpells(e.getKey(), e.getValue().toList())) + .toList()); + } + private static QuteCreature.CreatureSpellcasting getSpellcasting(JsonNode source, JsonSource convert) { return new QuteCreature.CreatureSpellcasting( name.getTextOrNull(source), @@ -244,8 +270,8 @@ private List getSpellsFrom(JsonNode source, JsonSou spells.streamFrom(e.getValue()) .map(n -> Pf2eCreatureSpellReference.getSpellReference(n, convert)) .toList())) - .filter(Predicate.not(creatureSpells -> creatureSpells.spells().isEmpty())) - .sorted(Comparator.comparing(QuteCreature.CreatureSpells::baseRank).reversed()) + .filter(creatureSpells -> !creatureSpells.spells().isEmpty()) + .sorted(Comparator.comparing(QuteCreature.CreatureSpells::knownRank).reversed()) .toList(); } } diff --git a/src/main/java/dev/ebullient/convert/tools/pf2e/qute/QuteCreature.java b/src/main/java/dev/ebullient/convert/tools/pf2e/qute/QuteCreature.java index a9a049282..2df41779c 100644 --- a/src/main/java/dev/ebullient/convert/tools/pf2e/qute/QuteCreature.java +++ b/src/main/java/dev/ebullient/convert/tools/pf2e/qute/QuteCreature.java @@ -66,15 +66,18 @@ public class QuteCreature extends Pf2eQuteBase { public final CreatureAbilities abilities; /** The creature's spellcasting capabilities, as a list of {@link QuteCreature.CreatureSpellcasting} */ public final List spellcasting; + /** The creature's ritual casting capabilities, as a list of {@link QuteCreature.CreatureRitualCasting} */ + public final List ritualCasting; - public QuteCreature(Pf2eSources sources, String text, Tags tags, + public QuteCreature( + Pf2eSources sources, String text, Tags tags, Collection traits, List aliases, String description, Integer level, Integer perception, QuteDataDefenses defenses, CreatureLanguages languages, CreatureSkills skills, List senses, Map abilityMods, List items, QuteDataSpeed speed, List attacks, CreatureAbilities abilities, - List spellcasting) { + List spellcasting, List ritualCasting) { super(sources, text, tags); this.traits = traits; this.aliases = aliases; @@ -91,6 +94,7 @@ public QuteCreature(Pf2eSources sources, String text, Tags tags, this.attacks = attacks; this.abilities = abilities; this.spellcasting = spellcasting; + this.ritualCasting = ritualCasting; } /** @@ -173,9 +177,9 @@ public enum SpellcastingPreparation { /** * A creature's abilities, split into the section of the statblock where they should be displayed. Each section is - * a list of {@link QuteAbilityOrAffliction QuteAbilityOrAffliction}. Using the value directly will give you a - * pre-formatted ability according to the embedded template defined for {@link QuteAbility QuteAbility} or - * {@link QuteAffliction QuteAffliction} as appropriate. + * a list of {@link QuteAbilityOrAffliction}. Using an entry in one of these lists directly + * will give you a pre-formatted ability according to the embedded template defined for {@link QuteAbility} or + * {@link QuteAffliction} as appropriate. * * @param top Abilities which should be displayed in the top section of the statblock * @param middle Abilities which should be displayed in the middle section of the statblock @@ -188,6 +192,24 @@ public record CreatureAbilities( List bottom) implements QuteUtil { } + /** + * Information about a type of ritual casting available to this creature. + * + * @param tradition The tradition for these rituals + * @param dc The spell save DC for these rituals + * @param ranks The ritual ranks, as a list of {@link QuteCreature.CreatureSpells} + */ + @TemplateData + public record CreatureRitualCasting( + SpellcastingTradition tradition, + Integer dc, + List ranks) { + /** The name of this set of rituals, e.g. "Divine Rituals" */ + public String name() { + return join(" ", tradition, "Rituals"); + } + } + /** * Information about a type of spellcasting available to this creature. * @@ -199,7 +221,7 @@ public record CreatureAbilities( * @param focusPoints The number of focus points available to this creature for these spells. Present only if these * are focus spells. * @param attackBonus The spell attack bonus for these spells (integer) - * @param dc The difficulty class for these spells (integer) + * @param dc The spell save DC for these spells (integer) * @param notes Any notes associated with these spells * @param ranks The spells for each rank, as a list of {@link QuteCreature.CreatureSpells}. * @param constantRanks The constant spells for each rank, as a list of {@link QuteCreature.CreatureSpells} @@ -249,27 +271,39 @@ public String formattedStats() { * 4th confusion, phantasmal killer (2 slots) * * - * @param baseRank The base rank for these spells (0 for cantrips). - * @param knownRank The rank that these spells are known at. Usually present only for cantrips. - * @param slots The number of slots available for these spells. Not present for constant spells. + * @param knownRank The rank that these spells are known at (0 for cantrips). May be absent for rituals. + * @param cantripRank The rank that these spells are auto-heightened to. Present only for cantrips. + * @param slots The number of slots available for these spells. Not present for constant spells or rituals. * @param spells A list of spells, as a list of {@link QuteCreature.CreatureSpellReference} */ @TemplateData public record CreatureSpells( - Integer baseRank, Integer knownRank, + Integer cantripRank, Integer slots, List spells) { - /** A string of the rank (base and known) for this set of spells. e.g. "5th", or "Cantrips (9th)" */ + + public CreatureSpells(Integer rank, List spells) { + this(rank, null, null, spells); + } + + /** True if these are cantrip spells */ + public boolean isCantrips() { + return knownRank != null && knownRank == 0; + } + + /** The rank for this set of spells, with appropriate cantrip handling. e.g. "5th", or "Cantrips (9th)" */ public String rank() { - return join(" ", - baseRank == 0 ? "Cantrips" : toOrdinal(baseRank), parenthesize(toOrdinal(knownRank))); + if (knownRank == null) { + return ""; + } + return isCantrips() ? "Cantrips " + parenthesize(toOrdinal(cantripRank)) : toOrdinal(knownRank); } @Override public String toString() { return join(" ", - "**%s**".formatted(rank()), + format("**%s**", rank()), join(", ", spells), format("(%d slots)", slots)); } diff --git a/src/main/resources/templates/toolsPf2e/creature2md.txt b/src/main/resources/templates/toolsPf2e/creature2md.txt index 8527e78f9..d54a7a554 100644 --- a/src/main/resources/templates/toolsPf2e/creature2md.txt +++ b/src/main/resources/templates/toolsPf2e/creature2md.txt @@ -46,6 +46,8 @@ aliases: !}{#if spells.constantRanks}; {/if}{#each spells.constantRanks}{! !}**Constant ({it.rank})** {it.spells join ", "}{#if it_hasNext}; {/if}{! !}{/each} +{/for}{#for rituals in ritualCasting} +- **{rituals.name}** {#if rituals.dc}DC {rituals.dc}; {/if}{rituals.ranks join "; "} {/for}{#each attacks} {it} {/each}{#each abilities.bottom}