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

177 lines
5.7 KiB
JavaScript

/**
* The client-side Card document which extends the common BaseCard document model.
* @extends foundry.documents.BaseCard
* @mixes ClientDocumentMixin
*
* @see {@link Cards} The Cards document type which contains Card embedded documents
* @see {@link CardConfig} The Card configuration application
*/
class Card extends ClientDocumentMixin(foundry.documents.BaseCard) {
/**
* The current card face
* @type {CardFaceData|null}
*/
get currentFace() {
if ( this.face === null ) return null;
const n = Math.clamp(this.face, 0, this.faces.length-1);
return this.faces[n] || null;
}
/**
* The image of the currently displayed card face or back
* @type {string}
*/
get img() {
return this.currentFace?.img || this.back.img || Card.DEFAULT_ICON;
}
/**
* A reference to the source Cards document which defines this Card.
* @type {Cards|null}
*/
get source() {
return this.parent?.type === "deck" ? this.parent : this.origin;
}
/**
* A convenience property for whether the Card is within its source Cards stack. Cards in decks are always
* considered home.
* @type {boolean}
*/
get isHome() {
return (this.parent?.type === "deck") || (this.origin === this.parent);
}
/**
* Whether to display the face of this card?
* @type {boolean}
*/
get showFace() {
return this.faces[this.face] !== undefined;
}
/**
* Does this Card have a next face available to flip to?
* @type {boolean}
*/
get hasNextFace() {
return (this.face === null) || (this.face < this.faces.length - 1);
}
/**
* Does this Card have a previous face available to flip to?
* @type {boolean}
*/
get hasPreviousFace() {
return this.face !== null;
}
/* -------------------------------------------- */
/* Core Methods */
/* -------------------------------------------- */
/** @override */
prepareDerivedData() {
super.prepareDerivedData();
this.back.img ||= this.source?.img || Card.DEFAULT_ICON;
this.name = (this.showFace ? (this.currentFace.name || this._source.name) : this.back.name)
|| game.i18n.format("CARD.Unknown", {source: this.source?.name || game.i18n.localize("Unknown")});
}
/* -------------------------------------------- */
/* API Methods */
/* -------------------------------------------- */
/**
* Flip this card to some other face. A specific face may be requested, otherwise:
* If the card currently displays a face the card is flipped to the back.
* If the card currently displays the back it is flipped to the first face.
* @param {number|null} [face] A specific face to flip the card to
* @returns {Promise<Card>} A reference to this card after the flip operation is complete
*/
async flip(face) {
// Flip to an explicit face
if ( Number.isNumeric(face) || (face === null) ) return this.update({face});
// Otherwise, flip to default
return this.update({face: this.face === null ? 0 : null});
}
/* -------------------------------------------- */
/**
* Pass this Card to some other Cards document.
* @param {Cards} to A new Cards document this card should be passed to
* @param {object} [options={}] Options which modify the pass operation
* @param {object} [options.updateData={}] Modifications to make to the Card as part of the pass operation,
* for example the displayed face
* @returns {Promise<Card>} A reference to this card after it has been passed to another parent document
*/
async pass(to, {updateData={}, ...options}={}) {
const created = await this.parent.pass(to, [this.id], {updateData, action: "pass", ...options});
return created[0];
}
/* -------------------------------------------- */
/**
* @alias Card#pass
* @see Card#pass
* @inheritdoc
*/
async play(to, {updateData={}, ...options}={}) {
const created = await this.parent.pass(to, [this.id], {updateData, action: "play", ...options});
return created[0];
}
/* -------------------------------------------- */
/**
* @alias Card#pass
* @see Card#pass
* @inheritdoc
*/
async discard(to, {updateData={}, ...options}={}) {
const created = await this.parent.pass(to, [this.id], {updateData, action: "discard", ...options});
return created[0];
}
/* -------------------------------------------- */
/**
* Recall this Card to its original Cards parent.
* @param {object} [options={}] Options which modify the recall operation
* @returns {Promise<Card>} A reference to the recalled card belonging to its original parent
*/
async recall(options={}) {
// Mark the original card as no longer drawn
const original = this.isHome ? this : this.source?.cards.get(this.id);
if ( original ) await original.update({drawn: false});
// Delete this card if it's not the original
if ( !this.isHome ) await this.delete();
return original;
}
/* -------------------------------------------- */
/**
* Create a chat message which displays this Card.
* @param {object} [messageData={}] Additional data which becomes part of the created ChatMessageData
* @param {object} [options={}] Options which modify the message creation operation
* @returns {Promise<ChatMessage>} The created chat message
*/
async toMessage(messageData={}, options={}) {
messageData = foundry.utils.mergeObject({
content: `<div class="card-draw flexrow">
<img class="card-face" src="${this.img}" alt="${this.name}"/>
<h4 class="card-name">${this.name}</h4>
</div>`
}, messageData);
return ChatMessage.implementation.create(messageData, options);
}
}