forked from foundryvtt/pf2e
-
Notifications
You must be signed in to change notification settings - Fork 0
/
helpers.ts
132 lines (117 loc) · 5.08 KB
/
helpers.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import { ActorPF2e } from "@actor";
import { DamageDicePF2e, DeferredValueParams, ModifierAdjustment, ModifierPF2e } from "@actor/modifiers";
import { ItemSourcePF2e } from "@item/data";
import { RollNotePF2e } from "@module/notes";
import { DegreeOfSuccessAdjustment } from "@system/degree-of-success";
import { RollTwiceOption } from "@system/rolls";
import { isObject, pick } from "@util";
import { RuleElementPF2e } from "./rule-element";
import { BracketedValue } from "./rule-element/data";
import { DamageDiceSynthetics, RollSubstitution, RollTwiceSynthetic, RuleElementSynthetics } from "./synthetics";
/** Extracts a list of all cloned modifiers across all given keys in a single list. */
function extractModifiers(
synthetics: Pick<RuleElementSynthetics, "modifierAdjustments" | "statisticsModifiers">,
selectors: string[],
options: DeferredValueParams = {}
): ModifierPF2e[] {
const { modifierAdjustments, statisticsModifiers } = synthetics;
const modifiers = Array.from(new Set(selectors))
.flatMap((s) => statisticsModifiers[s] ?? [])
.flatMap((d) => d(options) ?? []);
for (const modifier of modifiers) {
modifier.adjustments = extractModifierAdjustments(modifierAdjustments, selectors, modifier.slug);
}
return modifiers;
}
function extractModifierAdjustments(
adjustmentsRecord: RuleElementSynthetics["modifierAdjustments"],
selectors: string[],
slug: string
): ModifierAdjustment[] {
const adjustments = Array.from(new Set(selectors.flatMap((s) => adjustmentsRecord[s] ?? [])));
return adjustments.filter((a) => [slug, null].includes(a.slug));
}
/** Extracts a list of all cloned notes across all given keys in a single list. */
function extractNotes(rollNotes: Record<string, RollNotePF2e[]>, selectors: string[]) {
return selectors.flatMap((s) => (rollNotes[s] ?? []).map((n) => n.clone()));
}
function extractDamageDice(
deferredDice: DamageDiceSynthetics,
selectors: string[],
options: DeferredValueParams = {}
): DamageDicePF2e[] {
return selectors.flatMap((s) => deferredDice[s] ?? []).flatMap((d) => d(options) ?? []);
}
function extractRollTwice(
rollTwices: Record<string, RollTwiceSynthetic[]>,
selectors: string[],
options: Set<string>
): RollTwiceOption {
const twices = selectors.flatMap((s) => rollTwices[s] ?? []).filter((rt) => rt.predicate?.test(options) ?? true);
if (twices.length === 0) return false;
if (twices.some((rt) => rt.keep === "higher") && twices.some((rt) => rt.keep === "lower")) {
return false;
}
return twices.at(0)?.keep === "higher" ? "keep-higher" : "keep-lower";
}
function extractRollSubstitutions(
substitutions: Record<string, RollSubstitution[]>,
domains: string[],
rollOptions: Set<string>
): RollSubstitution[] {
return domains
.flatMap((d) => deepClone(substitutions[d] ?? []))
.filter((s) => s.predicate?.test(rollOptions) ?? true);
}
function extractDegreeOfSuccessAdjustments(
synthetics: Pick<RuleElementSynthetics, "degreeOfSuccessAdjustments">,
selectors: string[]
): DegreeOfSuccessAdjustment[] {
return Object.values(pick(synthetics.degreeOfSuccessAdjustments, selectors)).flat();
}
function isBracketedValue(value: unknown): value is BracketedValue {
return isObject<{ brackets?: unknown }>(value) && Array.isArray(value.brackets);
}
async function processPreUpdateActorHooks(
changed: DocumentUpdateData<ActorPF2e>,
{ pack }: { pack: string | null }
): Promise<void> {
const actorId = String(changed._id);
const actor = pack ? await game.packs.get(pack)?.getDocument(actorId) : game.actors.get(actorId);
if (!(actor instanceof ActorPF2e)) return;
// Run preUpdateActor rule element callbacks
type WithPreUpdateActor = RuleElementPF2e & {
preUpdateActor: NonNullable<RuleElementPF2e["preUpdateActor"]>;
};
const rules = actor.rules.filter((r): r is WithPreUpdateActor => !!r.preUpdateActor);
if (rules.length === 0) return;
actor.flags.pf2e.rollOptions = actor.clone(changed, { keepId: true }).flags.pf2e.rollOptions;
const createDeletes = (
await Promise.all(
rules.map(
(r): Promise<{ create: ItemSourcePF2e[]; delete: string[] }> =>
actor.items.has(r.item.id) ? r.preUpdateActor() : new Promise(() => ({ create: [], delete: [] }))
)
)
).reduce(
(combined, cd) => ({
create: [...combined.create, ...cd.create],
delete: Array.from(new Set([...combined.delete, ...cd.delete])),
}),
{ create: [], delete: [] }
);
createDeletes.delete = createDeletes.delete.filter((id) => actor.items.has(id));
await actor.createEmbeddedDocuments("Item", createDeletes.create, { keepId: true, render: false });
await actor.deleteEmbeddedDocuments("Item", createDeletes.delete, { render: false });
}
export {
extractDamageDice,
extractDegreeOfSuccessAdjustments,
extractModifierAdjustments,
extractModifiers,
extractNotes,
extractRollSubstitutions,
extractRollTwice,
isBracketedValue,
processPreUpdateActorHooks,
};