Skip to content

Commit

Permalink
Move calling of RE preUpdateActor to ActorPF2e.updateDocuments (f…
Browse files Browse the repository at this point in the history
  • Loading branch information
stwlam committed Dec 29, 2022
1 parent 68d462f commit 78d3fd6
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 23 deletions.
31 changes: 18 additions & 13 deletions src/module/actor/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ChatMessagePF2e } from "@module/chat-message";
import { OneToThree, Size } from "@module/data";
import { preImportJSON } from "@module/doc-helpers";
import { RuleElementSynthetics } from "@module/rules";
import { processPreUpdateActorHooks } from "@module/rules/helpers";
import { RuleElementPF2e } from "@module/rules/rule-element/base";
import { RollOptionRuleElement } from "@module/rules/rule-element/roll-option";
import { LocalizePF2e } from "@module/system/localize";
Expand Down Expand Up @@ -443,6 +444,23 @@ class ActorPF2e extends Actor<TokenDocumentPF2e, ItemTypeMap> {
return super.createDocuments(data, context);
}

static override updateDocuments<T extends foundry.abstract.Document>(
this: ConstructorOf<T>,
updates?: DocumentUpdateData<T>[],
context?: DocumentModificationContext
): Promise<T[]>;
static override async updateDocuments(
updates: DocumentUpdateData<ActorPF2e>[] = [],
context: DocumentModificationContext = {}
): Promise<Actor[]> {
// Process rule element hooks for each actor update
for (const changed of updates) {
processPreUpdateActorHooks(changed, { pack: context.pack ?? null });
}

return super.updateDocuments(updates, context);
}

protected override _initialize(): void {
this.rules = [];
this.conditions = new Map();
Expand Down Expand Up @@ -1307,19 +1325,6 @@ class ActorPF2e extends Actor<TokenDocumentPF2e, ItemTypeMap> {
if (hpChange !== 0 && !levelChanged) options.damageTaken = hpChange;
}

// Run preUpdateActor rule element callbacks
type WithPreUpdateActor = RuleElementPF2e & { preUpdateActor: NonNullable<RuleElementPF2e["preUpdateActor"]> };
const rules = this.rules.filter((r): r is WithPreUpdateActor => !!r.preUpdateActor);
if (rules.length > 0) {
const clone = this.clone(changed, { keepId: true });
this.flags.pf2e.rollOptions = clone.flags.pf2e.rollOptions;
for (const rule of rules) {
if (this.items.has(rule.item.id)) {
await rule.preUpdateActor();
}
}
}

await super._preUpdate(changed, options, user);
}

Expand Down
40 changes: 40 additions & 0 deletions src/module/rules/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
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";

Expand Down Expand Up @@ -80,6 +83,42 @@ 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,
Expand All @@ -89,4 +128,5 @@ export {
extractRollSubstitutions,
extractRollTwice,
isBracketedValue,
processPreUpdateActorHooks,
};
2 changes: 1 addition & 1 deletion src/module/rules/rule-element/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ interface RuleElementPF2e {
afterRoll?(params: RuleElementPF2e.AfterRollParams): Promise<void>;

/** Runs before the rule's parent item's owning actor is updated */
preUpdateActor?(): Promise<void>;
preUpdateActor?(): Promise<{ create: ItemSourcePF2e[]; delete: string[] }>;

/**
* Runs before this rules element's parent item is created. The item is temporarilly constructed. A rule element can
Expand Down
16 changes: 10 additions & 6 deletions src/module/rules/rule-element/grant-item/rule-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,19 +164,21 @@ class GrantItemRuleElement extends RuleElementPF2e {
}

/** Grant an item if this rule element permits it and the predicate passes */
override async preUpdateActor(): Promise<void> {
if (!this.reevaluateOnUpdate) return;
override async preUpdateActor(): Promise<{ create: ItemSourcePF2e[]; delete: string[] }> {
const noAction = { create: [], delete: [] };

if (!this.reevaluateOnUpdate) return noAction;

if (this.grantedId && this.actor.items.has(this.grantedId)) {
if (!this.test()) {
await this.actor.deleteEmbeddedDocuments("Item", [this.grantedId], { render: false });
return { create: [], delete: [this.grantedId] };
}
return;
return noAction;
}

const itemSource = this.item.toObject();
const ruleSource = itemSource.system.rules[this.sourceIndex ?? -1];
if (!ruleSource) return;
if (!ruleSource) return noAction;

const pendingItems: ItemSourcePF2e[] = [];
const context = { parent: this.actor, render: false };
Expand All @@ -185,8 +187,10 @@ class GrantItemRuleElement extends RuleElementPF2e {
if (pendingItems.length > 0) {
const updatedGrants = itemSource.flags.pf2e?.itemGrants ?? {};
await this.item.update({ "flags.pf2e.itemGrants": updatedGrants }, { render: false });
await this.actor.createEmbeddedDocuments("Item", pendingItems, context);
return { create: pendingItems, delete: [] };
}

return noAction;
}

#getOnDeleteActions(data: GrantItemSource): Partial<OnDeleteActions> | null {
Expand Down
7 changes: 4 additions & 3 deletions types/foundry/common/abstract/document.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,11 @@ declare global {
* const updated = await Actor.updateDocuments([{_id: actor.id, name: "New Name"}], {pack: "mymodule.mypack"});
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static updateDocuments<T extends ConstructorOf<any>>(
updates?: DocumentUpdateData<InstanceType<T>>[],
static updateDocuments<T extends Document>(
this: ConstructorOf<T>,
updates?: DocumentUpdateData<T>[],
context?: DocumentModificationContext
): Promise<InstanceType<T>[]>;
): Promise<T[]>;

/**
* Delete one or multiple existing Documents using an array of provided ids.
Expand Down

0 comments on commit 78d3fd6

Please sign in to comment.