Esprit de la Loi + Automaton
This commit is contained in:
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user