Esprit de la Loi + Automaton

This commit is contained in:
2026-05-02 23:16:10 +02:00
parent d6b5891519
commit 0df4a5a9fb
280 changed files with 10668 additions and 419 deletions
+241 -3
View File
@@ -219,8 +219,15 @@ export class MournbladeUtility {
'systems/fvtt-mournblade/templates/partial-item-description.hbs',
'systems/fvtt-mournblade/templates/partial-item-header.hbs',
'systems/fvtt-mournblade/templates/partial-item-nav.hbs',
'systems/fvtt-mournblade/templates/partial-item-enchantement.hbs',
'systems/fvtt-mournblade/templates/dialog-invocation-elementaire.hbs',
'systems/fvtt-mournblade/templates/chat-invocation-result.hbs',
'systems/fvtt-mournblade/templates/dialog-invocation-demon.hbs',
'systems/fvtt-mournblade/templates/chat-invocation-demon-result.hbs',
'systems/fvtt-mournblade/templates/dialog-enchantement.hbs',
'systems/fvtt-mournblade/templates/chat-enchantement-result.hbs',
'systems/fvtt-mournblade/templates/dialog-invocation-esprit.hbs',
'systems/fvtt-mournblade/templates/chat-invocation-esprit-result.hbs',
]
return foundry.applications.handlebars.loadTemplates(templatePaths);
}
@@ -1079,9 +1086,7 @@ export class MournbladeUtility {
return
}
const actor = rollData.tokenId
? game.canvas.tokens.get(rollData.tokenId)?.actor
: game.actors.get(rollData.actorId)
const actor = game.actors.get(rollData.actorId)
const pa = rollData.pointsAme ?? 1
const seuil = rollData.runeSeuil ?? 0
@@ -1269,6 +1274,8 @@ export class MournbladeUtility {
rollData.createdActorName = createdActorName
rollData.bonusPacte = bonusPacte
rollData.isGM = game.user.isGM
const powersByTier = { mineur: 2, median: 3, majeur: 4 }
rollData.invocationPowerCount = powersByTier[rollData.invocationTier] ?? 2
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(
@@ -1276,4 +1283,235 @@ export class MournbladeUtility {
}, { ...rollData, rollMode: "blindroll" })
}
/* -------------------------------------------- */
static async rollInvocationDemon(rollData) {
const actor = rollData.tokenId
? game.canvas.tokens.get(rollData.tokenId)?.actor
: game.actors.get(rollData.actorId)
if (!actor) {
ui.notifications.error("Acteur introuvable pour l'invocation démoniaque.")
return
}
const soulCost = rollData.invocationSoulCost ?? rollData.invocationSeuil ?? 20
const compNiveau = rollData.competence?.system?.niveau ?? 0
const compMod = compNiveau === 0 ? -3 : 0
const modificateur = rollData.modificateur ?? 0
// Validate soul
const ameDisponible = Math.max(0, actor.system.ame.currentmax - actor.system.ame.value)
if (ameDisponible < soulCost) {
ui.notifications.warn(`Âme insuffisante pour cette invocation (requis : ${soulCost}, disponible : ${ameDisponible}).`)
return
}
rollData.difficulte = rollData.invocationSeuil
rollData.diceFormula = `${rollData.mainDice ?? "1d10"}+${rollData.attr.value}+${compNiveau}+${modificateur}+${compMod}+${rollData.malusSante}+${rollData.malusAme}`
const myRoll = await new Roll(rollData.diceFormula).evaluate()
await this.showDiceSoNice(myRoll, "blindroll")
rollData.roll = foundry.utils.duplicate(myRoll)
rollData.diceResult = myRoll.terms[0].results[0].result
rollData.finalResult = myRoll.total
this.computeResult(rollData)
// Soul cost handling
let ameDeduct = soulCost
let d20Result = null
let isDemonAttaque = false
let isDisastreDramatique = false
let isTraitChaotique = false
if (rollData.isSuccess || rollData.isHeroique) {
// Soul spent immediately (not blocked)
await actor.subPointsAme("prononcer", soulCost)
// Track active invocation
const invocations = foundry.utils.duplicate(actor.system.invocationsDemons || [])
invocations.push({ demonName: "Démon invoqué", soulCost, date: Date.now() })
await actor.update({ "system.invocationsDemons": invocations })
} else if (rollData.isDramatique) {
// All soul lost
await actor.subPointsAme("prononcer", soulCost)
// Roll d20 for dramatic failure consequences
const d20Roll = await new Roll("1d20").evaluate()
await this.showDiceSoNice(d20Roll, "blindroll")
d20Result = d20Roll.total
if (d20Result === 1 || d20Result === 11) {
isDisastreDramatique = true
} else if (d20Result % 2 !== 0) {
// Odd (not 1 or 11) → demon attacks
isDemonAttaque = true
} else {
// Even → chaotic trait
isTraitChaotique = true
}
} else {
// Simple failure: half soul lost (round up)
ameDeduct = Math.ceil(soulCost / 2)
await actor.subPointsAme("prononcer", ameDeduct)
}
rollData.invocationSoulDeducted = (rollData.isSuccess || rollData.isHeroique) ? soulCost : ameDeduct
rollData.d20Result = d20Result
rollData.isDemonAttaque = isDemonAttaque
rollData.isDisastreDramatique = isDisastreDramatique
rollData.isTraitChaotique = isTraitChaotique
rollData.isGM = game.user.isGM
rollData.claValue = actor.system.attributs?.cla?.value ?? 0
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(
`systems/fvtt-mournblade/templates/chat-invocation-demon-result.hbs`, rollData)
}, { ...rollData, rollMode: "blindroll" })
}
/* -------------------------------------------- */
static async rollInvocationEsprit(rollData) {
const actor = rollData.tokenId
? game.canvas.tokens.get(rollData.tokenId)?.actor
: game.actors.get(rollData.actorId)
if (!actor) {
ui.notifications.error("Acteur introuvable pour l'invocation d'un Esprit de la Loi.")
return
}
const soulCost = rollData.invocationSoulCost ?? 15
const compNiveau = rollData.competence?.system?.niveau ?? 0
const compMod = compNiveau === 0 ? -3 : 0
const modificateur = rollData.modificateur ?? 0
const ameDisponible = Math.max(0, actor.system.ame.currentmax - actor.system.ame.value)
if (ameDisponible < soulCost) {
ui.notifications.warn(`Âme insuffisante (requis : ${soulCost}, disponible : ${ameDisponible}).`)
return
}
rollData.difficulte = soulCost
rollData.diceFormula = `${rollData.mainDice ?? "1d10"}+${rollData.attr.value}+${compNiveau}+${modificateur}+${compMod}+${rollData.malusSante}+${rollData.malusAme}`
const myRoll = await new Roll(rollData.diceFormula).evaluate()
await this.showDiceSoNice(myRoll, "roll")
rollData.roll = foundry.utils.duplicate(myRoll)
rollData.diceResult = myRoll.terms[0].results[0].result
rollData.finalResult = myRoll.total
this.computeResult(rollData)
let ameDeduct = soulCost
if (rollData.isSuccess || rollData.isHeroique) {
await actor.subPointsAme("prononcer", soulCost)
} else if (rollData.isDramatique) {
// All soul lost, Réceptacle destroyed
await actor.subPointsAme("prononcer", soulCost)
} else {
// Simple failure: half soul lost (round up)
ameDeduct = Math.ceil(soulCost / 2)
await actor.subPointsAme("prononcer", ameDeduct)
}
rollData.invocationSoulDeducted = (rollData.isSuccess || rollData.isHeroique) ? soulCost : ameDeduct
rollData.isGM = game.user.isGM
const typeLabels = {
combat: "Combat", voyage: "Voyage", perception: "Perception",
restauration: "Restauration", reparateur: "Réparateur",
}
const puissanceLabels = { mineur: "Mineur", median: "Médian", majeur: "Majeur" }
rollData.automatonTypeLabel = typeLabels[rollData.automatonType] ?? rollData.automatonType
rollData.automatonPuissanceLabel = puissanceLabels[rollData.automatonPuissance] ?? rollData.automatonPuissance
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(
`systems/fvtt-mournblade/templates/chat-invocation-esprit-result.hbs`, rollData)
}, { ...rollData, rollMode: "roll" })
}
/* -------------------------------------------- */
static async rollEnchantement({ actor, item, ptsAme, antiChaos, modificateur,
savoirRunesComp, hautParlerComp, artisanatComp, claValeur, limiteur }) {
// Validate soul
const ameDisponible = Math.max(0, actor.system.ame.currentmax - actor.system.ame.value)
if (ameDisponible < ptsAme) {
ui.notifications.warn(`Âme insuffisante (requis : ${ptsAme}, disponible : ${ameDisponible}).`)
return
}
const savoirNiveau = savoirRunesComp?.system?.niveau ?? 0
const compMod = savoirNiveau === 0 ? -3 : 0
const basePool = claValeur + savoirNiveau + compMod + (modificateur ?? 0)
const effectivePool = limiteur !== null ? Math.min(basePool, limiteur) : basePool
const difficulte = ptsAme
const formula = `1d10+${effectivePool}`
const myRoll = await new Roll(formula).evaluate()
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
const rollData = this.getBasicRollData()
rollData.alias = actor.name
rollData.actorImg = actor.img
rollData.diceResult = myRoll.terms[0].results[0].result
rollData.finalResult = myRoll.total
rollData.difficulte = difficulte
rollData.roll = foundry.utils.duplicate(myRoll)
this.computeResult(rollData)
// Compute bonus
let bonusBase = Math.floor(ptsAme / 5)
let bonusFinal = bonusBase
let ameDeduct = ptsAme
let itemDestroyed = false
let message = ""
if (rollData.isHeroique) {
bonusFinal = bonusBase + 1
await actor.subPointsAme("prononcer", ptsAme)
await item.update({
"system.enchantementLoi.actif": true,
"system.enchantementLoi.bonus": bonusFinal,
"system.enchantementLoi.antiChaos": antiChaos,
})
message = `Réussite héroïque ! L'objet est enchanté avec un bonus de +${bonusFinal}.`
} else if (rollData.isSuccess) {
await actor.subPointsAme("prononcer", ptsAme)
await item.update({
"system.enchantementLoi.actif": true,
"system.enchantementLoi.bonus": bonusFinal,
"system.enchantementLoi.antiChaos": antiChaos,
})
message = `Succès ! L'objet est enchanté avec un bonus de +${bonusFinal}.`
} else if (rollData.isDramatique) {
ameDeduct = ptsAme
await actor.subPointsAme("prononcer", ameDeduct)
await item.delete()
itemDestroyed = true
message = `Échec dramatique ! Tous les points d'Âme sont perdus et l'objet est détruit !`
} else {
// Failure: half soul lost
ameDeduct = Math.ceil(ptsAme / 2)
await actor.subPointsAme("prononcer", ameDeduct)
message = `Échec ! ${ameDeduct} points d'Âme perdus. L'objet n'est pas enchanté.`
}
rollData.itemName = item.name
rollData.itemImg = item.img
rollData.ptsAme = ptsAme
rollData.antiChaos = antiChaos
rollData.bonusFinal = bonusFinal
rollData.ameDeduct = ameDeduct
rollData.itemDestroyed = itemDestroyed
rollData.enchantMessage = message
rollData.effectivePool = effectivePool
rollData.savoirNiveau = savoirNiveau
this.createChatWithRollMode(actor.name, {
content: await foundry.applications.handlebars.renderTemplate(
`systems/fvtt-mournblade/templates/chat-enchantement-result.hbs`, rollData)
})
}
}