From da54434607363dc98c02dd1e1cb063df633d11d6 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 1 Feb 2025 20:20:44 -0500 Subject: [PATCH] Add support for disabling special resource renewal --- src/module/actor/base.ts | 16 ++++++------ .../rules/rule-element/special-resource.ts | 18 +++++++++++++ src/module/system/schema-data-fields.ts | 26 ++++++++++++++++++- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/module/actor/base.ts b/src/module/actor/base.ts index d0553d554a7..b6412e35023 100644 --- a/src/module/actor/base.ts +++ b/src/module/actor/base.ts @@ -577,15 +577,15 @@ class ActorPF2e 0; } + } - // Restore special resources - for (const resource of Object.values(this.synthetics.resources)) { - const updates = await resource.update(resource.max, { save: false, checkLevel: true }); - commitData.itemCreates.push(...updates.itemCreates); - commitData.itemUpdates.push(...updates.itemUpdates); - if (updates.itemCreates.length || updates.itemUpdates.length) { - commitData.affected.resources.push(resource.slug); - } + // Restore special resources based on elapsed time + for (const resource of Object.values(this.synthetics.resources)) { + const updates = await resource.renewUses(elapsed); + commitData.itemCreates.push(...updates.itemCreates); + commitData.itemUpdates.push(...updates.itemUpdates); + if (updates.itemCreates.length || updates.itemUpdates.length) { + commitData.affected.resources.push(resource.slug); } } diff --git a/src/module/rules/rule-element/special-resource.ts b/src/module/rules/rule-element/special-resource.ts index 57052ebbc46..8adde609bca 100644 --- a/src/module/rules/rule-element/special-resource.ts +++ b/src/module/rules/rule-element/special-resource.ts @@ -5,6 +5,7 @@ import { applyActorUpdate } from "@actor/helpers.ts"; import type { ActorCommitData } from "@actor/types.ts"; import { ItemProxyPF2e, PhysicalItemPF2e } from "@item"; import type { PhysicalItemSource } from "@item/base/data/index.ts"; +import { StrictChoicesField } from "@system/schema-data-fields.ts"; import { sluggify } from "@util"; import { createBatchRuleElementUpdate } from "../helpers.ts"; import { RuleElementPF2e, type RuleElementOptions } from "./base.ts"; @@ -44,6 +45,12 @@ class SpecialResourceRuleElement extends RuleElementPF2e label: "PF2E.UUID.Label", }), level: new ResolvableValueField({ required: false, nullable: true, initial: null }), + renew: new StrictChoicesField({ + required: false, + nullable: false, + choices: ["daily", false], + initial: "daily", + }), }; } @@ -96,6 +103,14 @@ class SpecialResourceRuleElement extends RuleElementPF2e } } + /** Returns data that when applied updates this resources uses based on renewal rules */ + async renewUses(duration: "turn" | "round" | "day"): Promise { + if (duration === "day" && this.renew !== false) { + return this.update(this.max, { save: false, checkLevel: true }); + } + return { actorUpdates: null, itemCreates: [], itemUpdates: [] }; + } + /** If an item uuid is specified, create it when this resource is first attached */ override async preCreate({ tempItems, pendingItems }: RuleElementPF2e.PreCreateParams): Promise { if (!this.test()) return; @@ -209,6 +224,7 @@ type SpecialResourceSource = RuleElementSource & { max?: unknown; itemUUID?: unknown; level?: unknown; + renew?: unknown; }; type SpecialResourceSchema = RuleElementSchema & { @@ -220,6 +236,8 @@ type SpecialResourceSchema = RuleElementSchema & { itemUUID: fields.DocumentUUIDField; /** If itemUUID exists, determines the level of the granted item */ level: ResolvableValueField; + /** Determines if the resource is rewnewable. Defaults to "daily" */ + renew: StrictChoicesField; }; interface SpecialResourceUpdateOptions { diff --git a/src/module/system/schema-data-fields.ts b/src/module/system/schema-data-fields.ts index 9bb23f0b488..ecf1355180d 100644 --- a/src/module/system/schema-data-fields.ts +++ b/src/module/system/schema-data-fields.ts @@ -1,5 +1,5 @@ import { Predicate, PredicateStatement, RawPredicate, StatementValidator } from "@system/predication.ts"; -import { SlugCamel, sluggify } from "@util"; +import { SlugCamel, objectHasKey, sluggify } from "@util"; import * as R from "remeda"; import type DataModel from "types/foundry/common/abstract/data.d.ts"; import type { @@ -164,6 +164,29 @@ class StrictObjectField< } } +/** A field that allows nothing except for the provided choices */ +class StrictChoicesField< + TChoices extends JSONValue, + TRequired extends boolean = true, + TNullable extends boolean = false, + THasInitial extends boolean = true, +> extends fields.DataField { + protected override _cast(value: unknown): unknown { + return value; + } + + protected override _validateType(value: unknown): void { + if (this.options.nullable && value === null) return; + + const choices = + this.options.choices instanceof Function ? this.options.choices() : (this.options.choices ?? []); + const isValid = Array.isArray(choices) ? choices.includes(value) : objectHasKey(choices, value); + if (!isValid) { + throw new Error(`${value} is not a valid choice`); + } + } +} + class DataUnionField< TField extends DataField, TRequired extends boolean = boolean, @@ -515,6 +538,7 @@ export { SlugField, StrictArrayField, StrictBooleanField, + StrictChoicesField, StrictNumberField, StrictObjectField, StrictSchemaField,