refactor: improve structure of src

This commit is contained in:
Johannes Loher 2022-11-04 21:47:18 +01:00
parent b74919b75b
commit c5d4ec1abd
96 changed files with 146 additions and 157 deletions

View file

@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
// SPDX-FileCopyrightText: 2021 Oliver Rümpelein
// SPDX-FileCopyrightText: 2021 Gesina Schwalbe
// SPDX-FileCopyrightText: 2021 Siegfried Krug
//
// SPDX-License-Identifier: MIT
import { DS4 } from "../../config";
import type { ModifiableDataBaseTotal, ResourceDataBaseTotalMax } from "../common/common-data";
export interface DS4ActorDataPropertiesDataBase {
attributes: DS4ActorDataPropertiesDataAttributes;
traits: DS4ActorDataPropertiesDataTraits;
combatValues: DS4ActorDataPropertiesDataCombatValues;
rolling: DS4ActorDataPropertiesDataRolling;
checks: DS4ActorDataPropertiesDataChecks;
}
type DS4ActorDataPropertiesDataAttributes = {
[Key in keyof typeof DS4.i18n.attributes]: ModifiableDataBaseTotal<number>;
};
type DS4ActorDataPropertiesDataTraits = { [Key in keyof typeof DS4.i18n.traits]: ModifiableDataBaseTotal<number> };
type DS4ActorDataPropertiesDataCombatValues = {
[Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints"
? ResourceDataBaseTotalMax<number>
: ModifiableDataBaseTotal<number>;
};
interface DS4ActorDataPropertiesDataRolling {
maximumCoupResult: number;
minimumFumbleResult: number;
}
type DS4ActorDataPropertiesDataChecks = {
[key in Check]: number;
};
export type Check = keyof typeof DS4.i18n.checks;
export function isCheck(value: string): value is Check {
return Object.keys(DS4.i18n.checks).includes(value);
}

View file

@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
import type { DS4CharacterDataProperties } from "./character/character-data-properties";
import type { DS4CreatureDataProperties } from "./creature/creature-data-properties";
declare global {
interface DataConfig {
Actor: DS4ActorDataProperties;
}
}
export type DS4ActorDataProperties = DS4CharacterDataProperties | DS4CreatureDataProperties;

View file

@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
// SPDX-FileCopyrightText: 2021 Oliver Rümpelein
// SPDX-FileCopyrightText: 2021 Gesina Schwalbe
// SPDX-FileCopyrightText: 2021 Siegfried Krug
//
// SPDX-License-Identifier: MIT
import { DS4 } from "../../config";
import type { ModifiableData, ModifiableDataBase, ResourceData } from "../common/common-data";
export interface DS4ActorDataSourceDataBase {
attributes: DS4ActorDataSourceDataAttributes;
traits: DS4ActorDataSourceDataTraits;
combatValues: DS4ActorDataSourceDataCombatValues;
}
type DS4ActorDataSourceDataAttributes = { [Key in keyof typeof DS4.i18n.attributes]: ModifiableDataBase<number> };
type Attribute = keyof DS4ActorDataSourceDataAttributes;
export function isAttribute(value: unknown): value is Attribute {
return (Object.keys(DS4.i18n.attributes) as Array<unknown>).includes(value);
}
type DS4ActorDataSourceDataTraits = { [Key in keyof typeof DS4.i18n.traits]: ModifiableDataBase<number> };
type Trait = keyof DS4ActorDataSourceDataTraits;
export function isTrait(value: unknown): value is Trait {
return (Object.keys(DS4.i18n.traits) as Array<unknown>).includes(value);
}
type DS4ActorDataSourceDataCombatValues = {
[Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints"
? ResourceData<number>
: ModifiableData<number>;
};
type CombatValue = keyof DS4ActorDataSourceDataCombatValues;
export function isCombatValue(value: string): value is CombatValue {
return (Object.keys(DS4.i18n.combatValues) as Array<unknown>).includes(value);
}

View file

@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
import type { DS4CharacterDataSource } from "./character/character-data-source";
import type { DS4CreatureDataSource } from "./creature/creature-data-source";
declare global {
interface SourceConfig {
Actor: DS4ActorDataSource;
}
}
export type DS4ActorDataSource = DS4CharacterDataSource | DS4CreatureDataSource;

View file

@ -0,0 +1,443 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
// SPDX-FileCopyrightText: 2021 Oliver RÜmpelein
//
// SPDX-License-Identifier: MIT
import { DS4 } from "../../config";
import { createCheckRoll } from "../../dice/check-factory";
import { mathEvaluator } from "../../expression-evaluation/evaluator";
import { logger } from "../../utils/logger";
import { getGame } from "../../utils/utils";
import { DS4ActiveEffect } from "../active-effect";
import { isAttribute, isTrait } from "./actor-data-source-base";
import type { ModifiableDataBaseTotal } from "../common/common-data";
import type { DS4ArmorDataProperties } from "../item/armor/armor-data-properties";
import type { DS4Item } from "../item/item";
import type { ItemType } from "../item/item-data-source";
import type { DS4ShieldDataProperties } from "../item/shield/shield-data-properties";
import type { Check } from "./actor-data-properties-base";
declare global {
interface DocumentClassConfig {
Actor: typeof DS4Actor;
}
}
/**
* The Actor class for DS4
*/
export class DS4Actor extends Actor {
initialized: boolean | undefined;
override prepareData(): void {
this.initialized = true;
this.data.reset();
this.prepareBaseData();
this.prepareEmbeddedDocuments();
this.applyActiveEffectsToBaseData();
this.prepareDerivedData();
this.applyActiveEffectsToDerivedData();
this.prepareFinalDerivedData();
}
override prepareBaseData(): void {
const data = this.data;
data.data.rolling = {
minimumFumbleResult: 20,
maximumCoupResult: 1,
};
const attributes = data.data.attributes;
Object.values(attributes).forEach(
(attribute: ModifiableDataBaseTotal<number>) => (attribute.total = attribute.base + attribute.mod),
);
const traits = data.data.traits;
Object.values(traits).forEach(
(trait: ModifiableDataBaseTotal<number>) => (trait.total = trait.base + trait.mod),
);
}
private get actorEffects() {
return this.effects.filter((effect) => !effect.data.flags.ds4?.itemEffectConfig?.applyToItems);
}
/**
* Get the effects of this actor that should be applied to the given item.
* @param item The item for which to get effects
* @returns The array of effects that are candidates to be applied to the item
*/
itemEffects(item: DS4Item) {
return this.effects.filter((effect) => {
const { applyToItems, itemName, condition } = effect.data.flags.ds4?.itemEffectConfig ?? {};
if (!applyToItems || (itemName !== undefined && itemName !== "" && itemName !== item.name)) {
return false;
}
if (condition !== undefined && condition !== "") {
try {
const replacedCondition = DS4Actor.replaceFormulaData(condition, {
item: item.data,
actor: this.data,
effect: effect.data,
});
return replacedCondition !== undefined ? Boolean(mathEvaluator.evaluate(replacedCondition)) : false;
} catch (error) {
logger.warn(error);
return false;
}
}
return true;
});
}
private static replaceFormulaData(formula: string, data: object): string | undefined {
const dataRgx = new RegExp(/@([a-z.0-9_\-]+)/gi);
try {
return formula.replace(dataRgx, (_, term) => {
const value = foundry.utils.getProperty(data, term);
if (value == null) {
throw new Error();
}
return String(value).trim();
});
} catch {
return undefined;
}
}
/**
* We override this with an empty implementation because we have our own custom way of applying
* {@link ActiveEffect}s and {@link Actor#prepareEmbeddedDocuments} calls this.
*/
override applyActiveEffects(): void {
return;
}
applyActiveEffectsToBaseData(): void {
// reset overrides because our variant of applying active effects does not set them, it only adds overrides
this.overrides = {};
DS4ActiveEffect.applyEffetcs(
this,
this.actorEffects,
(change) =>
!this.derivedDataProperties.includes(change.key) &&
!this.finalDerivedDataProperties.includes(change.key),
);
}
applyActiveEffectsToDerivedData(): void {
DS4ActiveEffect.applyEffetcs(this, this.actorEffects, (change) =>
this.derivedDataProperties.includes(change.key),
);
}
/**
* Apply transformations to the Actor data after effects have been applied to the base data.
*/
override prepareDerivedData(): void {
this.prepareCombatValues();
this.prepareChecks();
}
/**
* The list of properties that are derived from others, given in dot notation.
*/
get derivedDataProperties(): Array<string> {
const combatValueProperties = Object.keys(DS4.i18n.combatValues).map(
(combatValue) => `data.combatValues.${combatValue}.total`,
);
const checkProperties = Object.keys(DS4.i18n.checks)
.filter((check) => check !== "defend")
.map((check) => `data.checks.${check}`);
return combatValueProperties.concat(checkProperties);
}
/**
* Apply final transformations to the Actor data after all effects have been applied.
*/
prepareFinalDerivedData(): void {
Object.values(this.data.data.attributes).forEach(
(attribute: ModifiableDataBaseTotal<number>) => (attribute.total = Math.ceil(attribute.total)),
);
Object.values(this.data.data.traits).forEach(
(trait: ModifiableDataBaseTotal<number>) => (trait.total = Math.ceil(trait.total)),
);
Object.entries(this.data.data.combatValues)
.filter(([key]) => key !== "movement")
.map(([, value]) => value)
.forEach(
(combatValue: ModifiableDataBaseTotal<number>) => (combatValue.total = Math.ceil(combatValue.total)),
);
(Object.keys(this.data.data.checks) as (keyof typeof this.data.data.checks)[]).forEach((key) => {
this.data.data.checks[key] = Math.ceil(this.data.data.checks[key]);
});
this.data.data.combatValues.hitPoints.max = this.data.data.combatValues.hitPoints.total;
this.data.data.checks.defend = this.data.data.combatValues.defense.total;
}
/**
* The list of properties that are completely derived (i.e. {@link ActiveEffect}s cannot be applied to them),
* given in dot notation.
*/
get finalDerivedDataProperties(): string[] {
return ["data.combatValues.hitPoints.max", "data.checks.defend"];
}
/**
* The list of item types that can be owned by this actor.
*/
get ownableItemTypes(): Array<ItemType> {
return ["weapon", "armor", "shield", "equipment", "loot", "spell"];
}
/**
* Checks whether or not the given item type can be owned by the actor.
* @param itemType - The item type to check
*/
canOwnItemType(itemType: ItemType): boolean {
return this.ownableItemTypes.includes(itemType);
}
/**
* Prepares the combat values of the actor.
*/
protected prepareCombatValues(): void {
const data = this.data.data;
const armorValueOfEquippedItems = this.calculateArmorValueOfEquippedItems();
const spellMalusOfEquippedItems = this.calculateSpellMaluesOfEquippedItems();
data.combatValues.hitPoints.base = data.attributes.body.total + data.traits.constitution.total + 10;
data.combatValues.defense.base =
data.attributes.body.total + data.traits.constitution.total + armorValueOfEquippedItems;
data.combatValues.initiative.base = data.attributes.mobility.total + data.traits.agility.total;
data.combatValues.movement.base = data.attributes.mobility.total / 2 + 1;
data.combatValues.meleeAttack.base = data.attributes.body.total + data.traits.strength.total;
data.combatValues.rangedAttack.base = data.attributes.mobility.total + data.traits.dexterity.total;
data.combatValues.spellcasting.base =
data.attributes.mind.total + data.traits.aura.total - spellMalusOfEquippedItems;
data.combatValues.targetedSpellcasting.base =
data.attributes.mind.total + data.traits.dexterity.total - spellMalusOfEquippedItems;
Object.values(data.combatValues).forEach(
(combatValue: ModifiableDataBaseTotal<number>) => (combatValue.total = combatValue.base + combatValue.mod),
);
}
/**
* Calculates the total armor value of the equipped items.
*/
protected calculateArmorValueOfEquippedItems(): number {
return this.getEquippedItemsWithArmor()
.map((item) => item.data.data.armorValue)
.reduce((a, b) => a + b, 0);
}
/**
* Calculates the spell malus from equipped items.
*/
protected calculateSpellMaluesOfEquippedItems(): number {
return this.getEquippedItemsWithArmor()
.filter(
(item) =>
!(item.data.type === "armor" && ["cloth", "natural"].includes(item.data.data.armorMaterialType)),
)
.map((item) => item.data.data.armorValue)
.reduce((a, b) => a + b, 0);
}
private getEquippedItemsWithArmor() {
return this.items
.filter(
(item): item is DS4Item & { data: DS4ArmorDataProperties | DS4ShieldDataProperties } =>
item.data.type === "armor" || item.data.type === "shield",
)
.filter((item) => item.data.data.equipped);
}
/**
* Prepares the check target numbers of checks for the actor.
*/
protected prepareChecks(): void {
const data = this.data.data;
data.checks = {
appraise: data.attributes.mind.total + data.traits.intellect.total,
changeSpell: data.attributes.mind.total + data.traits.intellect.total,
climb: data.attributes.mobility.total + data.traits.strength.total,
communicate: data.attributes.mind.total + data.traits.dexterity.total,
decipherScript: data.attributes.mind.total + data.traits.intellect.total,
defend: 0, // assigned in prepareFinalDerivedData as it must always match data.combatValues.defense.total and is not changeable by effects
defyPoison: data.attributes.body.total + data.traits.constitution.total,
disableTraps: data.attributes.mind.total + data.traits.dexterity.total,
featOfStrength: data.attributes.body.total + data.traits.strength.total,
flirt: data.attributes.mind.total + data.traits.aura.total,
haggle: data.attributes.mind.total + Math.max(data.traits.intellect.total, data.traits.intellect.total),
hide: data.attributes.mobility.total + data.traits.agility.total,
identifyMagic: data.attributes.mind.total + data.traits.intellect.total,
jump: data.attributes.mobility.total + data.traits.agility.total,
knowledge: data.attributes.mind.total + data.traits.intellect.total,
openLock: data.attributes.mind.total + data.traits.dexterity.total,
perception: Math.max(data.attributes.mind.total + data.traits.intellect.total, 8),
pickPocket: data.attributes.mobility.total + data.traits.dexterity.total,
readTracks: data.attributes.mind.total + data.traits.intellect.total,
resistDisease: data.attributes.body.total + data.traits.constitution.total,
ride: data.attributes.mobility.total + Math.max(data.traits.agility.total, data.traits.aura.total),
search: Math.max(data.attributes.mind.total + data.traits.intellect.total, 8),
senseMagic: data.attributes.mind.total + data.traits.aura.total,
sneak: data.attributes.mobility.total + data.traits.agility.total,
startFire: data.attributes.mind.total + data.traits.dexterity.total,
swim: data.attributes.mobility.total + data.traits.strength.total,
wakeUp: data.attributes.mind.total + data.traits.intellect.total,
workMechanism:
data.attributes.mind.total + Math.max(data.traits.dexterity.total, data.traits.intellect.total),
};
}
/**
* Handle how changes to a Token attribute bar are applied to the Actor.
* This only differs from the base implementation by also allowing negative values.
*/
override async modifyTokenAttribute(
attribute: string,
value: number,
isDelta = false,
isBar = true,
): Promise<this | undefined> {
const current = foundry.utils.getProperty(this.data.data, attribute);
// Determine the updates to make to the actor data
let updates: Record<string, number>;
if (isBar) {
if (isDelta) value = Math.min(Number(current.value) + value, current.max);
updates = { [`data.${attribute}.value`]: value };
} else {
if (isDelta) value = Number(current) + value;
updates = { [`data.${attribute}`]: value };
}
// Call a hook to handle token resource bar updates
const allowed = Hooks.call("modifyTokenAttribute", { attribute, value, isDelta, isBar }, updates);
return allowed !== false ? this.update(updates) : this;
}
/**
* Roll for a given check.
* @param check - The check to perform
* @param options - Additional options to customize the roll
*/
async rollCheck(
check: Check,
options: { speaker?: { token?: TokenDocument; alias?: string } } = {},
): Promise<void> {
const speaker = ChatMessage.getSpeaker({ actor: this, ...options.speaker });
await createCheckRoll(this.data.data.checks[check], {
rollMode: getGame().settings.get("core", "rollMode"),
maximumCoupResult: this.data.data.rolling.maximumCoupResult,
minimumFumbleResult: this.data.data.rolling.minimumFumbleResult,
flavor: "DS4.ActorCheckFlavor",
flavorData: { actor: speaker.alias ?? this.name, check: DS4.i18nKeys.checks[check] },
speaker,
});
}
/**
* Roll a generic check. A dialog is presented to select the combination of
* Attribute and Trait to perform the check against.
* @param options - Additional options to customize the roll
*/
async rollGenericCheck(options: { speaker?: { token?: TokenDocument; alias?: string } } = {}): Promise<void> {
const attributeAndTrait = await this.selectAttributeAndTrait();
if (!attributeAndTrait) {
return;
}
const { attribute, trait } = attributeAndTrait;
const checkTargetNumber = this.data.data.attributes[attribute].total + this.data.data.traits[trait].total;
const speaker = ChatMessage.getSpeaker({ actor: this, ...options.speaker });
await createCheckRoll(checkTargetNumber, {
rollMode: getGame().settings.get("core", "rollMode"),
maximumCoupResult: this.data.data.rolling.maximumCoupResult,
minimumFumbleResult: this.data.data.rolling.minimumFumbleResult,
flavor: "DS4.ActorGenericCheckFlavor",
flavorData: {
actor: speaker.alias ?? this.name,
attribute: DS4.i18n.attributes[attribute],
trait: DS4.i18n.traits[trait],
},
speaker,
});
}
protected async selectAttributeAndTrait(): Promise<{
attribute: keyof typeof DS4.i18n.attributes;
trait: keyof typeof DS4.i18n.traits;
} | null> {
const attributeIdentifier = "attribute-trait-selection-attribute";
const traitIdentifier = "attribute-trait-selection-trait";
return Dialog.prompt({
title: getGame().i18n.localize("DS4.DialogAttributeTraitSelection"),
content: await renderTemplate("systems/ds4/templates/dialogs/simple-select-form.hbs", {
selects: [
{
label: getGame().i18n.localize("DS4.Attribute"),
identifier: attributeIdentifier,
options: Object.fromEntries(
(Object.entries(DS4.i18n.attributes) as [keyof typeof DS4.i18n.attributes, string][]).map(
([attribute, translation]) => [
attribute,
`${translation} (${this.data.data.attributes[attribute].total})`,
],
),
),
},
{
label: getGame().i18n.localize("DS4.Trait"),
identifier: traitIdentifier,
options: Object.fromEntries(
(Object.entries(DS4.i18n.traits) as [keyof typeof DS4.i18n.traits, string][]).map(
([trait, translation]) => [
trait,
`${translation} (${this.data.data.traits[trait].total})`,
],
),
),
},
],
}),
label: getGame().i18n.localize("DS4.GenericOkButton"),
callback: (html) => {
const selectedAttribute = html.find(`#${attributeIdentifier}`).val();
if (!isAttribute(selectedAttribute)) {
throw new Error(
getGame().i18n.format("DS4.ErrorUnexpectedAttribute", {
actualAttribute: selectedAttribute,
expectedTypes: Object.keys(DS4.i18n.attributes)
.map((attribute) => `'${attribute}'`)
.join(", "),
}),
);
}
const selectedTrait = html.find(`#${traitIdentifier}`).val();
if (!isTrait(selectedTrait)) {
throw new Error(
getGame().i18n.format("DS4.ErrorUnexpectedTrait", {
actualTrait: selectedTrait,
expectedTypes: Object.keys(DS4.i18n.traits)
.map((attribute) => `'${attribute}'`)
.join(", "),
}),
);
}
return {
attribute: selectedAttribute,
trait: selectedTrait,
};
},
rejectClose: false,
});
}
}

View file

@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
import type { DS4ActorDataPropertiesDataBase } from "../actor-data-properties-base";
import type {
DS4CharacterDataSourceDataBaseInfo,
DS4CharacterDataSourceDataCurrency,
DS4CharacterDataSourceDataProfile,
DS4CharacterDataSourceDataProgression,
DS4CharacterDataSourceDataSlayerPoints,
} from "./character-data-source";
export interface DS4CharacterDataProperties {
type: "character";
data: DS4CharacterDataPropertiesData;
}
interface DS4CharacterDataPropertiesData extends DS4ActorDataPropertiesDataBase {
baseInfo: DS4CharacterDataSourceDataBaseInfo;
progression: DS4CharacterDataSourceDataProgression;
profile: DS4CharacterDataSourceDataProfile;
currency: DS4CharacterDataSourceDataCurrency;
slayerPoints: DS4CharacterDataPropertiesDataSlayerPoints;
}
export interface DS4CharacterDataPropertiesDataSlayerPoints extends DS4CharacterDataSourceDataSlayerPoints {
max: number;
}

View file

@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
import type { UsableResource } from "../../common/common-data";
import type { DS4ActorDataSourceDataBase } from "../actor-data-source-base";
export interface DS4CharacterDataSource {
type: "character";
data: DS4CharacterDataSourceData;
}
interface DS4CharacterDataSourceData extends DS4ActorDataSourceDataBase {
baseInfo: DS4CharacterDataSourceDataBaseInfo;
progression: DS4CharacterDataSourceDataProgression;
profile: DS4CharacterDataSourceDataProfile;
currency: DS4CharacterDataSourceDataCurrency;
slayerPoints: DS4CharacterDataSourceDataSlayerPoints;
}
export interface DS4CharacterDataSourceDataBaseInfo {
race: string;
class: string;
heroClass: string;
culture: string;
}
export interface DS4CharacterDataSourceDataProgression {
level: number;
experiencePoints: number;
talentPoints: UsableResource<number>;
progressPoints: UsableResource<number>;
}
export interface DS4CharacterDataSourceDataProfile {
biography: string;
gender: string;
birthday: string;
birthplace: string;
age: number;
height: number;
hairColor: string;
weight: number;
eyeColor: string;
specialCharacteristics: string;
}
export interface DS4CharacterDataSourceDataCurrency {
gold: number;
silver: number;
copper: number;
}
export interface DS4CharacterDataSourceDataSlayerPoints {
value: number;
}

View file

@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
import { DS4Actor } from "../actor";
import type { ItemType } from "../../item/item-data-source";
export class DS4Character extends DS4Actor {
override prepareFinalDerivedData(): void {
super.prepareFinalDerivedData();
this.data.data.slayerPoints.max = 3;
}
override get finalDerivedDataProperties(): string[] {
return [...super.finalDerivedDataProperties, "data.slayerPoints.max"];
}
override get ownableItemTypes(): Array<ItemType> {
return [...super.ownableItemTypes, "talent", "racialAbility", "language", "alphabet"];
}
}
export interface DS4Character {
data: foundry.data.ActorData & { type: "character"; _source: { type: "character" } };
}

View file

@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
import type { DS4ActorDataPropertiesDataBase } from "../actor-data-properties-base";
import type { DS4CreatureDataSourceDataBaseInfo } from "./creature-data-source";
export interface DS4CreatureDataProperties {
type: "creature";
data: DS4CreatureDataPropertiesData;
}
interface DS4CreatureDataPropertiesData extends DS4ActorDataPropertiesDataBase {
baseInfo: DS4CreatureDataSourceDataBaseInfo;
}

View file

@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
import type { DS4 } from "../../../config";
import type { DS4ActorDataSourceDataBase } from "../actor-data-source-base";
export interface DS4CreatureDataSource {
type: "creature";
data: DS4CreatureDataSourceData;
}
interface DS4CreatureDataSourceData extends DS4ActorDataSourceDataBase {
baseInfo: DS4CreatureDataSourceDataBaseInfo;
}
export interface DS4CreatureDataSourceDataBaseInfo {
loot: string;
foeFactor: number;
creatureType: CreatureType;
sizeCategory: SizeCategory;
experiencePoints: number;
description: string;
}
type CreatureType = keyof typeof DS4.i18n.creatureTypes;
type SizeCategory = keyof typeof DS4.i18n.creatureSizeCategories;

View file

@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
import { DS4Actor } from "../actor";
import type { ItemType } from "../../item/item-data-source";
export class DS4Creature extends DS4Actor {
override get ownableItemTypes(): Array<ItemType> {
return [...super.ownableItemTypes, "specialCreatureAbility"];
}
}
export interface DS4Creature {
data: foundry.data.ActorData & { type: "creature"; _source: { type: "creature" } };
}

View file

@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
import { getGame } from "../../utils/utils";
import { DS4Actor } from "./actor";
import { DS4Character } from "./character/character";
import { DS4Creature } from "./creature/creature";
const handler = {
construct(_: typeof DS4Actor, args: ConstructorParameters<typeof DS4Actor>) {
switch (args[0]?.type) {
case "character":
return new DS4Character(...args);
case "creature":
return new DS4Creature(...args);
default:
throw new Error(getGame().i18n.format("DS4.ErrorInvalidActorType", { type: args[0]?.type }));
}
},
};
export const DS4ActorProxy: typeof DS4Actor = new Proxy(DS4Actor, handler);