709 lines
28 KiB
JavaScript
709 lines
28 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é.`)
|
|
}
|
|
|
|
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 },
|
|
)
|
|
}
|
|
|
|
}
|