Skip to content

Commit

Permalink
✨ Add rituals to pf2e creatures (#228)
Browse files Browse the repository at this point in the history
  • Loading branch information
miscoined committed Jun 16, 2024
1 parent e128ca4 commit ab84b80
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 25 deletions.
2 changes: 1 addition & 1 deletion src/main/java/dev/ebullient/convert/StringUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -182,21 +183,32 @@ private List<QuteCreature.CreatureSpellcasting> getSpellcastingFrom(JsonNode sou
.toList();
}

private List<QuteCreature.CreatureRitualCasting> 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,
/**
Expand All @@ -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),
Expand All @@ -244,8 +270,8 @@ private List<QuteCreature.CreatureSpells> 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();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<CreatureSpellcasting> spellcasting;
/** The creature's ritual casting capabilities, as a list of {@link QuteCreature.CreatureRitualCasting} */
public final List<CreatureRitualCasting> ritualCasting;

public QuteCreature(Pf2eSources sources, String text, Tags tags,
public QuteCreature(
Pf2eSources sources, String text, Tags tags,
Collection<String> traits, List<String> aliases,
String description, Integer level, Integer perception,
QuteDataDefenses defenses, CreatureLanguages languages, CreatureSkills skills,
List<CreatureSense> senses, Map<String, Integer> abilityMods,
List<String> items, QuteDataSpeed speed,
List<QuteInlineAttack> attacks, CreatureAbilities abilities,
List<CreatureSpellcasting> spellcasting) {
List<CreatureSpellcasting> spellcasting, List<CreatureRitualCasting> ritualCasting) {
super(sources, text, tags);
this.traits = traits;
this.aliases = aliases;
Expand All @@ -91,6 +94,7 @@ public QuteCreature(Pf2eSources sources, String text, Tags tags,
this.attacks = attacks;
this.abilities = abilities;
this.spellcasting = spellcasting;
this.ritualCasting = ritualCasting;
}

/**
Expand Down Expand Up @@ -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
Expand All @@ -188,6 +192,24 @@ public record CreatureAbilities(
List<QuteAbilityOrAffliction> 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<CreatureSpells> 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.
*
Expand All @@ -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}
Expand Down Expand Up @@ -249,27 +271,39 @@ public String formattedStats() {
* <b>4th</b> <a href="#">confusion</a>, <a href="#">phantasmal killer</a> (2 slots)
* </blockquote>
*
* @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<CreatureSpellReference> 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<CreatureSpellReference> 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));
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/templates/toolsPf2e/creature2md.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down

0 comments on commit ab84b80

Please sign in to comment.