From 85ec5faec2b230b3d73f568fc9cee629cebac37d Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Mon, 25 Jan 2021 01:09:51 +0100 Subject: [PATCH] implement basic active effects --- package-lock.json | 2 +- src/module/actor/actor.ts | 121 +++++++++++++++++++++++++++++++++- src/module/item/item-data.ts | 2 + src/module/item/item-sheet.ts | 1 - 4 files changed, 123 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 70e4ebaa..77ef1fe0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2718,7 +2718,7 @@ } }, "foundry-pc-types": { - "version": "git+https://git.f3l.de/dungeonslayers/foundry-pc-types.git#ac45653fdec5fb935bf7db72889cb40cd6b80b20", + "version": "git+https://git.f3l.de/dungeonslayers/foundry-pc-types.git#3779bbbd30dbb04fa8f18615496882d6c66e1af4", "from": "git+https://git.f3l.de/dungeonslayers/foundry-pc-types.git#f3l-fixes", "dev": true, "requires": { diff --git a/src/module/actor/actor.ts b/src/module/actor/actor.ts index 270fee10..4f182ac1 100644 --- a/src/module/actor/actor.ts +++ b/src/module/actor/actor.ts @@ -1,9 +1,64 @@ import { ModifiableData } from "../common/common-data"; +import { DS4 } from "../config"; import { DS4Item } from "../item/item"; -import { DS4Armor, DS4ItemDataType, DS4Shield, ItemType } from "../item/item-data"; +import { DS4Armor, DS4EquippableItemDataType, DS4ItemDataType, DS4Shield, ItemType } from "../item/item-data"; import { DS4ActorDataType } from "./actor-data"; +type DS4ActiveEffect = ActiveEffect; + export class DS4Actor extends Actor { + /** @override */ + prepareData(): void { + this.data = duplicate(this._data); + if (!this.data.img) this.data.img = CONST.DEFAULT_TOKEN; + if (!this.data.name) this.data.name = "New " + this.entity; + this.prepareBaseData(); + this.prepareEmbeddedEntities(); + this.applyActiveEffectsToNonDerivedData(); + this.prepareDerivedData(); + this.applyActiveEffectsToDerivedData(); + } + + applyActiveEffectsToNonDerivedData(): void { + this.applyActiveEffectsFiltered((change) => !this.derivedDataProperties.includes(change.key)); + } + + applyActiveEffectsToDerivedData(): void { + this.applyActiveEffectsFiltered((change) => this.derivedDataProperties.includes(change.key)); + } + + /** + * Apply ActiveEffectChanges to the Actor data which are caused by ActiveEffects and satisfy the given predicate. + * + * @param predicate The predicate that ActiveEffectChanges need to satisfy in order to be applied + */ + applyActiveEffectsFiltered(predicate: (change: ActiveEffectChange) => boolean): void { + const overrides = {}; + + // Organize non-disabled effects by their application priority + const changes = this.effects.reduce((changes: Array, e) => { + if (e.data.disabled) return changes; + + return changes.concat( + e.data.changes.filter(predicate).map((c) => { + c = duplicate(c); + c.priority = c.priority ?? c.mode * 10; + return { ...c, effect: e }; + }), + ); + }, []); + changes.sort((a, b) => a.priority - b.priority); + + // Apply all changes + for (const change of changes) { + const result = change.effect.apply(this, change); + if (result !== null) overrides[change.key] = result; + } + + // Expand the set of final overrides + this["overrides"] = expandObject({ ...flattenObject(this["overrides"] ?? {}), ...overrides }); + } + /** @override */ prepareDerivedData(): void { const data = this.data; @@ -18,6 +73,13 @@ export class DS4Actor extends Actor this._prepareCombatValues(); } + /** The list of properties that are dericed, in dot notation */ + get derivedDataProperties(): Array { + return Object.keys(DS4.combatValues) + .map((combatValue) => `data.combatValues.${combatValue}.base`) + .concat("data.combatValues.hitPoints.max"); + } + /** * The list of item types that can be owned by this actor. */ @@ -113,4 +175,61 @@ export class DS4Actor extends Actor const allowed = Hooks.call("modifyTokenAttribute", { attribute, value, isDelta, isBar }, updates); return allowed !== false ? this.update(updates) : this; } + + /** @override */ + createEmbeddedEntity( + embeddedName: string, + createData: Record | Array>, + options?: Record, + ): Promise { + if (embeddedName === "OwnedItem") { + this._preCreateOwnedItem((createData as unknown) as ItemData); + } + return super.createEmbeddedEntity(embeddedName, createData, options); + } + + /** + * If the item that is going to be created is equippable, set it to be non equipped and disable all ActiveEffects + * contained in the item + * @param itemData The data of the item to be created + */ + private _preCreateOwnedItem(itemData: ItemData): void { + if ("equipped" in itemData.data) { + itemData.effects = itemData.effects.map((effect) => ({ ...effect, disabled: true })); + const equippableUpdateData = itemData as ItemData; + equippableUpdateData.data.equipped = false; + } + } + + /** @override */ + updateEmbeddedEntity( + embeddedName: string, + updateData: Record | Array>, + options?: Record, + ): Promise { + if (embeddedName === "OwnedItem") { + this._preUpdateOwnedItem(updateData as Partial>); + } + return super.updateEmbeddedEntity(embeddedName, updateData, options); + } + + /** + * If the equipped flag of an item changed, update all ActiveEffects originating from that item accordingly. + * @param updateData The change that is going to be applied to the owned item + */ + private _preUpdateOwnedItem(updateData: Partial>): void { + if ("equipped" in updateData.data) { + const equippableUpdateData = updateData as Partial>; + const origin = `Actor.${this.id}.OwnedItem.${updateData._id}`; + const effects = this.effects + .filter((e) => e.data.origin === origin) + .map((e) => { + const data = duplicate(e.data); + data.disabled = !equippableUpdateData.data.equipped; + return data; + }); + if (effects.length > 0) + this.updateEmbeddedEntity("ActiveEffect", (effects as unknown) as Record); + } + } } diff --git a/src/module/item/item-data.ts b/src/module/item/item-data.ts index d7467f8a..00d7a696 100644 --- a/src/module/item/item-data.ts +++ b/src/module/item/item-data.ts @@ -16,6 +16,8 @@ export type DS4ItemDataType = | DS4Alphabet | DS4SpecialCreatureAbility; +export type DS4EquippableItemDataType = DS4Weapon | DS4Armor | DS4Shield | DS4Trinket; + // types interface DS4Weapon extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable { diff --git a/src/module/item/item-sheet.ts b/src/module/item/item-sheet.ts index 6464defc..bd143f31 100644 --- a/src/module/item/item-sheet.ts +++ b/src/module/item/item-sheet.ts @@ -34,7 +34,6 @@ export class DS4ItemSheet extends ItemSheet { actor: this.item.actor, isPhysical: isDS4ItemDataTypePhysical(this.item.data.data), }; - console.log(data); return data; }