Files
Foundry-VTT-Docker/resources/app/client/data/documents/actor-delta.js
2025-01-04 00:34:03 +01:00

222 lines
8.8 KiB
JavaScript

/**
* The client-side ActorDelta embedded document which extends the common BaseActorDelta document model.
* @extends foundry.documents.BaseActorDelta
* @mixes ClientDocumentMixin
* @see {@link TokenDocument} The TokenDocument document type which contains ActorDelta embedded documents.
*/
class ActorDelta extends ClientDocumentMixin(foundry.documents.BaseActorDelta) {
/** @inheritdoc */
_configure(options={}) {
super._configure(options);
this._createSyntheticActor();
}
/* -------------------------------------------- */
/** @inheritdoc */
_initialize({sceneReset=false, ...options}={}) {
// Do not initialize the ActorDelta as part of a Scene reset.
if ( sceneReset ) return;
super._initialize(options);
if ( !this.parent.isLinked && (this.syntheticActor?.id !== this.parent.actorId) ) {
this._createSyntheticActor({ reinitializeCollections: true });
}
}
/* -------------------------------------------- */
/**
* Pass-through the type from the synthetic Actor, if it exists.
* @type {string}
*/
get type() {
return this.syntheticActor?.type ?? this._type ?? this._source.type;
}
set type(type) {
this._type = type;
}
_type;
/* -------------------------------------------- */
/* Methods */
/* -------------------------------------------- */
/**
* Apply this ActorDelta to the base Actor and return a synthetic Actor.
* @param {object} [context] Context to supply to synthetic Actor instantiation.
* @returns {Actor|null}
*/
apply(context={}) {
return this.constructor.applyDelta(this, this.parent.baseActor, context);
}
/* -------------------------------------------- */
/** @override */
prepareEmbeddedDocuments() {
// The synthetic actor prepares its items in the appropriate context of an actor. The actor delta does not need to
// prepare its items, and would do so in the incorrect context.
}
/* -------------------------------------------- */
/** @inheritdoc */
updateSource(changes={}, options={}) {
// If there is no baseActor, there is no synthetic actor either, so we do nothing.
if ( !this.syntheticActor || !this.parent.baseActor ) return {};
// Perform an update on the synthetic Actor first to validate the changes.
let actorChanges = foundry.utils.deepClone(changes);
delete actorChanges._id;
actorChanges.type ??= this.syntheticActor.type;
actorChanges.name ??= this.syntheticActor.name;
// In the non-recursive case we must apply the changes as actor delta changes first in order to get an appropriate
// actor update, otherwise applying an actor delta update non-recursively to an actor will truncate most of its
// data.
if ( options.recursive === false ) {
const tmpDelta = new ActorDelta.implementation(actorChanges, { parent: this.parent });
const updatedActor = this.constructor.applyDelta(tmpDelta, this.parent.baseActor);
if ( updatedActor ) actorChanges = updatedActor.toObject();
}
this.syntheticActor.updateSource(actorChanges, { ...options });
const diff = super.updateSource(changes, options);
// If this was an embedded update, re-apply the delta to make sure embedded collections are merged correctly.
const embeddedUpdate = Object.keys(this.constructor.hierarchy).some(k => k in changes);
const deletionUpdate = Object.keys(foundry.utils.flattenObject(changes)).some(k => k.includes("-="));
if ( !this.parent.isLinked && (embeddedUpdate || deletionUpdate) ) this.updateSyntheticActor();
return diff;
}
/* -------------------------------------------- */
/** @inheritdoc */
reset() {
super.reset();
// Propagate reset calls on the ActorDelta to the synthetic Actor.
if ( !this.parent.isLinked ) this.syntheticActor?.reset();
}
/* -------------------------------------------- */
/**
* Generate a synthetic Actor instance when constructed, or when the represented Actor, or actorLink status changes.
* @param {object} [options]
* @param {boolean} [options.reinitializeCollections] Whether to fully re-initialize this ActorDelta's collections in
* order to re-retrieve embedded Documents from the synthetic
* Actor.
* @internal
*/
_createSyntheticActor({ reinitializeCollections=false }={}) {
Object.defineProperty(this, "syntheticActor", {value: this.apply({strict: false}), configurable: true});
if ( reinitializeCollections ) {
for ( const collection of Object.values(this.collections) ) collection.initialize({ full: true });
}
}
/* -------------------------------------------- */
/**
* Update the synthetic Actor instance with changes from the delta or the base Actor.
*/
updateSyntheticActor() {
if ( this.parent.isLinked ) return;
const updatedActor = this.apply();
if ( updatedActor ) this.syntheticActor.updateSource(updatedActor.toObject(), {diff: false, recursive: false});
}
/* -------------------------------------------- */
/**
* Restore this delta to empty, inheriting all its properties from the base actor.
* @returns {Promise<Actor>} The restored synthetic Actor.
*/
async restore() {
if ( !this.parent.isLinked ) await Promise.all(Object.values(this.syntheticActor.apps).map(app => app.close()));
await this.delete({diff: false, recursive: false, restoreDelta: true});
return this.parent.actor;
}
/* -------------------------------------------- */
/**
* Ensure that the embedded collection delta is managing any entries that have had their descendants updated.
* @param {Document} doc The parent whose immediate children have been modified.
* @internal
*/
_handleDeltaCollectionUpdates(doc) {
// Recurse up to an immediate child of the ActorDelta.
if ( !doc ) return;
if ( doc.parent !== this ) return this._handleDeltaCollectionUpdates(doc.parent);
const collection = this.getEmbeddedCollection(doc.parentCollection);
if ( !collection.manages(doc.id) ) collection.set(doc.id, doc);
}
/* -------------------------------------------- */
/* Database Operations */
/* -------------------------------------------- */
/** @inheritDoc */
async _preDelete(options, user) {
if ( this.parent.isLinked ) return super._preDelete(options, user);
// Emulate a synthetic actor update.
const data = this.parent.baseActor.toObject();
let allowed = await this.syntheticActor._preUpdate(data, options, user) ?? true;
allowed &&= (options.noHook || Hooks.call("preUpdateActor", this.syntheticActor, data, options, user.id));
if ( allowed === false ) {
console.debug(`${vtt} | Actor update prevented during pre-update`);
return false;
}
return super._preDelete(options, user);
}
/* -------------------------------------------- */
/** @inheritDoc */
_onUpdate(changed, options, userId) {
super._onUpdate(changed, options, userId);
if ( this.parent.isLinked ) return;
this.syntheticActor._onUpdate(changed, options, userId);
Hooks.callAll("updateActor", this.syntheticActor, changed, options, userId);
}
/* -------------------------------------------- */
/** @inheritDoc */
_onDelete(options, userId) {
super._onDelete(options, userId);
if ( !this.parent.baseActor ) return;
// Create a new, ephemeral ActorDelta Document in the parent Token and emulate synthetic actor update.
this.parent.updateSource({ delta: { _id: this.parent.id } });
this.parent.delta._onUpdate(this.parent.baseActor.toObject(), options, userId);
}
/* -------------------------------------------- */
/** @inheritDoc */
_dispatchDescendantDocumentEvents(event, collection, args, _parent) {
super._dispatchDescendantDocumentEvents(event, collection, args, _parent);
if ( !_parent ) {
// Emulate descendant events on the synthetic actor.
const fn = this.syntheticActor[`_${event}DescendantDocuments`];
fn?.call(this.syntheticActor, this.syntheticActor, collection, ...args);
/** @deprecated since v11 */
const legacyFn = `_${event}EmbeddedDocuments`;
const definingClass = foundry.utils.getDefiningClass(this.syntheticActor, legacyFn);
const isOverridden = definingClass?.name !== "ClientDocumentMixin";
if ( isOverridden && (this.syntheticActor[legacyFn] instanceof Function) ) {
const documentName = this.syntheticActor.constructor.hierarchy[collection].model.documentName;
const warning = `The Actor class defines ${legacyFn} method which is deprecated in favor of a new `
+ `_${event}DescendantDocuments method.`;
foundry.utils.logCompatibilityWarning(warning, { since: 11, until: 13 });
this.syntheticActor[legacyFn](documentName, ...args);
}
}
}
}