const ACTOR_EMBEDDED_DOCTYPES = ['Item', 'ActiveEffect'] /** * class designed to store actor modification instructions, to apply them in a single operation, and have the ability to revert these */ export class ActorImpacts { static $newDocumentImpacts(docType) { return { creates: [], deletes: [], updates: [], docType: docType } } static $checkDocType(docType) { if (!ACTOR_EMBEDDED_DOCTYPES.includes(docType)) { throw `Unsupported document type ${docType}` } } constructor(actorToken) { this.actorToken = actorToken this.updates = [] this.deltas = [] ACTOR_EMBEDDED_DOCTYPES.forEach( docType => this[docType] = ActorImpacts.$newDocumentImpacts(docType) ) } addActorUpdate(path, value) { this.updates.push([path, value]) } addActorDelta(path, value) { const intValue = Number.parseInt(value) if (Number.isInteger(intValue) && intValue != 0) { const delta = [path, intValue] this.deltas.push(delta) } else { console.error('Cannot use non integer value {} for delta update', value) } } addDeleted(docType, document) { ActorImpacts.$checkDocType(docType) this[docType].deletes.push(document) } addCreated(docType, document) { ActorImpacts.$checkDocType(docType) this[docType].creates.push(document) } addUpdate(docType, document, path, value) { ActorImpacts.$checkDocType(docType) const update = [path, value] const existing = this[docType].updates.find(it => it.id == document.id) if (existing) { existing.updates.push(update) } else { this[docType].updates.push({ id: document.id, updates: [update], deltas: [] }) } } addDelta(document, path, value) { ActorImpacts.$checkDocType(document) const intValue = Number.parseInt(value) if (Number.isInteger(intValue) && intValue != 0) { const delta = [path, intValue] const existing = this[docType].updates.find(it => it.id == document.id) if (existing) { existing.deltas.push(delta) } else { this[docType].updates.push({ id: document.id, updates: [], deltas: [delta] }) } } else { console.error('Cannot use non-integer value {} for delta update', value) } } reverseImpacts() { const reverse = ActorImpacts.$computeReverts(new ActorImpacts(this.actorToken), this, __ => this.actorToken.actor) ACTOR_EMBEDDED_DOCTYPES.forEach( docType => { reverse[docType].creates = this[docType].deletes.map(it => foundry.utils.duplicate(it)) reverse[docType].deletes = this[docType].creates.map(it => { return { id: it.id } }) reverse[docType].updates = this[docType].updates.map(it => ActorImpacts.$computeReverts({ id: it.id }, it, id => this.$getEmbeddedDocument(docType, id))) } ) return reverse } toStorable() { delete this.actorToken return this } async applyImpacts() { const actor = this.actorToken.actor await Promise.all(ACTOR_EMBEDDED_DOCTYPES.map(async docType => await this.$applyDocumentsImpacts(actor, docType))) const updates = ActorImpacts.$computeUpdates(this, id => actor) await actor.update(updates, { render: true }) } async $applyDocumentsImpacts(actor, docType) { if (this[docType].deletes.length > 0) { const deletes = this[docType].deletes.map(it => it.id) await actor.deleteEmbeddedDocuments(docType, deletes, { render: false }) } if (this[docType].creates.length > 0) { const creates = this[docType].creates const created = await actor.createEmbeddedDocuments(docType, creates, { render: false }) for (let i = 0; i < creates.length; i++) { creates[i].createdId = created[i].id } } if (this[docType].updates.length > 0) { const updates = this[docType].updates.map(u => ActorImpacts.$computeUpdates(u, id => this.$getEmbeddedDocument(docType, id))) await actor.updateEmbeddedDocuments(docType, updates, { render: false }) } } findCreatedId(docType, origId){ return this[docType].creates.find(it => it.id = origId)?.createdId } $getEmbeddedDocument(docType, id) { return this.actorToken.actor.getEmbeddedDocument(docType, id) } static $computeUpdates(u, getSource) { if (u.updates.length == 0 && u.deltas.length == 0) { return {} } const source = getSource(u.id) const instruction = { _id: u.id } u.updates.forEach(u => instruction[u[0]] = u[1]) u.deltas.forEach(u => instruction[u[0]] = foundry.utils.getProperty(source, u[0]) + u[1]) return instruction } static $computeReverts(target, u, getSource) { const source = getSource(u.id) target.updates = u.updates.map(u => [u[0], foundry.utils.getProperty(source, u[0])]) target.deltas = u.deltas.map(d => [d[0], -d[1]]) return target } }