Files
fvtt-hamalron/module/documents/tirage-tarot.mjs
T

841 lines
33 KiB
JavaScript

import { SYSTEM } from "../config/system.mjs"
import HamalronUtils from "../utils.mjs"
export default class HamalronTirageTarot extends Roll {
/**
* The HTML template path used to render dice checks of this type
* @type {string}
*/
static CHAT_TEMPLATE = "systems/fvtt-hamalron/templates/chat-message.hbs"
/**
* Handle Le Mat special effect when distributed to a player
* @param {Actor} actor - The actor who received Le Mat
* @param {HamalronTarotDeckManager} deckManager - The deck manager instance
*/
static async handleLeMatEffect(actor, deckManager) {
// 1. Défausser toutes les cartes du joueur
const allTarotCards = actor.items.filter(i => i.type === "tarot")
if (allTarotCards.length > 0) {
const cardIds = allTarotCards.map(c => c.id)
await actor.deleteEmbeddedDocuments("Item", cardIds)
console.log(`Discarded ${cardIds.length} cards from player`)
// Ajouter toutes les cartes à la défausse
for (const card of allTarotCards) {
deckManager.addToDiscard({
id: card.id,
name: card.name,
img: card.img,
symbole: card.system.symbole,
valeur: card.system.valeur,
type: card.system.type,
description: card.system.description,
})
}
}
// 2. Tirer 7 nouvelles cartes pour le joueur
for (let i = 0; i < 7; i++) {
if (deckManager.deck.length > 0) {
const newCards = deckManager.drawCards(1)
if (newCards.length > 0) {
const newCard = newCards[0]
const originalItem = await HamalronUtils.findTarotItem(newCard)
if (originalItem) {
await actor.createEmbeddedDocuments("Item", [originalItem.toObject()])
}
}
}
}
// 3. Regrouper la défausse et le deck, puis mélanger
await deckManager.resetDeck()
deckManager.render()
// 4. Proposer aux autres joueurs de renouveler leur main
if (game.user.isGM) {
const refreshOthers = await foundry.applications.api.DialogV2.confirm({
window: { title: "Le Mat - Renouvellement" },
content: `<p>Le Mat a été tiré ! Les autres joueurs peuvent renouveler leur main s'ils le désirent.</p>`,
rejectClose: false,
modal: true,
})
if (refreshOthers) {
for (const otherActor of game.actors.filter(a => a.type === "personnage")) {
if (otherActor.id === actor.id) continue
const otherCards = otherActor.items.filter(i => i.type === "tarot")
if (otherCards.length > 0) {
const otherCardIds = otherCards.map(c => c.id)
await otherActor.deleteEmbeddedDocuments("Item", otherCardIds)
for (let i = 0; i < 7; i++) {
if (deckManager.deck.length > 0) {
const newCards = deckManager.drawCards(1)
if (newCards.length > 0) {
const originalItem = await HamalronUtils.findTarotItem(newCards[0])
if (originalItem) {
await otherActor.createEmbeddedDocuments("Item", [originalItem.toObject()])
}
}
}
}
}
}
await deckManager.saveDeckState()
deckManager.render()
}
}
ui.notifications.info(`Le Mat ! Toutes vos cartes ont été défaussées et vous recevez 7 nouvelles cartes. Le deck a été mélangé.`)
}
// Carte de symbole par défaut pour les épreuves sans compétence
static async promptEpreuve(actor) {
const tarotCards = actor.items.filter(i => i.type === "tarot")
if (tarotCards.length === 0) {
ui.notifications.warn(game.i18n.localize("HAMALRON.Label.noTarotCards"))
return
}
const cardData = tarotCards.map(c => ({
id: c.id,
name: c.name,
img: c.img,
valeur: c.system.valeur,
symbole: c.system.symbole,
type: c.system.type,
valeurNumerique: HamalronTirageTarot.#vNumerique(c.system.valeur),
}))
const content = `
<div class="fvtt-hamalron-roll-dialog">
<fieldset>
<legend>${game.i18n.localize("HAMALRON.Label.selectTarotCard")}</legend>
<div class="tarot-hand">
${cardData.map(c => `
<div class="tarot-card-select" data-card-id="${c.id}" data-card-value="${c.valeurNumerique}">
<img src="${c.img}" alt="${c.name}" />
<span class="card-name">${c.name}</span>
<span class="card-value-display">${c.valeurNumerique}</span>
</div>
`).join("")}
</div>
</fieldset>
<fieldset>
<legend>${game.i18n.localize("HAMALRON.Label.difficulty")}</legend>
<div class="difficulty-row">
<select class="difficulty-select-epreuve">
${Object.entries(SYSTEM.DIFFICULTY_CHOICES).filter(([k]) => k !== "soustension_oppose").map(([k, v]) =>
`<option value="${v.standard}">${v.label} (${v.standard})</option>`
).join("")}
</select>
</div>
</fieldset>
<fieldset class="dialog-result">
<legend>${game.i18n.localize("HAMALRON.Label.total")}</legend>
<div class="total-display">
<span id="card-value-display">+ 0</span>
<span>=</span>
<span id="total-value">0</span>
</div>
<div class="difficulty-info">
<span>${game.i18n.localize("HAMALRON.Label.targetScore")}: <span id="target-score">3</span></span>
</div>
</fieldset>
</div>`
const result = await foundry.applications.api.DialogV2.wait({
window: { title: game.i18n.localize("HAMALRON.Label.titleStandard") },
classes: ["fvtt-hamalron"],
position: { width: 420 },
content,
buttons: [{ action: "roll", label: game.i18n.localize("HAMALRON.Roll.roll"), default: true, callback: (e, btn) => {
const selected = btn.form.querySelector('.tarot-card-select.selected')
if (!selected) return null
return {
cardId: selected.dataset.cardId,
cardValue: Number(selected.dataset.cardValue) || 0,
difficulty: Number(btn.form.querySelector('.difficulty-select-epreuve')?.value || 3),
}
}}, { action: "cancel", label: game.i18n.localize("HAMALRON.Roll.cancel") }],
rejectClose: false,
render: () => {
$(document).off(".epreuve")
$(document).on("click.epreuve", ".tarot-card-select", function () {
$(".tarot-card-select").removeClass("selected")
$(this).addClass("selected")
const val = Number($(this).data("cardValue")) || 0
$("#card-value-display").text(`+ ${val}`)
$("#total-value").text(String(val))
})
$(document).on("change.epreuve", ".difficulty-select-epreuve", function () {
$("#target-score").text($(this).val())
})
},
})
if (!result) return
const cardItem = actor.items.get(result.cardId)
const isSuccess = result.cardValue >= result.difficulty
// Card used: move to discard
const deckModule = await import("../applications/tarot-deck-manager.mjs")
const DM = deckModule.default
if (DM._instance) {
DM._instance.addToDiscard({
id: cardItem.id, name: cardItem.name, img: cardItem.img,
symbole: cardItem.system.symbole, valeur: cardItem.system.valeur,
type: cardItem.system.type,
})
await DM._instance.saveDeckState()
}
if (cardItem) await actor.deleteEmbeddedDocuments("Item", [cardItem.id])
// Draw replacement
if (DM._instance?.deck?.length > 0) {
const drawn = DM._instance.drawCards(1)
if (drawn.length > 0) {
const orig = await HamalronUtils.findTarotItem(drawn[0])
if (orig) await actor.createEmbeddedDocuments("Item", [orig.toObject()])
}
await DM._instance.saveDeckState()
}
ChatMessage.create({
user: game.user.id,
speaker: ChatMessage.getSpeaker({ actor }),
content: `<div class="fvtt-hamalron epreuve-result">
<h3>${game.i18n.localize("HAMALRON.Label.titleStandard")}</h3>
<p><strong>${cardItem?.name}</strong>: ${result.cardValue}</p>
<p>${game.i18n.localize("HAMALRON.Label.difficulty")}: ${result.difficulty}</p>
<p class="${isSuccess ? "result-success" : "result-failure"}">
${isSuccess ? game.i18n.localize("HAMALRON.Label.success") : game.i18n.localize("HAMALRON.Label.failure")}
</p>
</div>`,
style: CONST.CHAT_MESSAGE_STYLES.OTHER,
})
}
static #vNumerique(valeur) {
return SYSTEM.TAROT_VALEURS[valeur]?.value ?? (Number.parseInt(valeur) || 0)
}
static async prompt(options = {}) {
let formula = `3D6 + 0D6KH - 0D6KH + ${options?.rollItem?.value}`
let actor = game.actors.get(options.actorId)
switch (options.rollType) {
case "stat":
break
case "competence":
{
// Récupérer les cartes de tarot du personnage
options.tarotCards = actor.items.filter(i => i.type === "tarot")
// Récupérer le modificateur de compétence depuis NIVEAU_COMPETENCES
const niveauData = SYSTEM.NIVEAU_COMPETENCES[options.rollItem.system.niveau]
options.competenceModifier = niveauData ? niveauData.modificateur : 0
if (actor.system.malusBlessure) {
options.competenceModifier += actor.system.malusBlessure
}
// Marquer les cartes de succès
if (options.tarotCards && actor.system.cartesSucces) {
const competenceSymbole = options.rollItem.system.carteSymbole
options.tarotCards = options.tarotCards.map(card => {
const isSuccessCard = card.system.type !== "atout" &&
card.system.symbole === competenceSymbole &&
actor.system.cartesSucces[competenceSymbole] &&
String(card.system.valeur) === String(actor.system.cartesSucces[competenceSymbole].value)
// Créer un objet simple avec toutes les propriétés nécessaires
return {
id: card.id,
name: card.name,
img: card.img,
system: card.system,
isSuccessCard: isSuccessCard
}
})
}
}
break
case "damage":
{
let formula = options.rollItem.system.damage
if (options.rollItem?.system?.damageStat && options.rollItem.system.damageStat !== "none" && options.rollItem.system.damageStat !== "") {
let statKey = options.rollItem.system?.damageStat.toLowerCase()
let statValue = actor.system?.stats?.[statKey]?.value
if (statValue != null) {
formula = `${formula} + ${statValue}`
}
}
let damageRoll = new Roll(formula)
await damageRoll.evaluate()
await damageRoll.toMessage({
flavor: `<div class="flavor-text-damage"><h2>${options.rollItem.name}</h2>
<BR><span class="chat-damage-type"><span>${options.rollItem.system.damageType}</span></span></div>`,
});
return
}
case "weapon":
{
options.weapon = foundry.utils.duplicate(options.rollItem)
let statKey = options.weapon.system?.stat?.toLowerCase()
if (statKey && actor.system?.stats?.[statKey]) {
options.rollItem = actor.system.stats[statKey]
}
}
break
default:
break
}
const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes)
const fieldRollMode = new foundry.data.fields.StringField({
choices: rollModes,
blank: false,
default: "public",
})
options.formula = formula
options.nbAdvantages = "0"
options.nbDisadvantages = "0"
let dialogContext = {
actorId: options.actorId,
actorName: options.actorName,
actorImage: options.actorImage,
rollType: options.rollType,
rollItem: foundry.utils.duplicate(options.rollItem), // Object only, no class
weapon: options?.weapon,
formula: formula,
fullFormula: formula,
rollModes,
fieldRollMode,
choiceAdvantages: SYSTEM.CHOICE_ADVANTAGES_DISADVANTAGES,
choiceDisadvantages: SYSTEM.CHOICE_ADVANTAGES_DISADVANTAGES,
hasTarget: options.hasTarget,
difficulty: "0",
nbAdvantages: "0",
nbDisadvantages: "0",
tarotCards: options.tarotCards || [],
competenceModifier: options.competenceModifier || 0,
difficultyChoices: Object.fromEntries(Object.entries(SYSTEM.DIFFICULTY_CHOICES).map(([key, val]) => [key, val.label])),
}
const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-hamalron/templates/roll-dialog.hbs", dialogContext)
const title = HamalronTirageTarot.createTitle(options.rollType, options.rollTarget)
const label = game.i18n.localize("HAMALRON.Roll.roll")
const cancelLabel = game.i18n.localize("HAMALRON.Roll.cancel")
const rollContext = await foundry.applications.api.DialogV2.wait({
window: { title: title },
classes: ["fvtt-hamalron"],
position: { width: 420 },
content,
buttons: [
{
action: "cancel",
label: cancelLabel,
default: false,
},
{
action: "roll",
label: label,
default: true,
disabled: options.rollType === "competence",
callback: (event, button, dialog) => {
const output = Array.from(button.form.elements).reduce((obj, input) => {
if (input.name) obj[input.name] = input.value
return obj
}, {})
// Ajouter les données de sélection de carte pour les compétences
if (options.rollType === "competence") {
console.log("DEBUG CALLBACK: options.selectedCardId =", options.selectedCardId, "options.selectedCardValue =", options.selectedCardValue)
output.selectedCardId = options.selectedCardId
output.selectedCardValue = options.selectedCardValue
output.selectedDifficulty = options.selectedDifficulty
output.targetScore = options.targetScore
console.log("DEBUG CALLBACK: output =", output)
}
return output
},
},
],
actions: {
},
rejectClose: false, // Click on Close button will not launch an error
render: (event, dialog) => {
// Gestion de la sélection de carte pour les compétences
if (options.rollType === "competence") {
let selectedCardId = null
options.selectedDifficulty = "soustension_oppose"
options.targetScore = null
// Calculer et afficher les valeurs pour chaque carte
const competenceSymbole = options.rollItem.system.carteSymbole
$(".tarot-card-select").each(function () {
const cardSymbole = $(this).data("card-symbole")
const cardValeur = $(this).data("card-valeur")
const cardType = $(this).data("card-type")
const valeurData = SYSTEM.TAROT_VALEURS[cardValeur]
const baseValue = valeurData ? valeurData.value : 0
let calculatedValue = 0
if (cardType === "atout") {
calculatedValue = baseValue
} else if (cardSymbole === competenceSymbole) {
calculatedValue = baseValue
} else {
calculatedValue = Math.ceil(baseValue / 2)
}
$(this).find(".card-values").text(`${baseValue}${calculatedValue}`)
})
// Gestion du changement de difficulté
$(".difficulty-select").change(function () {
const difficultyKey = $(this).val()
options.selectedDifficulty = difficultyKey
const difficultyData = SYSTEM.DIFFICULTY_CHOICES[difficultyKey]
if (difficultyData.standard === null) {
options.targetScore = null
$("#target-score").text("-")
} else {
options.targetScore = difficultyData.standard
$("#target-score").text(options.targetScore)
}
})
// Tirage d'une carte pour la difficulté (épreuve sous tension)
$("#draw-difficulty-btn").click(async function () {
if (!game.user.isGM) return
const deckManager = globalThis.HamalronTarotDeckManager?._instance
if (!deckManager || deckManager.deck.length === 0) {
ui.notifications.warn(game.i18n.localize("HAMALRON.Notifications.deckEmpty"))
return
}
const drawn = deckManager.drawCards(1)
if (drawn.length > 0) {
const card = drawn[0]
const valData = SYSTEM.TAROT_VALEURS[card.valeur]
const val = valData?.value ?? 0
options.targetScore = val
options.selectedDifficulty = "soustension_oppose"
$(".difficulty-select").val("soustension_oppose")
$("#target-score").text(String(val))
$("#drawn-card-name").text(`${card.name} (${val})`)
$("#drawn-card-info").show()
deckManager.addToDiscard(card)
await deckManager.saveDeckState()
}
})
$(".tarot-card-select").click(function () {
// Désélectionner toutes les cartes
$(".tarot-card-select").removeClass("selected")
// Sélectionner la carte cliquée
$(this).addClass("selected")
selectedCardId = $(this).data("card-id")
console.log("DEBUG CLICK: selectedCardId =", selectedCardId)
const cardSymbole = $(this).data("card-symbole")
const cardValeur = $(this).data("card-valeur")
const cardType = $(this).data("card-type")
// Calculer la valeur de la carte
const competenceSymbole = options.rollItem.system.carteSymbole
let cardValue = 0
// Récupérer la valeur numérique de la carte depuis TAROT_VALEURS
const valeurData = SYSTEM.TAROT_VALEURS[cardValeur]
const baseValue = valeurData ? valeurData.value : 0
if (cardType === "atout") {
// Si c'est un atout, prendre directement la valeur
cardValue = baseValue
} else if (cardSymbole === competenceSymbole) {
// Si le symbole correspond, prendre la valeur complète
cardValue = baseValue
} else {
// Sinon, diviser par 2 arrondi au supérieur
cardValue = Math.ceil(baseValue / 2)
}
// Stocker la carte sélectionnée et sa valeur calculée
options.selectedCardId = selectedCardId
options.selectedCardValue = cardValue
console.log("DEBUG CLICK END: options.selectedCardId =", options.selectedCardId, "options.selectedCardValue =", options.selectedCardValue)
// Mettre à jour l'affichage
$("#card-value-display").text(`+ ${cardValue}`)
$("#total-value").text(options.competenceModifier + cardValue)
// Activer le bouton de lancement
$("button[data-action='roll']").prop("disabled", false)
})
// Permettre de désélectionner en cliquant à nouveau
$(".tarot-card-select").dblclick(function () {
$(this).removeClass("selected")
options.selectedCardId = null
options.selectedCardValue = 0
$("#card-value-display").text("+ 0")
$("#total-value").text(options.competenceModifier)
// Désactiver le bouton de lancement
$("button[data-action='roll']").prop("disabled", true)
})
}
}
})
// If the user cancels the dialog, exit
if (rollContext === null) return
// Fusionner les données du dialogue dans options
Object.assign(options, rollContext)
// Gestion spéciale pour les compétences (pas de dés)
if (options.rollType === "competence") {
const totalValue = options.competenceModifier + (options.selectedCardValue || 0)
// Récupérer la carte sélectionnée si elle existe
let selectedCard = null
let cardSymboleMatch = false
let isAutomaticSuccess = false
if (options.selectedCardId) {
selectedCard = actor.items.get(options.selectedCardId)
if (selectedCard) {
cardSymboleMatch = selectedCard.system.symbole === options.rollItem.system.carteSymbole
if (actor.system.cartesSucces && selectedCard.system.type !== "atout") {
const competenceSymbole = options.rollItem.system.carteSymbole
isAutomaticSuccess = selectedCard.system.symbole === competenceSymbole &&
actor.system.cartesSucces[competenceSymbole] &&
String(selectedCard.system.valeur) === String(actor.system.cartesSucces[competenceSymbole].value)
}
}
}
// Préparer les données pour le chat
const difficultyData = SYSTEM.DIFFICULTY_CHOICES[options.selectedDifficulty || "moyenne"]
const isSousTensionOppose = options.selectedDifficulty === "soustension_oppose"
const isSuccess = isAutomaticSuccess || totalValue >= options.targetScore
// Détecter si la carte est un valet
const isValet = selectedCard?.system.valeur === "valet"
// Détecter si la carte est un cavalier
const isCavalier = selectedCard?.system.valeur === "cavalier"
// Détecter si la carte est une reine
const isReine = selectedCard?.system.valeur === "reine"
// Détecter si la carte est un roi
const isRoi = selectedCard?.system.valeur === "roi"
// Détecter si la carte est un atout
const isAtout = selectedCard?.system.type === "atout"
// Si c'est un cavalier et un échec, transformer en égalité
const isEquality = isCavalier && !isSuccess && !isSousTensionOppose
const chatData = {
cssClass: [SYSTEM.id, "competence-roll"].join(" "),
actingCharImg: options.actorImage,
actingCharName: options.actorName,
actorName: options.actorName,
competence: options.rollItem,
competenceModifier: options.competenceModifier,
selectedCard: selectedCard,
cardBonus: options.selectedCardValue || 0,
cardSymboleMatch: cardSymboleMatch,
totalValue: totalValue,
difficulty: difficultyData.label,
targetScore: options.targetScore,
isSuccess: isSuccess,
isAutomaticSuccess: isAutomaticSuccess,
isSousTensionOppose: isSousTensionOppose,
isValet: isValet,
isCavalier: isCavalier,
isReine: isReine,
isRoi: isRoi,
isAtout: isAtout,
isEquality: isEquality,
}
// Créer le message de chat
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-hamalron/templates/chat-competence.hbs",
chatData
)
const messageData = {
user: game.user.id,
speaker: ChatMessage.getSpeaker({ actor }),
content: content,
style: CONST.CHAT_MESSAGE_STYLES.OTHER,
}
// Appliquer le mode de visibilité
ChatMessage.applyMode(messageData, rollContext.visibility)
await ChatMessage.create(messageData)
// Gérer la carte utilisée
console.log("DEBUG: selectedCard =", selectedCard, "options.selectedCardId =", options.selectedCardId)
if (selectedCard) {
console.log("Managing used card:", selectedCard.name)
// Supprimer la carte de l'acteur
const cardItem = actor.items.get(selectedCard.id)
if (cardItem) {
await actor.deleteEmbeddedDocuments("Item", [selectedCard.id])
console.log("Card deleted from actor")
}
// Récupérer le deck manager singleton
const module = await import("../applications/tarot-deck-manager.mjs")
const HamalronTarotDeckManager = module.default
let deckManager = HamalronTarotDeckManager._instance
// Si le deck manager n'existe pas, le créer
if (!deckManager) {
console.log("Deck manager not initialized, creating new instance...")
deckManager = new HamalronTarotDeckManager()
await deckManager.resetDeck()
}
console.log("Deck manager instance:", deckManager)
if (deckManager) {
console.log("Deck manager is available")
// Ajouter la carte à la défausse
deckManager.addToDiscard({
id: selectedCard.id,
name: selectedCard.name,
img: selectedCard.img,
symbole: selectedCard.system.symbole,
valeur: selectedCard.system.valeur,
type: selectedCard.system.type,
description: selectedCard.system.description,
})
console.log("Card added to discard")
// Tirer une nouvelle carte automatiquement
console.log("Attempting to draw new card. Deck length:", deckManager.deck.length)
if (deckManager.deck.length > 0) {
const newCards = deckManager.drawCards(1)
console.log("Cards drawn:", newCards)
if (newCards.length > 0) {
const newCard = newCards[0]
console.log("New card to add:", newCard)
const originalItem = await HamalronUtils.findTarotItem(newCard)
if (originalItem) {
// Cas spécial : Le Mat
if (newCard.valeur === "lemat") {
console.log("Le Mat drawn! Executing special rules...")
await HamalronTirageTarot.handleLeMatEffect(actor, deckManager)
} else {
console.log("Creating embedded document for actor...")
await actor.createEmbeddedDocuments("Item", [originalItem.toObject()])
console.log("New card added to actor")
ui.notifications.info(`${game.i18n.localize("HAMALRON.Notifications.newCardDrawn")}: ${newCard.name}`)
}
} else {
console.warn("Original item not found for card:", newCard)
}
} else {
console.log("No cards were drawn (empty result)")
}
} else {
console.log("Deck is empty, cannot draw new card")
}
await deckManager.saveDeckState()
deckManager.render()
ui.notifications.info(`${game.i18n.localize("HAMALRON.Notifications.cardUsed")}: ${selectedCard.name}`)
}
}
return null
}
// Code existant pour les autres types de jets
let rollData = foundry.utils.mergeObject(foundry.utils.duplicate(options), rollContext)
rollData.rollMode = rollContext.visibility
rollData.targetScore = 8
if (Hooks.call("fvtt-hamalron.preRoll", options, rollData) === false) return
options.nbAdvantages = Number(options.nbAdvantages)
options.nbDisadvantages = Number(options.nbDisadvantages)
let dice = 3;
let keep = ""
if ((options.nbAdvantages !== options.nbDisadvantages) && options.nbAdvantages > 0 || options.nbDisadvantages > 0) {
dice = 3 + Math.abs(options.nbAdvantages - options.nbDisadvantages)
if (options.nbAdvantages > options.nbDisadvantages) {
keep = "kh3"
} else if (options.nbAdvantages < options.nbDisadvantages) {
keep = "kl3"
}
}
let diceFormula = `${dice}D6${keep} + ${options.rollItem.value}`
const roll = new this(diceFormula, options.data, rollData)
await roll.evaluate()
console.log("Roll", rollData, roll)
options.difficulty = (rollData.difficulty === "unknown") ? 0 : (Number(rollData.difficulty) || 0)
roll.displayRollResult(roll, options, rollData, roll)
if (Hooks.call("fvtt-hamalron.Roll", options, rollData, roll) === false) return
return roll
}
displayRollResult(formula, options, rollData, roll) {
let resultType = "failure"
if (options.difficulty === 0) {
resultType = "unknown"
} else if (this.total >= options.difficulty) {
resultType = "success"
}
// Compute the result quality
this.options.satanicSuccess = false
this.options.fiendishFailure = false
this.options.rollData = foundry.utils.duplicate(rollData)
// Check if all results are equal
let workResults = foundry.utils.duplicate(roll.terms[0].results)
// Get the most common result of the roll
let commonResult = workResults.reduce((acc, r) => {
acc[r.result] = (acc[r.result] || 0) + 1
return acc
}, {})
commonResult = Object.entries(commonResult).reduce((a, b) => (a[1] > b[1]) ? a : b)[0]
let nbEqual = workResults.filter(r => Number(r.result) === Number(commonResult)).length
if (resultType === "success" && commonResult >= 4 && nbEqual >= 3) {
this.options.satanicSuccess = true
if (this.options.satanicSuccess) {
resultType = "success"
}
}
if (resultType === "failure" && commonResult <= 3 && nbEqual >= 3) {
this.options.fiendishFailure = true
if (this.options.fiendishFailure) {
resultType = "failure"
}
}
this.options.resultType = resultType
this.options.isSuccess = resultType === "success"
this.options.isFailure = resultType === "failure"
this.options.results = roll.terms[0].results
}
/**
* Creates a title based on the given type.
*
* @param {string} type The type of the roll.
* @param {string} target The target of the roll.
* @returns {string} The generated title.
*/
static createTitle(type, target) {
switch (type) {
case "stat":
return `${game.i18n.localize("HAMALRON.Label.titleStat")}`
case "weapon":
return `${game.i18n.localize("HAMALRON.Label.titleWeapon")}`
case "competence":
return `${game.i18n.localize("HAMALRON.Label.titleCompetence")}`
default:
return game.i18n.localize("HAMALRON.Label.titleStandard")
}
}
/** @override */
async render(chatOptions = {}) {
let chatData = await this._getChatCardData(chatOptions.isPrivate)
return await foundry.applications.handlebars.renderTemplate(this.constructor.CHAT_TEMPLATE, chatData)
}
/**
* Generates the data required for rendering a roll chat card.
*
* @param {boolean} isPrivate Indicates if the chat card is private.
* @returns {Promise<Object>} A promise that resolves to an object containing the chat card data.
* @property {Array<string>} css - CSS classes for the chat card.
* @property {Object} data - The data associated with the roll.
* @property {number} diceTotal - The total value of the dice rolled.
* @property {boolean} isGM - Indicates if the user is a Game Master.
* @property {string} formula - The formula used for the roll.
* @property {number} total - The total result of the roll.
* @property {boolean} isFailure - Indicates if the roll is a failure.
* @property {string} actorId - The ID of the actor performing the roll.
* @property {string} actingCharName - The name of the character performing the roll.
* @property {string} actingCharImg - The image of the character performing the roll.
* @property {string} resultType - The type of result (e.g., success, failure).
* @property {boolean} hasTarget - Indicates if the roll has a target.
* @property {string} targetName - The name of the target.
* @property {number} targetArmor - The armor value of the target.
* @property {number} realDamage - The real damage dealt.
* @property {boolean} isPrivate - Indicates if the chat card is private.
* @property {string} cssClass - The combined CSS classes as a single string.
* @property {string} tooltip - The tooltip text for the chat card.
*/
async _getChatCardData(isPrivate) {
let cardData = foundry.utils.duplicate(this.options)
cardData.css = [SYSTEM.id, "dice-roll"]
cardData.data = this.data
cardData.diceTotal = this.dice.reduce((t, d) => t + d.total, 0)
cardData.isGM = game.user.isGM
cardData.formula = this.formula
cardData.fullFormula = this.options.fullFormula
cardData.numericModifier = this.options.numericModifier
cardData.total = this.total
cardData.actorId = this.actorId
cardData.actingCharName = this.actorName
cardData.actingCharImg = this.actorImage
cardData.resultType = this.resultType
cardData.hasTarget = this.hasTarget
cardData.targetName = this.targetName
cardData.targetArmor = this.targetArmor
cardData.realDamage = this.realDamage
cardData.isPrivate = isPrivate
cardData.weapon = this.weapon
cardData.cssClass = cardData.css.join(" ")
cardData.tooltip = isPrivate ? "" : await this.getTooltip()
return cardData
}
/**
* Converts the roll result to a chat message.
*
* @param {Object} [messageData={}] Additional data to include in the message.
* @param {Object} options Options for message creation.
* @param {string} options.rollMode The mode of the roll (e.g., public, private).
* @param {boolean} [options.create=true] Whether to create the message.
* @returns {Promise} - A promise that resolves when the message is created.
*/
async toMessage(messageData = {}, { rollMode, create = true } = {}) {
return super.toMessage(
{
isFailure: this.resultType === "failure",
actingCharName: this.actorName,
actingCharImg: this.actorImage,
hasTarget: this.hasTarget,
realDamage: this.realDamage,
...messageData,
},
{ rollMode, create },
)
}
}