Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build out basic structure for doubly-embedded items #12145

Merged
merged 1 commit into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Build out basic structure for doubly-embedded items
  • Loading branch information
stwlam committed Dec 18, 2023
commit 5e899554c3756fb9b45cbc6b30fac29387cc3833
4 changes: 4 additions & 0 deletions build/lib/extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,10 @@ class PackExtractor {
delete (source.system as { specific?: unknown }).specific;
}

if ("subitems" in source.system && source.system.subitems.length === 0) {
delete (source.system as { subitems?: unknown[] }).subitems;
}

if (source.type === "weapon") {
delete (source.system as { property1?: unknown }).property1;
if ("value" in source.system.damage) {
Expand Down
20 changes: 19 additions & 1 deletion build/run-migration.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ActorSourcePF2e } from "@actor/data/index.ts";
import { CREATURE_ACTOR_TYPES } from "@actor/values.ts";
import { ItemSourcePF2e } from "@item/base/data/index.ts";
import { ItemSourcePF2e, isPhysicalData } from "@item/base/data/index.ts";
import { itemIsOfType } from "@item/helpers.ts";
import { PHYSICAL_ITEM_TYPES } from "@item/physical/values.ts";
import { MigrationBase } from "@module/migration/base.ts";
import { MigrationRunnerBase } from "@module/migration/runner/base.ts";
Expand Down Expand Up @@ -168,6 +169,9 @@ async function migrate() {
if (isActorData(source)) {
for (const embedded of source.items) {
embedded.flags ??= {};
if (itemIsOfType(embedded, "armor", "equipment", "shield", "weapon")) {
embedded.system.subitems ??= [];
}
}

const update = await migrationRunner.getUpdatedActor(source, migrationRunner.migrations);
Expand All @@ -184,12 +188,22 @@ async function migrate() {
if (updatedItem.type === "consumable" && !updatedItem.system.spell) {
delete (updatedItem.system as { spell?: object }).spell;
}
if (
isPhysicalData(updatedItem) &&
"subitems" in updatedItem.system &&
updatedItem.system.subitems.length === 0
) {
delete (updatedItem.system as { subitems?: unknown[] }).subitems;
}
pruneFlags(updatedItem);
}

return fu.mergeObject(source, update, { inplace: false, performDeletions: true });
} else if (isItemData(source)) {
source.system.slug = sluggify(source.name);
if (itemIsOfType(source, "armor", "equipment", "shield", "weapon")) {
source.system.subitems ??= [];
}
const update = await migrationRunner.getUpdatedItem(source, migrationRunner.migrations);

delete (source.system as { slug?: string }).slug;
Expand All @@ -198,6 +212,10 @@ async function migrate() {
if (update.type === "consumable" && !update.system.spell) {
delete (update.system as { spell?: null }).spell;
}
if (isPhysicalData(source) && "subitems" in source.system && source.system.subitems.length === 0) {
delete (source.system as { subitems?: unknown[] }).subitems;
}

pruneFlags(source);
pruneFlags(update);

Expand Down
7 changes: 6 additions & 1 deletion src/module/item/armor/data.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PhysicalItemSource } from "@item/base/data/index.ts";
import {
BasePhysicalItemSource,
Investable,
Expand Down Expand Up @@ -25,6 +26,8 @@ interface ArmorSystemSource extends Investable<PhysicalSystemSource> {
runes: ArmorRuneSource;
/** Details of specific magic armor, storing the material and rune state when toggled on */
specific: SpecificArmorData | null;
/** Doubly-embedded adjustments, attachments, talismans etc. */
subitems: PhysicalItemSource[];
/** Usage for armor isn't stored. */
readonly usage?: never;
}
Expand All @@ -42,14 +45,16 @@ type SpecificArmorData = {
};

interface ArmorSystemData
extends Omit<ArmorSystemSource, "bulk" | "hp" | "identification" | "material" | "price" | "temporary" | "usage">,
extends Omit<ArmorSystemSource, SourceOmission>,
Omit<Investable<PhysicalSystemData>, "baseItem" | "traits"> {
runes: ArmorRuneData;
/** Armor is always worn in the "armor" slot. */
usage: WornUsage;
stackGroup: null;
}

type SourceOmission = "bulk" | "hp" | "identification" | "items" | "material" | "price" | "temporary" | "usage";

interface ArmorTraits extends PhysicalItemTraits<ArmorTrait> {
otherTags: OtherArmorTag[];
}
Expand Down
7 changes: 4 additions & 3 deletions src/module/item/base/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -862,11 +862,12 @@ const ItemProxyPF2e = new Proxy(ItemPF2e, {
_target,
args: [source: PreCreate<ItemSourcePF2e>, context?: DocumentConstructionContext<ActorPF2e | null>],
) {
const source = args[0];
const type =
args[0]?.type === "armor" && (args[0].system?.category as string | undefined) === "shield"
source?.type === "armor" && (source.system?.category as string | undefined) === "shield"
? "shield"
: args[0]?.type;
const ItemClass = CONFIG.PF2E.Item.documentClasses[type] ?? ItemPF2e;
: source?.type;
const ItemClass: typeof ItemPF2e = CONFIG.PF2E.Item.documentClasses[type] ?? ItemPF2e;
return new ItemClass(...args);
},
});
Expand Down
29 changes: 15 additions & 14 deletions src/module/item/book/data.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import { EquipmentSystemData, EquipmentSystemSource } from "@item/equipment/data.ts";
import { BasePhysicalItemSource } from "@item/physical/data.ts";
import { EquipmentTrait } from "@item/equipment/data.ts";
import {
BasePhysicalItemSource,
PhysicalItemTraits,
PhysicalSystemData,
PhysicalSystemSource,
} from "@item/physical/data.ts";

type BookSource = BasePhysicalItemSource<"book", BookSystemSource>;
type BookTraits = PhysicalItemTraits<EquipmentTrait>;

type BookSystemSource = EquipmentSystemSource & {
interface BookSystemSource extends PhysicalSystemSource {
traits: BookTraits;
category: "formula" | "spell";
capacity: number;
} & (FormulaBookData | SpellBookData);

type BookSystemData = Omit<BookSystemSource, "hp" | "price"> & EquipmentSystemData;

interface FormulaBookData {
subtype: "formula";
item: ItemUUID[];
contents: ItemUUID[];
}

interface SpellBookData {
subtype: "spell";
item: object[];
}
interface BookSystemData extends Omit<BookSystemSource, SourceOmission>, PhysicalSystemData {}

type SourceOmission = "bulk" | "hp" | "identification" | "material" | "price" | "temporary" | "traits" | "usage";

export type { BookSource, BookSystemData };
10 changes: 6 additions & 4 deletions src/module/item/equipment/data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AttributeString } from "@actor/types.ts";
import { PhysicalItemSource } from "@item/base/data/index.ts";
import {
BasePhysicalItemSource,
Investable,
Expand All @@ -23,13 +24,12 @@ interface EquipmentSystemSource extends Investable<PhysicalSystemSource> {
};

usage: { value: string };
/** Doubly-embedded adjustments, attachments, talismans etc. */
subitems: PhysicalItemSource[];
}

interface EquipmentSystemData
extends Omit<
EquipmentSystemSource,
"bulk" | "hp" | "identification" | "material" | "price" | "temporary" | "usage"
>,
extends Omit<EquipmentSystemSource, SourceOmission>,
Omit<Investable<PhysicalSystemData>, "traits"> {
apex?: {
attribute: AttributeString;
Expand All @@ -38,6 +38,8 @@ interface EquipmentSystemData
stackGroup: null;
}

type SourceOmission = "bulk" | "hp" | "identification" | "items" | "material" | "price" | "temporary" | "usage";

interface EquipmentTraits extends PhysicalItemTraits<EquipmentTrait> {
otherTags: OtherEquipmentTag[];
}
Expand Down
36 changes: 32 additions & 4 deletions src/module/item/physical/document.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type ActorPF2e } from "@actor";
import { ItemPF2e, type ContainerPF2e } from "@item";
import { ItemSummaryData, PhysicalItemSource, TraitChatData } from "@item/base/data/index.ts";
import { ItemPF2e, ItemProxyPF2e, type ContainerPF2e } from "@item";
import { ItemSourcePF2e, ItemSummaryData, PhysicalItemSource, TraitChatData } from "@item/base/data/index.ts";
import { MystifiedTraits } from "@item/base/data/values.ts";
import { isCycle } from "@item/container/helpers.ts";
import { Rarity, Size, ZeroToTwo } from "@module/data.ts";
Expand All @@ -25,9 +25,23 @@ import { getUsageDetails, isEquipped } from "./usage.ts";
import { DENOMINATIONS } from "./values.ts";

abstract class PhysicalItemPF2e<TParent extends ActorPF2e | null = ActorPF2e | null> extends ItemPF2e<TParent> {
// The cached container of this item, if in a container, or null
/** The item in which this item is embedded */
parentItem: PhysicalItemPF2e | null;

/**
* The cached container of this item, if in a container, or null
* @ignore
*/
private declare _container: ContainerPF2e<ActorPF2e> | null;

/** Doubly-embedded adjustments, attachments, talismans etc. */
declare subitems: PhysicalItemPF2e<TParent>[];

constructor(data: PreCreate<ItemSourcePF2e>, context: PhysicalItemConstructionContext<TParent> = {}) {
super(data, context);
this.parentItem = context.parentItem ?? null;
}

get level(): number {
return this.system.level.value;
}
Expand Down Expand Up @@ -258,6 +272,15 @@ abstract class PhysicalItemPF2e<TParent extends ActorPF2e | null = ActorPF2e | n
if (this._container?.id !== this.system.containerId) {
this._container = null;
}

// Prepare doubly-embedded items if this is of an appropriate physical-item type
this.subitems =
"subitems" in this.system && Array.isArray(this.system.subitems)
? this.system.subitems.map(
(i) =>
new ItemProxyPF2e(i, { parent: this.parent, parentItem: this }) as PhysicalItemPF2e<TParent>,
)
: [];
}

/** Refresh certain derived properties in case of special data preparation from subclasses */
Expand Down Expand Up @@ -583,4 +606,9 @@ interface PhysicalItemPF2e<TParent extends ActorPF2e | null = ActorPF2e | null>
system: PhysicalSystemData;
}

export { PhysicalItemPF2e };
interface PhysicalItemConstructionContext<TParent extends ActorPF2e | null>
extends DocumentConstructionContext<TParent> {
parentItem?: PhysicalItemPF2e<TParent>;
}

export { PhysicalItemPF2e, type PhysicalItemConstructionContext };
7 changes: 6 additions & 1 deletion src/module/item/shield/data.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PhysicalItemSource } from "@item/base/data/index.ts";
import {
BasePhysicalItemSource,
PhysicalItemTraits,
Expand All @@ -23,6 +24,8 @@ interface ShieldSystemSource extends PhysicalSystemSource {
runes: ShieldRuneData;
/** Usage for shields isn't stored. */
readonly usage?: never;
/** Doubly-embedded adjustments, attachments, talismans etc. */
subitems: PhysicalItemSource[];
}

interface IntegratedWeaponSource {
Expand All @@ -42,14 +45,16 @@ interface SpecificShieldData extends Pick<ShieldSystemSource, "material" | "rune
}

interface ShieldSystemData
extends Omit<ShieldSystemSource, "bulk" | "hp" | "identification" | "material" | "price" | "temporary" | "usage">,
extends Omit<ShieldSystemSource, SourceOmission>,
Omit<PhysicalSystemData, "baseItem" | "traits"> {
traits: ShieldTraits;
/** Shields are always held. */
usage: HeldUsage;
stackGroup: null;
}

type SourceOmission = "bulk" | "hp" | "identification" | "items" | "material" | "price" | "temporary" | "usage";

interface IntegratedWeaponData extends IntegratedWeaponSource {
damageType: DamageType;
versatile: { options: DamageType[]; selection: DamageType } | null;
Expand Down
8 changes: 4 additions & 4 deletions src/module/item/spell/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,6 @@ import { createDescriptionPrepend, createSpellRankLabel } from "./helpers.ts";
import { SpellOverlayCollection } from "./overlay.ts";
import { EffectAreaSize, MagicTradition, SpellTrait } from "./types.ts";

interface SpellConstructionContext<TParent extends ActorPF2e | null> extends DocumentConstructionContext<TParent> {
fromConsumable?: boolean;
}

class SpellPF2e<TParent extends ActorPF2e | null = ActorPF2e | null> extends ItemPF2e<TParent> {
readonly isFromConsumable: boolean;

Expand Down Expand Up @@ -1089,6 +1085,10 @@ interface SpellPF2e<TParent extends ActorPF2e | null = ActorPF2e | null> extends
system: SpellSystemData;
}

interface SpellConstructionContext<TParent extends ActorPF2e | null> extends DocumentConstructionContext<TParent> {
fromConsumable?: boolean;
}

interface SpellDamage {
template: SpellDamageTemplate;
context: DamageRollContext;
Expand Down
8 changes: 7 additions & 1 deletion src/module/item/weapon/data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AttributeString } from "@actor/types.ts";
import { PhysicalItemSource } from "@item/base/data/index.ts";
import { ItemFlagsPF2e } from "@item/base/data/system.ts";
import {
BasePhysicalItemSource,
Expand Down Expand Up @@ -82,6 +83,9 @@ interface WeaponSystemSource extends Investable<PhysicalSystemSource> {
/** Whether this is an unarmed attack that is a grasping appendage, requiring a free hand for use */
graspingAppendage?: boolean;

/** Doubly-embedded adjustments, attachments, talismans etc. */
subitems: PhysicalItemSource[];

// Refers to custom damage, *not* property runes
property1: {
value: string;
Expand Down Expand Up @@ -139,7 +143,7 @@ type WeaponRuneSource = {
};

interface WeaponSystemData
extends Omit<WeaponSystemSource, "bulk" | "hp" | "identification" | "price" | "temporary">,
extends Omit<WeaponSystemSource, SourceOmission>,
Omit<Investable<PhysicalSystemData>, "material"> {
traits: WeaponTraits;
baseItem: BaseWeaponType | null;
Expand All @@ -159,6 +163,8 @@ interface WeaponSystemData
stackGroup: null;
}

type SourceOmission = "bulk" | "hp" | "identification" | "items" | "price" | "temporary";

type WeaponUsageDetails = UsageDetails & Required<WeaponSystemSource["usage"]>;

interface WeaponTraits extends WeaponTraitsSource {
Expand Down
3 changes: 2 additions & 1 deletion src/module/item/weapon/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ConsumablePF2e, MeleePF2e, PhysicalItemPF2e, ShieldPF2e } from "@item";
import { createActionRangeLabel } from "@item/ability/helpers.ts";
import { ItemSourcePF2e, ItemSummaryData, MeleeSource } from "@item/base/data/index.ts";
import { NPCAttackDamage, NPCAttackTrait } from "@item/melee/data.ts";
import { PhysicalItemConstructionContext } from "@item/physical/document.ts";
import { IdentificationStatus, MystifiedData, RUNE_DATA, getPropertyRuneSlots } from "@item/physical/index.ts";
import { MAGIC_TRADITIONS } from "@item/spell/values.ts";
import { RangeData } from "@item/types.ts";
Expand Down Expand Up @@ -728,7 +729,7 @@ interface WeaponPF2e<TParent extends ActorPF2e | null = ActorPF2e | null> extend
get traits(): Set<WeaponTrait>;
}

interface WeaponConstructionContext<TParent extends ActorPF2e | null> extends DocumentConstructionContext<TParent> {
interface WeaponConstructionContext<TParent extends ActorPF2e | null> extends PhysicalItemConstructionContext<TParent> {
shield?: ShieldPF2e<TParent>;
}

Expand Down
18 changes: 17 additions & 1 deletion src/module/migration/runner/base.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ActorSourcePF2e } from "@actor/data/index.ts";
import { ItemSourcePF2e } from "@item/base/data/index.ts";
import { itemIsOfType } from "@item/helpers.ts";
import { MigrationRecord } from "@module/data.ts";
import { MigrationBase } from "@module/migration/base.ts";
import type { ScenePF2e, TokenDocumentPF2e } from "@scene";
Expand Down Expand Up @@ -78,6 +79,11 @@ export class MigrationRunnerBase {
if (currentItem.type === "consumable" && currentItem.system.spell) {
await migration.preUpdateItem?.(currentItem.system.spell);
}
if (itemIsOfType(currentItem, "armor", "equipment", "shield", "weapon")) {
for (const embed of currentItem.system.subitems) {
migration.preUpdateItem?.(embed);
}
}
}
}

Expand All @@ -86,10 +92,15 @@ export class MigrationRunnerBase {

for (const currentItem of currentActor.items) {
await migration.updateItem?.(currentItem, currentActor);
// Handle embedded spells
// Handle embedded items
if (currentItem.type === "consumable" && currentItem.system.spell) {
await migration.updateItem?.(currentItem.system.spell, currentActor);
}
if (itemIsOfType(currentItem, "armor", "equipment", "shield", "weapon")) {
for (const embed of currentItem.system.subitems) {
migration.preUpdateItem?.(embed);
}
}
}
}

Expand All @@ -115,6 +126,11 @@ export class MigrationRunnerBase {
if (current.type === "consumable" && current.system.spell) {
await migration.preUpdateItem?.(current.system.spell);
}
if (itemIsOfType(current, "armor", "equipment", "shield", "weapon")) {
for (const embed of current.system.subitems) {
migration.preUpdateItem?.(embed);
}
}
}

for (const migration of migrations) {
Expand Down
Loading