feat: système de magie complet avec dialogue de lancement
- Modèle sortilege enrichi (type, niveau, portée, durée, conditions, effets) - Personnage: champ rangMagie (ensorceleur/initié/thaumaturge) - Constantes: SORTILEGE_MODIFICATEURS (table 3x3), RANG_MAGICIEN - Dialogue de lancement: tirage MJ, sélection atouts + Coupe, total temps réel - Calcul difficulté: carte MJ + modificateur selon type × niveau - Table des échecs magiques (normal/critique selon rang) - Pioche automatique après succès, défausse après échec - Template chat avec résultat localisé Corrections post-review: - Boucle de défausse sur échec corrigée (itérait toujours sur [0]) - Clé de localisation HAMALRON.Roll.cast -> HAMALRON.Sortilege.cast - Ajout de la clé manquante noCoupe - Labels localisés dans le chat (categorieLabel, niveauLabel)
This commit is contained in:
+49
-14
@@ -515,6 +515,12 @@
|
|||||||
"quote": "Citation",
|
"quote": "Citation",
|
||||||
"resistances": "Résistances",
|
"resistances": "Résistances",
|
||||||
"rituals": "Rituels",
|
"rituals": "Rituels",
|
||||||
|
"amateur": "Amateur",
|
||||||
|
"competent": "Compétent",
|
||||||
|
"expert": "Expert",
|
||||||
|
"effects": "Effets",
|
||||||
|
"information": "Informations",
|
||||||
|
"newSortilege": "Nouveau sortilège",
|
||||||
"rollView": "Vue de jet",
|
"rollView": "Vue de jet",
|
||||||
"satanicSuccess": "Succès satanique",
|
"satanicSuccess": "Succès satanique",
|
||||||
"scars": "Cicatrices",
|
"scars": "Cicatrices",
|
||||||
@@ -577,31 +583,59 @@
|
|||||||
"Sortilege": {
|
"Sortilege": {
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
"categorie": {
|
"categorie": {
|
||||||
"label": "Catégorie"
|
"label": "Type"
|
||||||
},
|
},
|
||||||
"cost": {
|
"niveauRequis": {
|
||||||
"label": "Coût"
|
"label": "Niveau requis"
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"label": "Description"
|
"label": "Description"
|
||||||
},
|
},
|
||||||
"difficulty": {
|
"portee": {
|
||||||
"label": "Difficulté"
|
"label": "Portée"
|
||||||
},
|
},
|
||||||
"ingredients": {
|
"duree": {
|
||||||
"label": "Ingrédients"
|
"label": "Durée"
|
||||||
},
|
},
|
||||||
"limit": {
|
"conditions": {
|
||||||
"label": "Limite"
|
"label": "Conditions"
|
||||||
},
|
},
|
||||||
"nbAttempts": {
|
"effetAmateur": {
|
||||||
"label": "Nombre de tentatives"
|
"label": "Effet Amateur"
|
||||||
},
|
},
|
||||||
"threshold": {
|
"effetCompetent": {
|
||||||
"label": "Seuil"
|
"label": "Effet Compétent"
|
||||||
}
|
},
|
||||||
|
"effetExpert": {
|
||||||
|
"label": "Effet Expert"
|
||||||
|
},
|
||||||
|
"concentration": {
|
||||||
|
"label": "Concentration"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cast": "Lancer le sort",
|
||||||
|
"noAtout": "Vous n'avez pas d'atout en main",
|
||||||
|
"noCoupe": "Vous n'avez pas de carte Coupe disponible",
|
||||||
|
"drawCard": "Tirer une carte",
|
||||||
|
"cardDrawn": "Carte tirée",
|
||||||
|
"cardValue": "Valeur de la carte",
|
||||||
|
"modifier": "Modificateur",
|
||||||
|
"totalDifficulty": "Difficulté totale",
|
||||||
|
"selectAtouts": "Choisir les atouts",
|
||||||
|
"atoutHint": "Sélectionnez un ou plusieurs atouts (lames majeures) à jouer. Leurs valeurs s'additionnent.",
|
||||||
|
"atoutTotal": "Total des atouts",
|
||||||
|
"selectCoupe": "Cartes Coupe bonus",
|
||||||
|
"coupeHint": "En tant que thaumaturge, vous pouvez ajouter des cartes Coupe.",
|
||||||
|
"coupeTotal": "Total Coupe",
|
||||||
|
"total": "Total",
|
||||||
|
"playerTotal": "Total du joueur",
|
||||||
|
"difficulty": "Difficulté du sort",
|
||||||
|
"rang": "Rang",
|
||||||
|
"atoutsUsed": "Atouts utilisés",
|
||||||
|
"coupeUsed": "Cartes Coupe utilisées",
|
||||||
|
"failureNormal": "Échec normal",
|
||||||
|
"failureCritical": "Échec critique"
|
||||||
|
},
|
||||||
"Roll": {
|
"Roll": {
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"roll": "Lancer l'épreuve",
|
"roll": "Lancer l'épreuve",
|
||||||
@@ -732,6 +766,7 @@
|
|||||||
"addTalent": "Ajouter un talent",
|
"addTalent": "Ajouter un talent",
|
||||||
"addTrait": "Ajouter un trait",
|
"addTrait": "Ajouter un trait",
|
||||||
"addWeapon": "Ajouter une arme",
|
"addWeapon": "Ajouter une arme",
|
||||||
|
"addSortilege": "Ajouter un sortilège",
|
||||||
"damages": "Entrer les dégâts actuels subis"
|
"damages": "Entrer les dégâts actuels subis"
|
||||||
},
|
},
|
||||||
"ViewDetails": "Voir les détails",
|
"ViewDetails": "Voir les détails",
|
||||||
|
|||||||
@@ -13,3 +13,4 @@ export { default as HamalronTarotSheet } from "./sheets/tarot-sheet.mjs"
|
|||||||
export { default as HamalronItemSheet } from "./sheets/base-item-sheet.mjs"
|
export { default as HamalronItemSheet } from "./sheets/base-item-sheet.mjs"
|
||||||
export { default as HamalronActorSheet } from "./sheets/base-actor-sheet.mjs"
|
export { default as HamalronActorSheet } from "./sheets/base-actor-sheet.mjs"
|
||||||
export { default as HamalronTarotDeckManager } from "./tarot-deck-manager.mjs"
|
export { default as HamalronTarotDeckManager } from "./tarot-deck-manager.mjs"
|
||||||
|
export { default as HamalronSortilegeCasting } from "./sortilege-casting.mjs"
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export default class HamalronPersonnageSheet extends HamalronActorSheet {
|
|||||||
modifyResistance: HamalronPersonnageSheet.#onModifyResistance,
|
modifyResistance: HamalronPersonnageSheet.#onModifyResistance,
|
||||||
discardCard: HamalronPersonnageSheet.#onDiscardCard,
|
discardCard: HamalronPersonnageSheet.#onDiscardCard,
|
||||||
rollCompetence: HamalronPersonnageSheet.#onRollCompetence,
|
rollCompetence: HamalronPersonnageSheet.#onRollCompetence,
|
||||||
|
castSortilege: HamalronPersonnageSheet.#onCastSortilege,
|
||||||
drawCard: HamalronPersonnageSheet.#onDrawCard,
|
drawCard: HamalronPersonnageSheet.#onDrawCard,
|
||||||
selectCard: HamalronPersonnageSheet.#onSelectCard,
|
selectCard: HamalronPersonnageSheet.#onSelectCard,
|
||||||
discardAndDraw: HamalronPersonnageSheet.#onDiscardAndDraw,
|
discardAndDraw: HamalronPersonnageSheet.#onDiscardAndDraw,
|
||||||
@@ -345,6 +346,17 @@ export default class HamalronPersonnageSheet extends HamalronActorSheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #onCastSortilege(event, target) {
|
||||||
|
const itemId = $(target).data("item-id")
|
||||||
|
const item = this.document.items.get(itemId)
|
||||||
|
if (!item || item.type !== "sortilege") {
|
||||||
|
console.warn(`HamalronPersonnageSheet: Sortilege with ID ${itemId} not found`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { default: HamalronSortilegeCasting } = await import("../sortilege-casting.mjs")
|
||||||
|
await HamalronSortilegeCasting.prompt(item, this.document)
|
||||||
|
}
|
||||||
|
|
||||||
static async #onRollCompetence(event, target) {
|
static async #onRollCompetence(event, target) {
|
||||||
const itemId = $(target).data("item-id")
|
const itemId = $(target).data("item-id")
|
||||||
const item = this.document.items.get(itemId)
|
const item = this.document.items.get(itemId)
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ export default class HamalronSortilegeSheet extends HamalronItemSheet {
|
|||||||
async _prepareContext() {
|
async _prepareContext() {
|
||||||
const context = await super._prepareContext()
|
const context = await super._prepareContext()
|
||||||
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
|
||||||
|
context.enrichedEffetAmateur = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.effetAmateur, { async: true })
|
||||||
|
context.enrichedEffetCompetent = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.effetCompetent, { async: true })
|
||||||
|
context.enrichedEffetExpert = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.effetExpert, { async: true })
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,290 @@
|
|||||||
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
import HamalronUtils from "../utils.mjs"
|
||||||
|
|
||||||
|
export default class HamalronSortilegeCasting {
|
||||||
|
static CHAT_TEMPLATE = "systems/fvtt-hamalron/templates/chat-sortilege.hbs"
|
||||||
|
static DIALOG_TEMPLATE = "systems/fvtt-hamalron/templates/dialog-sortilege-casting.hbs"
|
||||||
|
|
||||||
|
static getModificateur(categorie, niveau) {
|
||||||
|
const mods = SYSTEM.SORTILEGE_MODIFICATEURS[niveau]
|
||||||
|
return mods?.[categorie] ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
static getRangMagie(actor) {
|
||||||
|
return actor.system.rangMagie || "initie"
|
||||||
|
}
|
||||||
|
|
||||||
|
static getEchecMagique(valeurCarteMJ, rang) {
|
||||||
|
const rangs = ["ensorceleur", "initie", "thaumaturge"]
|
||||||
|
const idx = rangs.indexOf(rang)
|
||||||
|
if (idx === -1) return "normal"
|
||||||
|
const paliers = [
|
||||||
|
{ max: 10, resultats: ["normal", "normal", "normal"] },
|
||||||
|
{ max: 13, resultats: ["critique", "normal", "normal"] },
|
||||||
|
{ max: 16, resultats: ["critique", "normal", "normal"] },
|
||||||
|
{ max: 19, resultats: ["critique", "critique", "normal"] },
|
||||||
|
{ max: 23, resultats: ["critique", "critique", "critique"] },
|
||||||
|
]
|
||||||
|
for (const palier of paliers) {
|
||||||
|
if (valeurCarteMJ <= palier.max) {
|
||||||
|
return palier.resultats[idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "critique"
|
||||||
|
}
|
||||||
|
|
||||||
|
static async prompt(spellItem, actor) {
|
||||||
|
const atouts = actor.items.filter(i => i.type === "tarot" && i.system.type === "atout")
|
||||||
|
const rang = HamalronSortilegeCasting.getRangMagie(actor)
|
||||||
|
const peutUtiliserCoupe = rang === "thaumaturge"
|
||||||
|
const cartesCoupe = peutUtiliserCoupe
|
||||||
|
? actor.items.filter(i => i.type === "tarot" && i.system.symbole === "coupe" && i.system.type !== "atout")
|
||||||
|
: []
|
||||||
|
|
||||||
|
if (atouts.length === 0) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("HAMALRON.Sortilege.noAtout"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const niveauData = SYSTEM.NIVEAU_COMPETENCES[spellItem.system.niveauRequis]
|
||||||
|
const niveau = niveauData?.id || spellItem.system.niveauRequis
|
||||||
|
const modificateur = HamalronSortilegeCasting.getModificateur(spellItem.system.categorie, niveau)
|
||||||
|
|
||||||
|
const categorieLabel = SYSTEM.SORTILEGE_CATEGORIES[spellItem.system.categorie]?.label || spellItem.system.categorie
|
||||||
|
const niveauLabel = niveauData?.name || spellItem.system.niveauRequis
|
||||||
|
const dialogContext = {
|
||||||
|
spell: spellItem,
|
||||||
|
actorName: actor.name,
|
||||||
|
actorImage: actor.img,
|
||||||
|
rang: rang,
|
||||||
|
rangLabel: SYSTEM.RANG_MAGICIEN[rang]?.label || rang,
|
||||||
|
categorieLabel,
|
||||||
|
niveauLabel,
|
||||||
|
modificateur: modificateur >= 0 ? `+${modificateur}` : modificateur,
|
||||||
|
peutUtiliserCoupe,
|
||||||
|
atouts: atouts.map(c => ({
|
||||||
|
id: c.id,
|
||||||
|
name: c.name,
|
||||||
|
img: c.img,
|
||||||
|
valeur: c.system.valeur,
|
||||||
|
valeurNumerique: HamalronSortilegeCasting.#valeurNumerique(c.system.valeur),
|
||||||
|
})),
|
||||||
|
cartesCoupe: cartesCoupe.map(c => ({
|
||||||
|
id: c.id,
|
||||||
|
name: c.name,
|
||||||
|
img: c.img,
|
||||||
|
valeur: c.system.valeur,
|
||||||
|
valeurNumerique: HamalronSortilegeCasting.#valeurNumerique(c.system.valeur),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
HamalronSortilegeCasting.DIALOG_TEMPLATE, dialogContext
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = await foundry.applications.api.DialogV2.wait({
|
||||||
|
window: { title: `${game.i18n.localize("HAMALRON.Sortilege.cast")}: ${spellItem.name}` },
|
||||||
|
classes: ["fvtt-hamalron"],
|
||||||
|
position: { width: 520 },
|
||||||
|
content,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
action: "cancel",
|
||||||
|
label: game.i18n.localize("Cancel"),
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "cast",
|
||||||
|
label: game.i18n.localize("HAMALRON.Sortilege.cast"),
|
||||||
|
default: true,
|
||||||
|
disabled: true,
|
||||||
|
callback: (event, button, dialog) => {
|
||||||
|
const form = button.form.elements
|
||||||
|
const carteMJ = Number(form["carte-mj"].value) || 0
|
||||||
|
const selectedAtouts = Array.from(form.querySelectorAll('input[name="atout"]:checked'))
|
||||||
|
.map(el => atouts.find(c => c.id === el.value))
|
||||||
|
.filter(Boolean)
|
||||||
|
const selectedCoupe = Array.from(form.querySelectorAll('input[name="coupe"]:checked'))
|
||||||
|
.map(el => cartesCoupe.find(c => c.id === el.value))
|
||||||
|
.filter(Boolean)
|
||||||
|
return { carteMJ, selectedAtouts, selectedCoupe }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: {},
|
||||||
|
rejectClose: false,
|
||||||
|
render: (event, dialog) => {
|
||||||
|
const calcTotal = () => {
|
||||||
|
const carteMJ = Number(document.getElementById("carte-mj")?.value || 0)
|
||||||
|
const atoutTotal = Array.from(document.querySelectorAll('input[name="atout"]:checked'))
|
||||||
|
.reduce((sum, el) => sum + (Number(el.dataset.value) || 0), 0)
|
||||||
|
const coupeTotal = Array.from(document.querySelectorAll('input[name="coupe"]:checked'))
|
||||||
|
.reduce((sum, el) => sum + (Number(el.dataset.value) || 0), 0)
|
||||||
|
const total = atoutTotal + coupeTotal
|
||||||
|
const difficulte = carteMJ + modificateur
|
||||||
|
document.getElementById("total-atouts").textContent = String(atoutTotal)
|
||||||
|
document.getElementById("total-coupe").textContent = String(coupeTotal)
|
||||||
|
document.getElementById("total-joueur").textContent = String(total)
|
||||||
|
document.getElementById("difficulte-totale").textContent = String(difficulte)
|
||||||
|
const castBtn = document.querySelector("button[data-action='cast']")
|
||||||
|
const peutLancer = atoutTotal > 0 && carteMJ > 0
|
||||||
|
castBtn.disabled = !peutLancer
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('input[name="atout"], input[name="coupe"], #carte-mj')
|
||||||
|
.forEach(el => el.addEventListener("change", calcTotal))
|
||||||
|
|
||||||
|
document.getElementById("draw-card-btn")?.addEventListener("click", async () => {
|
||||||
|
const module = await import("./tarot-deck-manager.mjs")
|
||||||
|
const HamalronTarotDeckManager = module.default
|
||||||
|
if (!HamalronTarotDeckManager._instance) {
|
||||||
|
HamalronTarotDeckManager._instance = new HamalronTarotDeckManager()
|
||||||
|
}
|
||||||
|
const cards = HamalronTarotDeckManager._instance.drawCards(1)
|
||||||
|
if (cards.length > 0) {
|
||||||
|
const valeurData = SYSTEM.TAROT_VALEURS[cards[0].valeur]
|
||||||
|
const value = valeurData?.value || 0
|
||||||
|
document.getElementById("carte-mj").value = String(value)
|
||||||
|
document.getElementById("carte-mj-nom").textContent = `${cards[0].name} (${value})`
|
||||||
|
calcTotal()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
calcTotal()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!result) return
|
||||||
|
if (result.carteMJ === 0 || result.selectedAtouts.length === 0) return
|
||||||
|
|
||||||
|
const totalAtouts = result.selectedAtouts.reduce((sum, c) => {
|
||||||
|
return sum + (HamalronSortilegeCasting.#valeurNumerique(c.system.valeur) || 0)
|
||||||
|
}, 0)
|
||||||
|
const totalCoupe = result.selectedCoupe.reduce((sum, c) => {
|
||||||
|
return sum + (HamalronSortilegeCasting.#valeurNumerique(c.system.valeur) || 0)
|
||||||
|
}, 0)
|
||||||
|
const totalJoueur = totalAtouts + totalCoupe
|
||||||
|
const difficulte = result.carteMJ + modificateur
|
||||||
|
const isSuccess = totalJoueur >= difficulte
|
||||||
|
|
||||||
|
const deckModule = await import("./tarot-deck-manager.mjs")
|
||||||
|
const HamalronTarotDeckManager = deckModule.default
|
||||||
|
if (!HamalronTarotDeckManager._instance) {
|
||||||
|
HamalronTarotDeckManager._instance = new HamalronTarotDeckManager()
|
||||||
|
}
|
||||||
|
const deckManager = HamalronTarotDeckManager._instance
|
||||||
|
|
||||||
|
if (!isSuccess) {
|
||||||
|
const echecType = HamalronSortilegeCasting.getEchecMagique(result.carteMJ, rang)
|
||||||
|
const nbCartesADefausser = Math.max(1, result.selectedAtouts.length)
|
||||||
|
|
||||||
|
for (let i = 0; i < nbCartesADefausser && i < result.selectedAtouts.length; i++) {
|
||||||
|
const cardToDiscard = result.selectedAtouts[i]
|
||||||
|
deckManager.addToDiscard({
|
||||||
|
id: cardToDiscard.id,
|
||||||
|
name: cardToDiscard.name,
|
||||||
|
img: cardToDiscard.img,
|
||||||
|
symbole: cardToDiscard.system.symbole,
|
||||||
|
valeur: cardToDiscard.system.valeur,
|
||||||
|
type: cardToDiscard.system.type,
|
||||||
|
})
|
||||||
|
const cardItem = actor.items.get(cardToDiscard.id)
|
||||||
|
if (cardItem) {
|
||||||
|
await actor.deleteEmbeddedDocuments("Item", [cardItem.id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatDataFail = {
|
||||||
|
cssClass: [SYSTEM.id, "sortilege-roll"].join(" "),
|
||||||
|
isSuccess: false,
|
||||||
|
isNormal: echecType === "normal",
|
||||||
|
isCritique: echecType === "critique",
|
||||||
|
actingCharImg: actor.img,
|
||||||
|
actingCharName: actor.name,
|
||||||
|
spell: spellItem,
|
||||||
|
categorieLabel,
|
||||||
|
niveauLabel,
|
||||||
|
atoutsUtilises: result.selectedAtouts,
|
||||||
|
cartesCoupeUtilisees: result.selectedCoupe,
|
||||||
|
carteMJ: result.carteMJ,
|
||||||
|
modificateur,
|
||||||
|
difficulte,
|
||||||
|
totalJoueur,
|
||||||
|
}
|
||||||
|
const failContent = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
HamalronSortilegeCasting.CHAT_TEMPLATE, chatDataFail
|
||||||
|
)
|
||||||
|
await ChatMessage.create({
|
||||||
|
user: game.user.id,
|
||||||
|
speaker: ChatMessage.getSpeaker({ actor }),
|
||||||
|
content: failContent,
|
||||||
|
type: CONST.CHAT_MESSAGE_TYPES.OTHER,
|
||||||
|
})
|
||||||
|
|
||||||
|
await deckManager.saveDeckState()
|
||||||
|
deckManager.render()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const card of [...result.selectedAtouts, ...result.selectedCoupe]) {
|
||||||
|
deckManager.addToDiscard({
|
||||||
|
id: card.id,
|
||||||
|
name: card.name,
|
||||||
|
img: card.img,
|
||||||
|
symbole: card.system.symbole,
|
||||||
|
valeur: card.system.valeur,
|
||||||
|
type: card.system.type,
|
||||||
|
})
|
||||||
|
const cardItem = actor.items.get(card.id)
|
||||||
|
if (cardItem) {
|
||||||
|
await actor.deleteEmbeddedDocuments("Item", [card.id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nbCartesAPiocher = result.selectedAtouts.length + result.selectedCoupe.length
|
||||||
|
for (let i = 0; i < nbCartesAPiocher; i++) {
|
||||||
|
if (deckManager.deck.length > 0) {
|
||||||
|
const drawn = deckManager.drawCards(1)
|
||||||
|
if (drawn.length > 0) {
|
||||||
|
const originalItem = await HamalronUtils.findTarotItem(drawn[0])
|
||||||
|
if (originalItem) {
|
||||||
|
await actor.createEmbeddedDocuments("Item", [originalItem.toObject()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatDataSuccess = {
|
||||||
|
cssClass: [SYSTEM.id, "sortilege-roll"].join(" "),
|
||||||
|
isSuccess: true,
|
||||||
|
actingCharImg: actor.img,
|
||||||
|
actingCharName: actor.name,
|
||||||
|
spell: spellItem,
|
||||||
|
categorieLabel,
|
||||||
|
niveauLabel,
|
||||||
|
atoutsUtilises: result.selectedAtouts,
|
||||||
|
cartesCoupeUtilisees: result.selectedCoupe,
|
||||||
|
carteMJ: result.carteMJ,
|
||||||
|
modificateur,
|
||||||
|
difficulte,
|
||||||
|
totalJoueur,
|
||||||
|
}
|
||||||
|
const chatContent = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
HamalronSortilegeCasting.CHAT_TEMPLATE, chatDataSuccess
|
||||||
|
)
|
||||||
|
await ChatMessage.create({
|
||||||
|
user: game.user.id,
|
||||||
|
speaker: ChatMessage.getSpeaker({ actor }),
|
||||||
|
content: chatContent,
|
||||||
|
type: CONST.CHAT_MESSAGE_TYPES.OTHER,
|
||||||
|
})
|
||||||
|
|
||||||
|
await deckManager.saveDeckState()
|
||||||
|
deckManager.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
static #valeurNumerique(valeur) {
|
||||||
|
const data = SYSTEM.TAROT_VALEURS[valeur]
|
||||||
|
return data?.value ?? (Number.parseInt(valeur) || 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -80,8 +80,21 @@ export const ENEMY_TYPES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SORTILEGE_CATEGORIES = {
|
export const SORTILEGE_CATEGORIES = {
|
||||||
"malefica": { id: "malefica", label: "Maléfica" },
|
"domestique": { id: "domestique", label: "Domestique" },
|
||||||
"ritual": { id: "ritual", label: "Rituel" },
|
"specialise": { id: "specialise", label: "Spécialisé" },
|
||||||
|
"miraculeux": { id: "miraculeux", label: "Miraculeux" },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SORTILEGE_MODIFICATEURS = {
|
||||||
|
amateur: { domestique: 2, specialise: 6, miraculeux: 9 },
|
||||||
|
competent: { domestique: 0, specialise: 4, miraculeux: 7 },
|
||||||
|
expert: { domestique: -2, specialise: 2, miraculeux: 5 },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RANG_MAGICIEN = {
|
||||||
|
ensorceleur: { id: "ensorceleur", label: "Ensorceleur" },
|
||||||
|
initie: { id: "initie", label: "Initié" },
|
||||||
|
thaumaturge: { id: "thaumaturge", label: "Thaumaturge" },
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NIVEAU_COMPETENCES = {
|
export const NIVEAU_COMPETENCES = {
|
||||||
@@ -141,6 +154,8 @@ export const SYSTEM = {
|
|||||||
COMPETENCE_TYPES: COMPETENCE_TYPES,
|
COMPETENCE_TYPES: COMPETENCE_TYPES,
|
||||||
ENEMY_TYPES: ENEMY_TYPES,
|
ENEMY_TYPES: ENEMY_TYPES,
|
||||||
SORTILEGE_CATEGORIES: SORTILEGE_CATEGORIES,
|
SORTILEGE_CATEGORIES: SORTILEGE_CATEGORIES,
|
||||||
|
SORTILEGE_MODIFICATEURS: SORTILEGE_MODIFICATEURS,
|
||||||
|
RANG_MAGICIEN: RANG_MAGICIEN,
|
||||||
NIVEAU_COMPETENCES: NIVEAU_COMPETENCES,
|
NIVEAU_COMPETENCES: NIVEAU_COMPETENCES,
|
||||||
CHOICE_ADVANTAGES_DISADVANTAGES,
|
CHOICE_ADVANTAGES_DISADVANTAGES,
|
||||||
ASCII
|
ASCII
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export default class HamalronPersonnage extends foundry.abstract.TypeDataModel {
|
|||||||
|
|
||||||
schema.historial = new fields.HTMLField({ required: true, textSearch: true })
|
schema.historial = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
schema.progression = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
schema.progression = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
schema.rangMagie = new fields.StringField({ required: true, initial: "initie", choices: Object.fromEntries(Object.entries(SYSTEM.RANG_MAGICIEN).map(([key, val]) => [key, val.label])) })
|
||||||
|
|
||||||
schema.biodata = new fields.SchemaField({
|
schema.biodata = new fields.SchemaField({
|
||||||
age: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
age: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
|||||||
@@ -4,15 +4,20 @@ export default class HamalronSortilege extends foundry.abstract.TypeDataModel {
|
|||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields
|
const fields = foundry.data.fields
|
||||||
const schema = {}
|
const schema = {}
|
||||||
const requiredInteger = { required: true, nullable: false, integer: true }
|
|
||||||
|
|
||||||
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
schema.categorie = new fields.StringField({ required: true, initial: "domestique", choices: Object.fromEntries(Object.entries(SYSTEM.SORTILEGE_CATEGORIES).map(([key, val]) => [key, val.label])) })
|
||||||
schema.categorie = new fields.StringField({ required: true, initial: "malefica", choices: Object.fromEntries(Object.entries(SYSTEM.SORTILEGE_CATEGORIES).map(([key, val]) => [key, val.label])) })
|
schema.niveauRequis = new fields.StringField({ required: true, initial: "amateur", choices: Object.fromEntries(Object.entries(SYSTEM.NIVEAU_COMPETENCES).map(([key, val]) => [key, val.name])) })
|
||||||
|
schema.portee = new fields.StringField({ required: false, initial: "" })
|
||||||
|
schema.duree = new fields.StringField({ required: false, initial: "" })
|
||||||
|
schema.conditions = new fields.StringField({ required: false, initial: "" })
|
||||||
|
schema.effetAmateur = new fields.HTMLField({ required: false, textSearch: true })
|
||||||
|
schema.effetCompetent = new fields.HTMLField({ required: false, textSearch: true })
|
||||||
|
schema.effetExpert = new fields.HTMLField({ required: false, textSearch: true })
|
||||||
|
schema.concentration = new fields.BooleanField({ required: true, initial: false })
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
static LOCALIZATION_PREFIXES = ["HAMALRON.Sortilege"]
|
static LOCALIZATION_PREFIXES = ["HAMALRON.Sortilege"]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<div class="{{cssClass}}">
|
||||||
|
<div class="sortilege-header">
|
||||||
|
<img src="{{actingCharImg}}" class="actor-avatar" data-tooltip="{{actingCharName}}" />
|
||||||
|
<div>
|
||||||
|
<h3>{{spell.name}}</h3>
|
||||||
|
<span>{{categorieLabel}} · {{niveauLabel}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sortilege-cards-used">
|
||||||
|
<h4>{{localize "HAMALRON.Sortilege.atoutsUsed"}}</h4>
|
||||||
|
<div class="used-cards">
|
||||||
|
{{#each atoutsUtilises}}
|
||||||
|
<div class="used-card">
|
||||||
|
<img src="{{this.img}}" />
|
||||||
|
<span>{{this.name}}</span>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{#if cartesCoupeUtilisees.length}}
|
||||||
|
<h4>{{localize "HAMALRON.Sortilege.coupeUsed"}}</h4>
|
||||||
|
<div class="used-cards">
|
||||||
|
{{#each cartesCoupeUtilisees}}
|
||||||
|
<div class="used-card">
|
||||||
|
<img src="{{this.img}}" />
|
||||||
|
<span>{{this.name}}</span>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sortilege-result">
|
||||||
|
<div class="calc-row">
|
||||||
|
<span>{{localize "HAMALRON.Sortilege.playerTotal"}}</span>
|
||||||
|
<span>{{totalJoueur}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="calc-row">
|
||||||
|
<span>{{localize "HAMALRON.Sortilege.totalDifficulty"}}</span>
|
||||||
|
<span>{{difficulte}} (carte {{carteMJ}} + {{modificateur}})</span>
|
||||||
|
</div>
|
||||||
|
<div class="calc-divider"></div>
|
||||||
|
<div class="calc-row total-row {{#if isSuccess}}result-success{{else}}result-failure{{/if}}">
|
||||||
|
<span>
|
||||||
|
{{#if isSuccess}}
|
||||||
|
<i class="fas fa-check-circle"></i> {{localize "HAMALRON.Label.success"}}
|
||||||
|
{{else if isCritique}}
|
||||||
|
<i class="fas fa-bolt"></i> {{localize "HAMALRON.Sortilege.failureCritical"}}
|
||||||
|
{{else}}
|
||||||
|
<i class="fas fa-times-circle"></i> {{localize "HAMALRON.Sortilege.failureNormal"}}
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<div class="fvtt-hamalron-sortilege-dialog">
|
||||||
|
<div class="dialog-info">
|
||||||
|
<img src="{{actorImage}}" alt="{{actorName}}" class="actor-portrait" />
|
||||||
|
<div class="spell-info">
|
||||||
|
<h3>{{spell.name}}</h3>
|
||||||
|
<span class="spell-meta">{{categorieLabel}} · {{niveauLabel}}</span>
|
||||||
|
<span class="spell-meta">{{localize "HAMALRON.Sortilege.rang"}} : {{rangLabel}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{localize "HAMALRON.Sortilege.difficulty"}}</legend>
|
||||||
|
<div class="difficulty-row">
|
||||||
|
<button type="button" id="draw-card-btn" class="draw-btn">
|
||||||
|
<i class="fas fa-cards"></i> {{localize "HAMALRON.Sortilege.drawCard"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="difficulty-row">
|
||||||
|
<label>{{localize "HAMALRON.Sortilege.cardDrawn"}} :</label>
|
||||||
|
<span id="carte-mj-nom">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="difficulty-row">
|
||||||
|
<label>{{localize "HAMALRON.Sortilege.cardValue"}} :</label>
|
||||||
|
<input type="number" id="carte-mj" name="carte-mj" value="0" min="0" max="23" />
|
||||||
|
</div>
|
||||||
|
<div class="difficulty-row">
|
||||||
|
<label>{{localize "HAMALRON.Sortilege.modifier"}} :</label>
|
||||||
|
<span>{{modificateur}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="difficulty-row">
|
||||||
|
<label>{{localize "HAMALRON.Sortilege.totalDifficulty"}} :</label>
|
||||||
|
<span id="difficulte-totale">0</span>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{localize "HAMALRON.Sortilege.selectAtouts"}}</legend>
|
||||||
|
<p class="hint">{{localize "HAMALRON.Sortilege.atoutHint"}}</p>
|
||||||
|
<div class="card-select-grid">
|
||||||
|
{{#each atouts}}
|
||||||
|
<label class="card-option">
|
||||||
|
<input type="checkbox" name="atout" value="{{this.id}}" data-value="{{this.valeurNumerique}}" />
|
||||||
|
<img src="{{this.img}}" alt="{{this.name}}" />
|
||||||
|
<span>{{this.name}} ({{this.valeurNumerique}})</span>
|
||||||
|
</label>
|
||||||
|
{{else}}
|
||||||
|
<p class="no-cards">{{localize "HAMALRON.Sortilege.noAtout"}}</p>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
<div class="card-total">
|
||||||
|
<label>{{localize "HAMALRON.Sortilege.atoutTotal"}} :</label>
|
||||||
|
<span id="total-atouts">0</span>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
{{#if peutUtiliserCoupe}}
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{localize "HAMALRON.Sortilege.selectCoupe"}}</legend>
|
||||||
|
<p class="hint">{{localize "HAMALRON.Sortilege.coupeHint"}}</p>
|
||||||
|
<div class="card-select-grid">
|
||||||
|
{{#each cartesCoupe}}
|
||||||
|
<label class="card-option">
|
||||||
|
<input type="checkbox" name="coupe" value="{{this.id}}" data-value="{{this.valeurNumerique}}" />
|
||||||
|
<img src="{{this.img}}" alt="{{this.name}}" />
|
||||||
|
<span>{{this.name}} ({{this.valeurNumerique}})</span>
|
||||||
|
</label>
|
||||||
|
{{else}}
|
||||||
|
<p class="no-cards">{{localize "HAMALRON.Sortilege.noCoupe"}}</p>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
<div class="card-total">
|
||||||
|
<label>{{localize "HAMALRON.Sortilege.coupeTotal"}} :</label>
|
||||||
|
<span id="total-coupe">0</span>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<fieldset class="result-row">
|
||||||
|
<legend>{{localize "HAMALRON.Sortilege.total"}}</legend>
|
||||||
|
<div class="total-display">
|
||||||
|
<span>{{localize "HAMALRON.Sortilege.playerTotal"}} : <strong id="total-joueur">0</strong></span>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
@@ -36,8 +36,17 @@
|
|||||||
<span class="categorie" data-tooltip="Catégorie">{{upperFirst
|
<span class="categorie" data-tooltip="Catégorie">{{upperFirst
|
||||||
item.system.categorie
|
item.system.categorie
|
||||||
}}</span>
|
}}</span>
|
||||||
|
<span class="niveau" data-tooltip="Niveau requis">{{upperFirst
|
||||||
|
item.system.niveauRequis
|
||||||
|
}}</span>
|
||||||
|
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
<a
|
||||||
|
data-tooltip="{{localize 'HAMALRON.Roll.cast'}}"
|
||||||
|
data-action="castSortilege"
|
||||||
|
data-item-id="{{item.id}}"
|
||||||
|
data-item-uuid="{{item.uuid}}"
|
||||||
|
><i class="fas fa-wand-magic-sparkles"></i></a>
|
||||||
<a
|
<a
|
||||||
data-tooltip="{{localize 'HAMALRON.Edit'}}"
|
data-tooltip="{{localize 'HAMALRON.Edit'}}"
|
||||||
data-action="edit"
|
data-action="edit"
|
||||||
|
|||||||
@@ -11,7 +11,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
<legend>{{localize "HAMALRON.Label.information"}}</legend>
|
||||||
{{formField systemFields.categorie value=system.categorie localize=true}}
|
{{formField systemFields.categorie value=system.categorie localize=true}}
|
||||||
|
{{formField systemFields.niveauRequis value=system.niveauRequis localize=true}}
|
||||||
|
{{formField systemFields.concentration value=system.concentration localize=true}}
|
||||||
|
{{formField systemFields.portee value=system.portee localize=true}}
|
||||||
|
{{formField systemFields.duree value=system.duree localize=true}}
|
||||||
|
{{formField systemFields.conditions value=system.conditions localize=true}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@@ -25,4 +31,34 @@
|
|||||||
}}
|
}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{localize "HAMALRON.Label.effects"}}</legend>
|
||||||
|
|
||||||
|
<label>{{localize "HAMALRON.Label.amateur"}}</label>
|
||||||
|
{{formInput
|
||||||
|
systemFields.effetAmateur
|
||||||
|
enriched=enrichedEffetAmateur
|
||||||
|
value=system.effetAmateur
|
||||||
|
name="system.effetAmateur"
|
||||||
|
toggled=true
|
||||||
|
}}
|
||||||
|
|
||||||
|
<label>{{localize "HAMALRON.Label.competent"}}</label>
|
||||||
|
{{formInput
|
||||||
|
systemFields.effetCompetent
|
||||||
|
enriched=enrichedEffetCompetent
|
||||||
|
value=system.effetCompetent
|
||||||
|
name="system.effetCompetent"
|
||||||
|
toggled=true
|
||||||
|
}}
|
||||||
|
|
||||||
|
<label>{{localize "HAMALRON.Label.expert"}}</label>
|
||||||
|
{{formInput
|
||||||
|
systemFields.effetExpert
|
||||||
|
enriched=enrichedEffetExpert
|
||||||
|
value=system.effetExpert
|
||||||
|
name="system.effetExpert"
|
||||||
|
toggled=true
|
||||||
|
}}
|
||||||
|
</fieldset>
|
||||||
</section>
|
</section>
|
||||||
Reference in New Issue
Block a user