Skip to content

Commit

Permalink
Add configurability of whether a granted feat/feature should be visua…
Browse files Browse the repository at this point in the history
…lly nested (foundryvtt#17378)
  • Loading branch information
stwlam authored Nov 19, 2024
1 parent a792ef7 commit e78f301
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 27 deletions.
9 changes: 6 additions & 3 deletions src/module/actor/character/feats/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,12 @@ class CharacterFeats<TActor extends CharacterPF2e> extends Collection<FeatGroup<
// put the feats in their feat slots
const feats = this.actor.itemTypes.feat.sort((f1, f2) => f1.sort - f2.sort);
for (const feat of feats.filter((f) => !isBoonOrCurse(f))) {
if (feat.flags.pf2e.grantedBy && !feat.system.location) {
const granter = this.actor.items.get(feat.flags.pf2e.grantedBy.id);
if (granter?.isOfType("feat") && granter.grants.includes(feat)) {
const grantedBy = feat.flags.pf2e.grantedBy;
if (grantedBy && !feat.system.location) {
const granter = this.actor.items.get(grantedBy.id);
const grantsById = granter ? R.mapKeys(granter.flags.pf2e.itemGrants, (_, g) => g.id) : null;
const isNested = grantsById?.[feat.id]?.nested !== false;
if (granter?.isOfType("feat") && granter.grants.includes(feat) && isNested) {
continue;
}
}
Expand Down
24 changes: 14 additions & 10 deletions src/module/actor/character/feats/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ActorPF2e } from "@actor";
import type { FeatPF2e, HeritagePF2e, ItemPF2e } from "@item";
import { FeatOrFeatureCategory } from "@item/feat/types.ts";
import { tupleHasValue } from "@util/misc.ts";
import * as R from "remeda";
import type { FeatBrowserFilterProps, FeatGroupData, FeatLike, FeatSlot } from "./types.ts";

class FeatGroup<TActor extends ActorPF2e = ActorPF2e, TItem extends FeatLike = FeatPF2e> {
Expand Down Expand Up @@ -122,16 +123,19 @@ class FeatGroup<TActor extends ActorPF2e = ActorPF2e, TItem extends FeatLike = F

#getChildSlots(feat: Maybe<ItemPF2e>): FeatSlot<FeatPF2e<ActorPF2e> | HeritagePF2e<ActorPF2e>>[] {
if (!feat?.isOfType("feat")) return [];

return feat.grants.map((grant): FeatSlot<FeatPF2e<ActorPF2e> | HeritagePF2e<ActorPF2e>> => {
return {
id: grant.id,
label: null,
level: grant.system.level?.taken ?? null,
feat: grant,
children: this.#getChildSlots(grant),
};
});
const grantsById = R.mapKeys(feat.flags.pf2e.itemGrants, (_, g) => g.id);

return feat.grants
.filter((g) => grantsById[g.id]?.nested !== false)
.map((grant): FeatSlot<FeatPF2e<ActorPF2e> | HeritagePF2e<ActorPF2e>> => {
return {
id: grant.id,
label: null,
level: grant.system.level?.taken ?? null,
feat: grant,
children: this.#getChildSlots(grant),
};
});
}

/** Returns true if this feat is a valid type for the group */
Expand Down
16 changes: 12 additions & 4 deletions src/module/item/base/data/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ interface OtherTagsOnly {
interface ItemFlagsPF2e extends DocumentFlags {
pf2e: {
rulesSelections: Record<string, string | number | object | null>;
itemGrants: Record<string, ItemGrantData>;
itemGrants: Record<string, ItemGranterData>;
grantedBy: ItemGrantData | null;
[key: string]: unknown;
};
Expand All @@ -53,21 +53,28 @@ interface ItemFlagsPF2e extends DocumentFlags {
interface ItemSourceFlagsPF2e extends DocumentFlags {
pf2e?: {
rulesSelections?: Record<string, string | number | object>;
itemGrants?: Record<string, ItemGrantSource>;
itemGrants?: Record<string, ItemGranterSource>;
grantedBy?: ItemGrantSource | null;
[key: string]: unknown;
};
}

type ItemGrantData = Required<ItemGrantSource>;

interface ItemGrantSource {
/** The ID of a granting or granted item */
id: string;
/** The action taken when the user attempts to delete the item referenced by `id` */
onDelete?: ItemGrantDeleteAction;
}

type ItemGrantData = Required<ItemGrantSource>;

interface ItemGranterSource extends ItemGrantSource {
/** Is this granted item visually nested under its granter: only applies to feats and features */
nested?: boolean | null;
}

interface ItemGranterData extends Required<ItemGranterSource> {}

type ItemGrantDeleteAction = "cascade" | "detach" | "restrict";

type ItemSystemSource = {
Expand Down Expand Up @@ -142,6 +149,7 @@ export type {
ItemGrantDeleteAction,
ItemGrantSource,
ItemSchemaPF2e,
ItemSourceFlagsPF2e,
ItemSystemData,
ItemSystemSource,
ItemTrait,
Expand Down
13 changes: 10 additions & 3 deletions src/module/rules/rule-element/grant-item/rule-element.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ActorPF2e, ActorType } from "@actor";
import { ConditionPF2e, ItemPF2e, ItemProxyPF2e } from "@item";
import { ItemSourcePF2e } from "@item/base/data/index.ts";
import { ItemGrantDeleteAction } from "@item/base/data/system.ts";
import type { ItemSourcePF2e } from "@item/base/data/index.ts";
import { ItemGrantDeleteAction, ItemSourceFlagsPF2e } from "@item/base/data/system.ts";
import { PHYSICAL_ITEM_TYPES } from "@item/physical/values.ts";
import { SlugField, StrictArrayField } from "@system/schema-data-fields.ts";
import { ErrorPF2e, isObject, setHasElement, sluggify, tupleHasValue } from "@util";
Expand Down Expand Up @@ -85,6 +85,7 @@ class GrantItemRuleElement extends RuleElementPF2e<GrantItemSchema> {
initial: true,
label: "PF2E.RuleEditor.GrantItem.AllowDuplicate",
}),
nestUnderGranter: new fields.BooleanField({ required: false, nullable: false, initial: undefined }),
alterations: new StrictArrayField(new fields.EmbeddedDataField(ItemAlteration)),
track: new fields.BooleanField(),
};
Expand Down Expand Up @@ -294,7 +295,10 @@ class GrantItemRuleElement extends RuleElementPF2e<GrantItemSchema> {

/** Set flags on granting and grantee items to indicate relationship between the two */
#setGrantFlags(granter: PreCreate<ItemSourcePF2e>, grantee: ItemSourcePF2e | ItemPF2e<ActorPF2e>): void {
const flags = fu.mergeObject(granter.flags ?? {}, { pf2e: { itemGrants: {} } });
const flags: ItemSourceFlagsPF2e & { pf2e: { itemGrants: Record<string, object> } } = fu.mergeObject(
granter.flags ?? {},
{ pf2e: { itemGrants: {} } },
);
if (!this.flag) throw ErrorPF2e("Unexpected failure looking up RE flag key");
flags.pf2e.itemGrants[this.flag] = {
// The granting item records the granted item's ID in an array at `flags.pf2e.itemGrants`
Expand All @@ -303,6 +307,9 @@ class GrantItemRuleElement extends RuleElementPF2e<GrantItemSchema> {
// Default to "detach" (do nothing).
onDelete: this.onDeleteActions?.grantee ?? "detach",
};
if (granter.type === "feat" && grantee.type === "feat" && this.nestUnderGranter === false) {
flags.pf2e.itemGrants[this.flag].nested = this.nestUnderGranter;
}

// The granted item records its granting item's ID at `flags.pf2e.grantedBy`
const grantedBy = {
Expand Down
16 changes: 9 additions & 7 deletions src/module/rules/rule-element/grant-item/schema.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
import type { SlugField, StrictArrayField } from "@system/schema-data-fields.ts";
import type { BooleanField, EmbeddedDataField, StringField } from "types/foundry/common/data/fields.d.ts";
import type * as fields from "types/foundry/common/data/fields.d.ts";
import type { RuleElementSchema } from "../data.ts";
import type { ItemAlteration } from "../item-alteration/alteration.ts";

type GrantItemSchema = RuleElementSchema & {
/** The UUID of the item to grant: must be a compendium or world item */
uuid: StringField<string, string, true, false, false>;
uuid: fields.StringField<string, string, true, false, false>;
/** A flag for referencing the granted item ID in other rule elements */
flag: SlugField<true, true, true>;
/** Permit this grant to be applied during an actor update--if it isn't already granted and the predicate passes */
reevaluateOnUpdate: BooleanField<boolean, boolean, false, false, true>;
reevaluateOnUpdate: fields.BooleanField<boolean, boolean, false, false, true>;
/**
* Instead of creating a new item in the actor's embedded collection, add a "virtual" one. Usable only with
* conditions
*/
inMemoryOnly: BooleanField<boolean, boolean, false, false, true>;
inMemoryOnly: fields.BooleanField<boolean, boolean, false, false, true>;
/** Allow multiple of the same item (as determined by source ID) to be granted */
allowDuplicate: BooleanField<boolean, boolean, false, false, true>;
allowDuplicate: fields.BooleanField<boolean, boolean, false, false, true>;
/** Visually nest this granted item under its granter: only applies to feats and features */
nestUnderGranter: fields.BooleanField<boolean, boolean, false, false, false>;
/** A list of alterations to make on the item before granting it */
alterations: StrictArrayField<EmbeddedDataField<ItemAlteration>>;
alterations: StrictArrayField<fields.EmbeddedDataField<ItemAlteration>>;
/**
* Track a granted physical item from roll options: the sluggified `flag` will serve as a prefix for item roll
* options, which are added to the `all` domain.
*/
track: BooleanField<boolean, boolean, false, false, false>;
track: fields.BooleanField<boolean, boolean, false, false, false>;
};

export type { GrantItemSchema };

0 comments on commit e78f301

Please sign in to comment.