-
Notifications
You must be signed in to change notification settings - Fork 56
[0.7.x] Actors and Items
Using foundry-vtt-types
for your system development allows you to easily get type checking on your actor and item data. After setting up the dev environment, you'll want to start with at least defining the basics of your items, as actors are dependent on item data.
To start off, you'll want to define the data structure of items matching the schema defined in template.json
. Each item type will have its own interface. Then each of those gets wrapped into a data interface with a defined type. Once all of the data interfaces are defined, you can then create an Item data Union type. Doing this allows item data to be narrowed by testing the item.data.type
parameter.
This is what a basic item data definition could look like:
src/module/item-data.ts
interface WeaponData {
damage: number;
hands: string;
};
interface WeaponItemData extends Item.Data<WeaponData> {
type: "weapon";
}
interface ArmorData {
reduction: number;
class: "light" | "medium" | "heavy";
}
interface ArmorItemData extends Item.Data<ArmorData> {
type: "armor";
}
export type SystemItemData = WeaponItemData | ArmorItemData;
Once the data is set up, the custom item class can be defined. Because of how the data fields are defined, they can be type guarded to narrow the Union.
src/module/item.ts
import { SystemItemData } from "./item-data";
export class SystemItem extends Item<SystemItemData> {
doWeaponThing(): void {
if (this.data.type !== "weapon") return;
console.log(this.data.data.damage);
}
doArmorThing(): void {
if (this.data.type !== "armor") return;
console.log(this.data.data.reduction);
}
}
In the above example, trying to access this.data.data.damage
in the doArmorThing()
method would result in an error being shown by tsserver
.
Once the basics of items are done, actors can be set up. The process is very similar to items. The main difference is that Actors are dependent on Item typing. Start off similarly by defining the data for the different actor types as defined in template.json
. Then when defining the ActorData for each actor type, be sure to specify the same system item data that was used for the item class in addition to the actor schema.
src/module/actor-data.ts
import { SystemItemData } from "./item-data.ts";
interface CharData {
pcOnlyField: string;
// Schema from template.json
}
interface CharActorData extends Actor.Data<CharData, SystemItemData> {
type: "character";
}
interface NpcData {
npcOnlyField: string;
// Schema from template.json
}
interface NpcActorData extends Actor.Data<NpcData, SystemItemData> {
type: "npc";
}
export type SysActorData = CharActorData | NpcActorData;
Once the actor data is defined, the class can be created. Once again item information needs to be supplied, this time the custom item class. TypeScript will check to make sure that the item data on the provided actor data and item class are compatible.
src/module/actor.ts
import { SysActorData } from "./actor-data";
import { SystemItem } from "./item";
export class SystemActor extends Actor<SysActorData, SystemItem> {
async doStuff(): Promise<void> {
if (this.data.type !== "character") return;
console.log(this.data.data.pcOnlyField);
}
getArmorBonus(): number {
const armor = this.items.find(i => i.data.type === "armor");
if (armor === null || armor.data.type !== "armor") return 0; // Second condition is to help typescript infer the type on armor.data
return armor.data.data.reduction;
}
}
Different actor data can be narrowed just like with the items.
Specifying the new classes to Foundry is pretty much the same as when using pure JavaScript.
src/system.ts
import { SystemActor } from "./module/actor";
import { SystemItem } from "./module/item";
Hooks.once("init", () => {
CONFIG.Actor.entityClass = SystemActor;
CONFIG.Item.entityClass = SystemItem;
});