Compare commits

..

2 Commits

Author SHA1 Message Date
Lukas
3e62e54274 Strip all angle brackets in PF2e attack traits and damage
All checks were successful
CI / check (push) Successful in 2m23s
CI / build-image (push) Successful in 17s
Broaden stripDiceBrackets to stripAngleBrackets to handle all
PF2e tools angle-bracket formatting (e.g. <10 feet>, <15 feet>),
not just dice notation. Also strip in damage text.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:34:28 +02:00
Lukas
12a089dfd7 Fix PF2e condition tooltip descriptions and sort picker alphabetically
Correct inaccurate PF2e condition descriptions against official AoN
rules (blinded, deafened, confused, grabbed, hidden, paralyzed,
unconscious, drained, fascinated, enfeebled, stunned). Sort condition
picker alphabetically per game system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:29:49 +02:00
3 changed files with 51 additions and 18 deletions

View File

@@ -114,8 +114,8 @@ describe("normalizePf2eBestiary", () => {
});
});
describe("attack traits formatting", () => {
it("strips angle-bracket dice notation from traits", () => {
describe("attack formatting", () => {
it("strips angle brackets from traits", () => {
const [creature] = normalizePf2eBestiary({
creature: [
minimalCreature({
@@ -140,6 +140,34 @@ describe("normalizePf2eBestiary", () => {
}),
);
});
it("strips angle brackets from reach values in traits", () => {
const [creature] = normalizePf2eBestiary({
creature: [
minimalCreature({
attacks: [
{
name: "tentacle",
range: "Melee",
attack: 18,
traits: ["agile", "chaotic", "magical", "reach <10 feet>"],
damage: "2d8+6 piercing",
},
],
}),
],
});
const attack = creature.attacks?.[0];
expect(attack).toBeDefined();
expect(attack?.segments[0]).toEqual(
expect.objectContaining({
type: "text",
value: expect.stringContaining(
"(agile, chaotic, magical, reach 10 feet)",
),
}),
);
});
});
describe("resistances formatting", () => {

View File

@@ -79,8 +79,8 @@ function capitalize(s: string): string {
return s.charAt(0).toUpperCase() + s.slice(1);
}
function stripDiceBrackets(s: string): string {
return s.replaceAll(/<(\d*d\d+)>/g, "$1");
function stripAngleBrackets(s: string): string {
return s.replaceAll(/<([^>]+)>/g, "$1");
}
function makeCreatureId(source: string, name: string): CreatureId {
@@ -263,9 +263,11 @@ function normalizeAttacks(
const attackMod = a.attack == null ? "" : ` +${a.attack}`;
const traits =
a.traits && a.traits.length > 0
? ` (${a.traits.map((t) => stripDiceBrackets(stripTags(t))).join(", ")})`
? ` (${a.traits.map((t) => stripAngleBrackets(stripTags(t))).join(", ")})`
: "";
const damage = a.damage
? `, ${stripAngleBrackets(stripTags(a.damage))}`
: "";
const damage = a.damage ? `, ${stripTags(a.damage)}` : "";
return {
name: capitalize(stripTags(a.name)),
segments: [

View File

@@ -77,7 +77,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
description5e:
"Can't see. Auto-fail sight checks. Attacks have Disadvantage; attacks against have Advantage.",
descriptionPf2e:
"Can't see. All terrain is difficult terrain. 4 status penalty to Perception checks involving sight. Immune to visual effects. Auto-fail checks requiring sight. Off-guard.",
"Can't see. All terrain is difficult terrain. Auto-fail checks requiring sight. Immune to visual effects. Overrides dazzled.",
iconName: "EyeOff",
color: "neutral",
},
@@ -98,7 +98,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
description: "Can't hear. Auto-fail hearing checks.",
description5e: "Can't hear. Auto-fail hearing checks.",
descriptionPf2e:
"Can't hear. 2 status penalty to Perception checks and Initiative. Auto-fail hearing checks. Immune to auditory effects.",
"Can't hear. Auto-critically-fail hearing checks. 2 status penalty to Perception. Auditory actions require DC 5 flat check. Immune to auditory effects.",
iconName: "EarOff",
color: "neutral",
},
@@ -166,7 +166,8 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
"Incapacitated. Can't move or speak. Auto-fail Str/Dex saves. Attacks against have Advantage. Hits within 5 ft. are critical.",
description5e:
"Incapacitated. Can't move or speak. Auto-fail Str/Dex saves. Attacks against have Advantage. Hits within 5 ft. are critical.",
descriptionPf2e: "Can't act. Off-guard. 4 status penalty to AC.",
descriptionPf2e:
"Can't act. Off-guard. Can only Recall Knowledge or use mental actions.",
iconName: "ZapOff",
color: "yellow",
},
@@ -243,7 +244,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
description5e:
"Incapacitated. Can't move. Can speak only falteringly. Auto-fail Str/Dex saves. Attacks against have Advantage.",
descriptionPf2e:
"Can't act. X value to actions per turn while the value counts down.",
"Can't act. Lose X total actions across turns, then the condition ends. Overrides slowed.",
iconName: "Sparkles",
color: "yellow",
valued: true,
@@ -256,7 +257,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
description5e:
"Incapacitated. Speed 0. Can't move or speak. Unaware of surroundings. Drops held items, falls Prone. Auto-fail Str/Dex saves. Attacks against have Advantage. Hits within 5 ft. are critical.",
descriptionPf2e:
"Can't act. Off-guard. 4 status penalty to AC. 3 to Perception. Fall prone, drop items.",
"Can't act. Off-guard. Blinded. 4 status penalty to AC, Perception, and Reflex saves. Fall prone, drop items.",
iconName: "Moon",
color: "indigo",
},
@@ -290,7 +291,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
description: "",
description5e: "",
descriptionPf2e:
"Off-guard. Can't Delay, Ready, or use reactions. GM determines targets randomly. Flat check DC 11 to act normally each turn.",
"Off-guard. Can't Delay, Ready, or use reactions. Must Strike or cast offensive cantrips at random targets. DC 11 flat check when damaged to end.",
iconName: "CircleHelp",
color: "pink",
systems: ["pf2e"],
@@ -335,7 +336,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
description: "",
description5e: "",
descriptionPf2e:
"X status penalty to Con-based checks and DCs. Lose X × Hit Die in max HP. Decreases by 1 on full night's rest.",
"X status penalty to Con-based checks and DCs. Lose X × level in max HP. Decreases by 1 on full night's rest.",
iconName: "Droplets",
color: "red",
systems: ["pf2e"],
@@ -359,7 +360,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
description: "",
description5e: "",
descriptionPf2e:
"X status penalty to Str-based rolls, including melee attack and damage rolls.",
"X status penalty to Str-based rolls and DCs, including melee attack and damage rolls and Athletics checks.",
iconName: "TrendingDown",
color: "amber",
systems: ["pf2e"],
@@ -371,7 +372,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
description: "",
description5e: "",
descriptionPf2e:
"2 status penalty to all checks. Can't use hostile actions. Ends if hostile action is used against you.",
"2 status penalty to Perception and skill checks. Can't use concentrate actions unless related to the fascination. Ends if hostile action is used against you or allies.",
iconName: "Eye",
color: "violet",
systems: ["pf2e"],
@@ -404,7 +405,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
description: "",
description5e: "",
descriptionPf2e:
"Immobilized. Off-guard. Can't use actions with the move trait unless to Break Grapple.",
"Off-guard. Immobilized. Manipulate actions require DC 5 flat check or are wasted.",
iconName: "Hand",
color: "neutral",
systems: ["pf2e"],
@@ -415,7 +416,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
description: "",
description5e: "",
descriptionPf2e:
"Known location but can't be seen. DC 11 flat check to target. Can use Seek to find.",
"Known location but can't be seen. Off-guard to that creature. DC 11 flat check to target or miss.",
iconName: "EyeOff",
color: "slate",
systems: ["pf2e"],
@@ -521,5 +522,7 @@ export function getConditionsForEdition(
): readonly ConditionDefinition[] {
return CONDITION_DEFINITIONS.filter(
(d) => d.systems === undefined || d.systems.includes(edition),
);
)
.slice()
.sort((a, b) => a.label.localeCompare(b.label));
}