Skip to content

Commit

Permalink
Rewrite creation of IWR labels to allow for any number of `exceptions…
Browse files Browse the repository at this point in the history
…` or `doubleVs`es (#18156)
  • Loading branch information
stwlam authored Jan 31, 2025
1 parent 21ea2f8 commit a50d2b3
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 93 deletions.
115 changes: 47 additions & 68 deletions src/module/actor/data/iwr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import { isObject, objectHasKey, setHasElement } from "@util";
abstract class IWR<TType extends IWRType> {
readonly type: TType;

declare value?: number;

readonly exceptions: IWRException<TType>[];

declare readonly doubleVs?: IWRException<TType>[];

/** A definition for a custom IWR */
readonly definition: Predicate | null;

Expand All @@ -20,6 +24,12 @@ abstract class IWR<TType extends IWRType> {

protected abstract readonly typeLabels: Record<TType, string>;

static get disjuncter(): Intl.ListFormat {
return (this.#disjunctor ??= new Intl.ListFormat(game.i18n.lang, { style: "long", type: "disjunction" }));
}

static #disjunctor: Intl.ListFormat | null = null;

constructor(data: IWRConstructorData<TType>) {
this.type = data.type;
this.exceptions = fu.deepClone(data.exceptions ?? []);
Expand All @@ -28,17 +38,41 @@ abstract class IWR<TType extends IWRType> {
this.#customLabel = this.type === "custom" ? (data.customLabel ?? null) : null;
}

abstract get label(): string;
get label(): string {
return this.#createLabel({ withValue: this.value !== undefined });
}

/** A label showing the type, exceptions, and doubleVs but no value (in case of weaknesses and resistances) */
get applicationLabel(): string {
return this.#createLabel({ withValue: false });
}

/** Create a possibly complex IWR label with exceptions and types for which the value doubles. */
#createLabel({ withValue }: { withValue: boolean }): string {
const type = this.typeLabel;
const exceptions = this.createFormatData({ list: this.exceptions, prefix: "exception" });
const key = `Exceptions${this.exceptions.length}DoubleVs0`;
const exceptions = IWR.disjuncter.format(
this.exceptions.map((e) => game.i18n.localize(typeof e === "string" ? this.typeLabels[e] : e.label)),
);
const doubleVs = IWR.disjuncter.format(
this.doubleVs?.map((e) => game.i18n.localize(typeof e === "string" ? this.typeLabels[e] : e.label)) ?? [],
);
const key =
this.exceptions.length > 0
? this.doubleVs?.length
? "ExceptionsDoubleVs"
: "Exceptions"
: this.doubleVs?.length
? "DoubleVs"
: "Simple";
const value = withValue ? (this.value ?? "") : "";

// Remove extra spacing from localization strings with unused {value} placeholders
return game.i18n
.format(`PF2E.Damage.IWR.CompositeLabel.${key}`, { type, ...exceptions, value: "" })
.format(`PF2E.Damage.IWR.CompositeLabel.${key}`, {
type,
value,
exceptions,
doubleVs,
})
.replace(/\s+/g, " ")
.trim();
}
Expand Down Expand Up @@ -230,23 +264,6 @@ abstract class IWR<TType extends IWRType> {
};
}

/** Construct an object argument for Localization#format (see also PF2E.Actor.IWR.CompositeLabel in en.json) */
protected createFormatData({
list,
prefix,
}: {
list: IWRException<TType>[];
prefix: string;
}): Record<string, string> {
return list
.slice(0, 4)
.map((exception, index) => {
const label = typeof exception === "string" ? this.typeLabels[exception] : exception.label;
return { [`${prefix}${index + 1}`]: game.i18n.localize(label) };
})
.reduce((accum, obj) => ({ ...accum, ...obj }), {});
}

test(statements: string[] | Set<string>): boolean {
return this.predicate.test(statements);
}
Expand All @@ -265,10 +282,9 @@ type IWRDisplayData<TType extends IWRType> = Pick<IWR<TType>, "type" | "exceptio
class Immunity extends IWR<ImmunityType> implements ImmunitySource {
protected readonly typeLabels = CONFIG.PF2E.immunityTypes;

/** No value on immunities, so the full label is the same as the application label */
get label(): string {
return this.applicationLabel;
}
declare value?: never;

declare readonly doubleVs?: never;
}

interface IWRSource<TType extends IWRType = IWRType> {
Expand All @@ -281,21 +297,15 @@ type ImmunitySource = IWRSource<ImmunityType>;
class Weakness extends IWR<WeaknessType> implements WeaknessSource {
protected readonly typeLabels = CONFIG.PF2E.weaknessTypes;

value: number;
declare readonly doubleVs?: never;

override value: number;

constructor(data: IWRConstructorData<WeaknessType> & { value: number }) {
super(data);
this.value = data.value;
}

get label(): string {
const type = this.typeLabel;
const exceptions = this.createFormatData({ list: this.exceptions, prefix: "exception" });
const key = `Exceptions${this.exceptions.length}DoubleVs0`;

return game.i18n.format(`PF2E.Damage.IWR.CompositeLabel.${key}`, { type, value: this.value, ...exceptions });
}

override toObject(): Readonly<WeaknessDisplayData> {
return {
...super.toObject(),
Expand All @@ -313,9 +323,9 @@ interface WeaknessSource extends IWRSource<WeaknessType> {
class Resistance extends IWR<ResistanceType> implements ResistanceSource {
protected readonly typeLabels = CONFIG.PF2E.resistanceTypes;

value: number;
override value: number;

readonly doubleVs: IWRException<ResistanceType>[];
override readonly doubleVs: IWRException<ResistanceType>[];

constructor(
data: IWRConstructorData<ResistanceType> & { value: number; doubleVs?: IWRException<ResistanceType>[] },
Expand All @@ -325,37 +335,6 @@ class Resistance extends IWR<ResistanceType> implements ResistanceSource {
this.doubleVs = fu.deepClone(data.doubleVs ?? []);
}

get label(): string {
const type = this.typeLabel;
const exceptions = this.createFormatData({ list: this.exceptions, prefix: "exception" });
const doubleVs = this.createFormatData({ list: this.doubleVs, prefix: "doubleVs" });
const key = `Exceptions${this.exceptions.length}DoubleVs${this.doubleVs.length}`;

return game.i18n.format(`PF2E.Damage.IWR.CompositeLabel.${key}`, {
type,
value: this.value,
...exceptions,
...doubleVs,
});
}

override get applicationLabel(): string {
const type = this.typeLabel;
const exceptions = this.createFormatData({ list: this.exceptions, prefix: "exception" });
const doubleVs = this.createFormatData({ list: this.doubleVs, prefix: "doubleVs" });
const key = `Exceptions${this.exceptions.length}DoubleVs${this.doubleVs.length}`;

return game.i18n
.format(`PF2E.Damage.IWR.CompositeLabel.${key}`, {
type,
value: "",
...exceptions,
...doubleVs,
})
.replace(/\s+/g, " ")
.trim();
}

override toObject(): ResistanceDisplayData {
return {
...super.toObject(),
Expand Down
29 changes: 4 additions & 25 deletions static/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1211,31 +1211,10 @@
"weakness": "Weak to {type}: {adjustment}"
},
"CompositeLabel": {
"Exceptions0DoubleVs0": "{type} {value}",
"Exceptions0DoubleVs1": "{type} {value} (double resistance vs. {doubleVs1})",
"Exceptions0DoubleVs2": "{type} {value} (double resistance vs. {doubleVs1} or {doubleVs2})",
"Exceptions0DoubleVs3": "{type} {value} (double resistance vs. {doubleVs1}, {doubleVs2}, or {doubleVs3})",
"Exceptions0DoubleVs4": "{type} {value} (double resistance vs. {doubleVs1}, {doubleVs2}, {doubleVs3}, or {doubleVs4})",
"Exceptions1DoubleVs0": "{type} {value} (except {exception1})",
"Exceptions1DoubleVs1": "{type} {value} (except {exception1}; double resistance vs. {doubleVs1})",
"Exceptions1DoubleVs2": "{type} {value} (except {exception1}; double resistance vs. {doubleVs1} or {doubleVs2})",
"Exceptions1DoubleVs3": "{type} {value} (except {exception1}; double resistance vs. {doubleVs1}, {doubleVs2}, or {doubleVs3})",
"Exceptions1DoubleVs4": "{type} {value} (except {exception1}; double resistance vs. {doubleVs1}, {doubleVs2}, {doubleVs3}, or {doubleVs4})",
"Exceptions2DoubleVs0": "{type} {value} (except {exception1} or {exception2})",
"Exceptions2DoubleVs1": "{type} {value} (except {exception1} or {exception2}; double resistance vs. {doubleVs1})",
"Exceptions2DoubleVs2": "{type} {value} (except {exception1} or {exception2}; double resistance vs. {doubleVs1} or {doubleVs2})",
"Exceptions2DoubleVs3": "{type} {value} (except {exception1} or {exception2}; double resistance vs. {doubleVs1}, {doubleVs2}, or {doubleVs3})",
"Exceptions2DoubleVs4": "{type} {value} (except {exception1} or {exception2}; double resistance vs. {doubleVs1}, {doubleVs2}, {doubleVs3}, or {doubleVs4})",
"Exceptions3DoubleVs0": "{type} {value} (except {exception1}, {exception2}, or {exception3})",
"Exceptions3DoubleVs1": "{type} {value} (except {exception1}, {exception2}, or {exception3}; double resistance vs. {doubleVs1})",
"Exceptions3DoubleVs2": "{type} {value} (except {exception1}, {exception2}, or {exception3}; double resistance vs. {doubleVs1} or {doubleVs2})",
"Exceptions3DoubleVs3": "{type} {value} (except {exception1}, {exception2}, or {exception3}; double resistance vs. {doubleVs1}, {doubleVs2}, or {doubleVs3})",
"Exceptions3DoubleVs4": "{type} {value} (except {exception1}, {exception2}, or {exception3}; double resistance vs. {doubleVs1}, {doubleVs2}, {doubleVs3}, or {doubleVs4})",
"Exceptions4DoubleVs0": "{type} {value} (except {exception1}, {exception2}, {exception3}, or {exception4})",
"Exceptions4DoubleVs1": "{type} {value} (except {exception1}, {exception2}, {exception3}, or {exception4}; double resistance vs. {doubleVs1})",
"Exceptions4DoubleVs2": "{type} {value} (except {exception1}, {exception2}, {exception3}, or {exception4}; double resistance vs. {doubleVs1} or {doubleVs2})",
"Exceptions4DoubleVs3": "{type} {value} (except {exception1}, {exception2}, {exception3}, or {exception4}; double resistance vs. {doubleVs1}, {doubleVs2}, or {doubleVs3})",
"Exceptions4DoubleVs4": "{type} {value} (except {exception1}, {exception2}, {exception3}, or {exception4}; double resistance vs. {doubleVs1}, {doubleVs2}, {doubleVs3}, or {doubleVs4})"
"Simple": "{type} {value}",
"DoubleVs": "{type} {value} (double resistance vs. {doubleVs})",
"Exceptions": "{type} {value} (except {exceptions})",
"ExceptionsDoubleVs": "{type} {value} (except {exceptions}; double resistance vs. {doubleVs})"
},
"Type": {
"abysium": "abysium",
Expand Down

0 comments on commit a50d2b3

Please sign in to comment.