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:
2026-07-10 19:07:05 +02:00
parent 54e769176a
commit 1749210322
12 changed files with 567 additions and 22 deletions
+49 -14
View File
@@ -515,6 +515,12 @@
"quote": "Citation",
"resistances": "Résistances",
"rituals": "Rituels",
"amateur": "Amateur",
"competent": "Compétent",
"expert": "Expert",
"effects": "Effets",
"information": "Informations",
"newSortilege": "Nouveau sortilège",
"rollView": "Vue de jet",
"satanicSuccess": "Succès satanique",
"scars": "Cicatrices",
@@ -577,30 +583,58 @@
"Sortilege": {
"FIELDS": {
"categorie": {
"label": "Catégorie"
"label": "Type"
},
"cost": {
"label": "Coût"
"niveauRequis": {
"label": "Niveau requis"
},
"description": {
"label": "Description"
},
"difficulty": {
"label": "Difficulté"
"portee": {
"label": "Pore"
},
"ingredients": {
"label": "Ingrédients"
"duree": {
"label": "Durée"
},
"limit": {
"label": "Limite"
"conditions": {
"label": "Conditions"
},
"nbAttempts": {
"label": "Nombre de tentatives"
"effetAmateur": {
"label": "Effet Amateur"
},
"threshold": {
"label": "Seuil"
"effetCompetent": {
"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": {
"cancel": "Annuler",
@@ -732,6 +766,7 @@
"addTalent": "Ajouter un talent",
"addTrait": "Ajouter un trait",
"addWeapon": "Ajouter une arme",
"addSortilege": "Ajouter un sortilège",
"damages": "Entrer les dégâts actuels subis"
},
"ViewDetails": "Voir les détails",
+1
View File
@@ -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 HamalronActorSheet } from "./sheets/base-actor-sheet.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,
discardCard: HamalronPersonnageSheet.#onDiscardCard,
rollCompetence: HamalronPersonnageSheet.#onRollCompetence,
castSortilege: HamalronPersonnageSheet.#onCastSortilege,
drawCard: HamalronPersonnageSheet.#onDrawCard,
selectCard: HamalronPersonnageSheet.#onSelectCard,
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) {
const itemId = $(target).data("item-id")
const item = this.document.items.get(itemId)
@@ -23,7 +23,9 @@ export default class HamalronSortilegeSheet extends HamalronItemSheet {
async _prepareContext() {
const context = await super._prepareContext()
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
}
}
+290
View File
@@ -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)
}
}
+17 -2
View File
@@ -80,8 +80,21 @@ export const ENEMY_TYPES = {
}
export const SORTILEGE_CATEGORIES = {
"malefica": { id: "malefica", label: "Maléfica" },
"ritual": { id: "ritual", label: "Rituel" },
"domestique": { id: "domestique", label: "Domestique" },
"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 = {
@@ -141,6 +154,8 @@ export const SYSTEM = {
COMPETENCE_TYPES: COMPETENCE_TYPES,
ENEMY_TYPES: ENEMY_TYPES,
SORTILEGE_CATEGORIES: SORTILEGE_CATEGORIES,
SORTILEGE_MODIFICATEURS: SORTILEGE_MODIFICATEURS,
RANG_MAGICIEN: RANG_MAGICIEN,
NIVEAU_COMPETENCES: NIVEAU_COMPETENCES,
CHOICE_ADVANTAGES_DISADVANTAGES,
ASCII
+1
View File
@@ -34,6 +34,7 @@ export default class HamalronPersonnage extends foundry.abstract.TypeDataModel {
schema.historial = new fields.HTMLField({ required: true, textSearch: true })
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({
age: new fields.StringField({ required: true, nullable: false, initial: "" }),
+9 -4
View File
@@ -4,15 +4,20 @@ export default class HamalronSortilege extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const schema = {}
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.categorie = new fields.StringField({ required: true, initial: "malefica", choices: Object.fromEntries(Object.entries(SYSTEM.SORTILEGE_CATEGORIES).map(([key, val]) => [key, val.label])) })
schema.categorie = new fields.StringField({ required: true, initial: "domestique", 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
}
/** @override */
static LOCALIZATION_PREFIXES = ["HAMALRON.Sortilege"]
}
+55
View File
@@ -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>
+84
View File
@@ -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>
+9
View File
@@ -36,8 +36,17 @@
<span class="categorie" data-tooltip="Catégorie">{{upperFirst
item.system.categorie
}}</span>
<span class="niveau" data-tooltip="Niveau requis">{{upperFirst
item.system.niveauRequis
}}</span>
<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
data-tooltip="{{localize 'HAMALRON.Edit'}}"
data-action="edit"
+36
View File
@@ -11,7 +11,13 @@
</div>
<fieldset>
<legend>{{localize "HAMALRON.Label.information"}}</legend>
{{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>
@@ -25,4 +31,34 @@
}}
</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>