All checks were successful
Release Creation / build (release) Successful in 48s
821 lines
27 KiB
JavaScript
821 lines
27 KiB
JavaScript
import { SYSTEM } from "./config/system.mjs"
|
|
|
|
// Map temporaire pour stocker les données d'attaque en attente de défense
|
|
if (!globalThis.pendingDefenses) {
|
|
globalThis.pendingDefenses = new Map()
|
|
}
|
|
|
|
export default class LethalFantasyUtils {
|
|
|
|
/* -------------------------------------------- */
|
|
static async loadCompendiumData(compendium) {
|
|
const pack = game.packs.get(compendium)
|
|
return await pack?.getDocuments() ?? []
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static async loadCompendium(compendium, filter = item => true) {
|
|
let compendiumData = await LethalFantasyUtils.loadCompendiumData(compendium)
|
|
return compendiumData.filter(filter)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static pushCombatOptions(html, options) {
|
|
options.push({ name: "Reset Progression", condition: true, icon: '<i class="fas fa-rotate-right"></i>', callback: target => { game.combat.resetProgression(target.data('combatant-id')); } })
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static setHookListeners() {
|
|
|
|
Hooks.on('renderTokenHUD', async (hud, html, token) => {
|
|
const lossHPButton = await foundry.applications.handlebars.renderTemplate('systems/fvtt-lethal-fantasy/templates/loss-hp-hud.hbs', {})
|
|
$(html).find('div.left').append(lossHPButton);
|
|
$(html).find('img.lethal-hp-loss-hud').click((event) => {
|
|
event.preventDefault();
|
|
let hpMenu = $(html).find('.hp-loss-wrap')[0]
|
|
if (hpMenu.classList.contains("hp-loss-hud-disabled")) {
|
|
$(html).find('.hp-loss-wrap')[0].classList.add('hp-loss-hud-active');
|
|
$(html).find('.hp-loss-wrap')[0].classList.remove('hp-loss-hud-disabled');
|
|
$(html).find('.hp-loss-wrap')[1].classList.add('hp-loss-hud-active');
|
|
$(html).find('.hp-loss-wrap')[1].classList.remove('hp-loss-hud-disabled');
|
|
$(html).find('.hp-loss-wrap')[2].classList.add('hp-loss-hud-active');
|
|
$(html).find('.hp-loss-wrap')[2].classList.remove('hp-loss-hud-disabled');
|
|
} else {
|
|
$(html).find('.hp-loss-wrap')[0].classList.remove('hp-loss-hud-active');
|
|
$(html).find('.hp-loss-wrap')[0].classList.add('hp-loss-hud-disabled');
|
|
$(html).find('.hp-loss-wrap')[1].classList.remove('hp-loss-hud-active');
|
|
$(html).find('.hp-loss-wrap')[1].classList.add('hp-loss-hud-disabled');
|
|
$(html).find('.hp-loss-wrap')[2].classList.remove('hp-loss-hud-active');
|
|
$(html).find('.hp-loss-wrap')[2].classList.add('hp-loss-hud-disabled');
|
|
}
|
|
})
|
|
$(html).find('.loss-hp-hud-click').click((event) => {
|
|
event.preventDefault();
|
|
let hpLoss = event.currentTarget.dataset.hpValue;
|
|
if (token) {
|
|
let tokenFull = canvas.tokens.placeables.find(t => t.id === token._id);
|
|
console.log(tokenFull, token)
|
|
let actor = tokenFull.actor;
|
|
actor.applyDamage(Number(hpLoss));
|
|
$(html).find('.hp-loss-wrap')[0].classList.remove('hp-loss-hud-active');
|
|
$(html).find('.hp-loss-wrap')[0].classList.add('hp-loss-hud-disabled');
|
|
$(html).find('.hp-loss-wrap')[1].classList.remove('hp-loss-hud-active');
|
|
$(html).find('.hp-loss-wrap')[1].classList.add('hp-loss-hud-disabled');
|
|
$(html).find('.hp-loss-wrap')[2].classList.remove('hp-loss-hud-active');
|
|
$(html).find('.hp-loss-wrap')[2].classList.add('hp-loss-hud-disabled');
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static handleSocketEvent(msg = {}) {
|
|
console.log(`handleSocketEvent !`, msg)
|
|
let actor
|
|
switch (msg.type) {
|
|
case "rollInitiative":
|
|
actor = game.actors.get(msg.actorId)
|
|
actor.system.rollInitiative(msg.combatId, msg.combatantId)
|
|
break
|
|
case "rollProgressionDice":
|
|
actor = game.actors.get(msg.actorId)
|
|
actor.system.rollProgressionDice(msg.combatId, msg.combatantId, msg.rollProgressionCount)
|
|
break
|
|
case "requestDefense":
|
|
// Vérifier si le message est destiné à cet utilisateur
|
|
if (msg.userId === game.user.id) {
|
|
LethalFantasyUtils.showDefenseRequest(msg)
|
|
}
|
|
break
|
|
case "offerAttackerGrit":
|
|
// Vérifier si le message est destiné à cet utilisateur
|
|
if (msg.userId === game.user.id) {
|
|
LethalFantasyUtils.handleAttackerGritOffer(msg)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static async handleAttackerGritOffer(msg) {
|
|
const { attackerId, attackRoll, defenseRoll, attackerName, defenderName, attackWeaponId, attackRollType, attackRollKey, defenderId } = msg
|
|
|
|
const attacker = game.actors.get(attackerId)
|
|
if (!attacker) {
|
|
console.warn("Attacker not found:", attackerId)
|
|
return
|
|
}
|
|
|
|
const attackBonus = await LethalFantasyUtils.offerAttackerGritBonus(
|
|
attacker,
|
|
attackRoll,
|
|
defenseRoll,
|
|
attackerName,
|
|
defenderName
|
|
)
|
|
|
|
const attackRollFinal = attackRoll + attackBonus
|
|
|
|
// Maintenant créer le message de comparaison
|
|
await LethalFantasyUtils.compareAttackDefense({
|
|
attackerName,
|
|
attackerId,
|
|
attackRoll: attackRollFinal,
|
|
attackWeaponId,
|
|
attackRollType,
|
|
attackRollKey,
|
|
defenderName,
|
|
defenderId,
|
|
defenseRoll
|
|
})
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static async showDefenseRequest(msg) {
|
|
const attackerName = msg.attackerName
|
|
const attackerId = msg.attackerId
|
|
const defenderName = msg.defenderName
|
|
const weaponName = msg.weaponName || "attack"
|
|
const attackRoll = msg.attackRoll
|
|
const attackWeaponId = msg.attackWeaponId
|
|
const attackRollType = msg.attackRollType
|
|
const attackRollKey = msg.attackRollKey
|
|
const combatantId = msg.combatantId
|
|
const tokenId = msg.tokenId
|
|
|
|
// Récupérer le défenseur - essayer d'abord depuis le combat, puis depuis le token
|
|
let defender = null
|
|
|
|
if (game.combat && combatantId) {
|
|
const combatant = game.combat.combatants.get(combatantId)
|
|
if (combatant) {
|
|
defender = combatant.actor
|
|
}
|
|
}
|
|
|
|
// Si pas trouvé dans le combat, chercher le token directement
|
|
if (!defender && tokenId) {
|
|
const token = canvas.tokens.get(tokenId)
|
|
if (token) {
|
|
defender = token.actor
|
|
}
|
|
}
|
|
|
|
if (!defender) {
|
|
ui.notifications.error("Defender actor not found")
|
|
return
|
|
}
|
|
|
|
const isMonster = defender.type === "monster"
|
|
|
|
// Pour les monstres, récupérer les attaques activées
|
|
if (isMonster) {
|
|
const enabledAttacks = Object.entries(defender.system.attacks).filter(([key, attack]) => attack.enabled)
|
|
|
|
if (enabledAttacks.length === 0) {
|
|
ui.notifications.warn("No enabled attacks available for defense")
|
|
return
|
|
}
|
|
|
|
// Créer le contenu du dialogue pour monstre
|
|
let attacksHTML = enabledAttacks.map(([key, attack]) =>
|
|
`<option value="${key}">${attack.name}</option>`
|
|
).join("")
|
|
|
|
const content = `
|
|
<div class="defense-request-dialog">
|
|
<div class="attack-info">
|
|
<p><strong>${attackerName}</strong> attacks <strong>${defenderName}</strong> with <strong>${weaponName}</strong>!</p>
|
|
<p>Attack roll: <strong>${attackRoll}</strong></p>
|
|
</div>
|
|
<div class="weapon-selection">
|
|
<label for="defense-attack">Choose your defense attack:</label>
|
|
<select id="defense-attack" name="attackKey" style="width: 100%; margin-top: 8px;">
|
|
${attacksHTML}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
`
|
|
|
|
// Afficher le dialogue
|
|
const result = await foundry.applications.api.DialogV2.wait({
|
|
window: { title: "Defense Roll" },
|
|
classes: ["lethalfantasy"],
|
|
content,
|
|
buttons: [
|
|
{
|
|
label: "Roll Defense",
|
|
icon: "fa-solid fa-shield",
|
|
callback: (event, button, dialog) => {
|
|
const attackKey = button.form.elements.attackKey.value
|
|
return attackKey
|
|
},
|
|
},
|
|
],
|
|
rejectClose: false
|
|
})
|
|
|
|
// Si l'utilisateur a validé, lancer le jet de défense
|
|
if (result) {
|
|
// Stocker temporairement les données pour le hook preCreateChatMessage
|
|
game.lethalFantasy = game.lethalFantasy || {}
|
|
game.lethalFantasy.nextDefenseData = {
|
|
attackerId,
|
|
attackRoll,
|
|
attackerName,
|
|
defenderName,
|
|
attackWeaponId,
|
|
attackRollType,
|
|
attackRollKey,
|
|
defenderId: defender.id
|
|
}
|
|
|
|
console.log("Storing defense data for monster:", defender.id)
|
|
|
|
defender.system.prepareMonsterRoll("monster-defense", result)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Pour les personnages, récupérer les armes équipées
|
|
const equippedWeapons = defender.items.filter(i =>
|
|
i.type === "weapon" && i.system.equipped === true
|
|
)
|
|
|
|
if (equippedWeapons.length === 0) {
|
|
ui.notifications.warn("No equipped weapons for defense")
|
|
return
|
|
}
|
|
|
|
// Créer le contenu du dialogue pour personnage
|
|
let weaponsHTML = equippedWeapons.map(w =>
|
|
`<option value="${w.id}">${w.name}</option>`
|
|
).join("")
|
|
|
|
const content = `
|
|
<div class="defense-request-dialog">
|
|
<div class="attack-info">
|
|
<p><strong>${attackerName}</strong> attacks <strong>${defenderName}</strong> with <strong>${weaponName}</strong>!</p>
|
|
<p>Attack roll: <strong>${attackRoll}</strong></p>
|
|
</div>
|
|
<div class="weapon-selection">
|
|
<label for="defense-weapon">Choose your defense weapon:</label>
|
|
<select id="defense-weapon" name="weaponId" style="width: 100%; margin-top: 8px;">
|
|
${weaponsHTML}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
`
|
|
|
|
// Afficher le dialogue
|
|
const result = await foundry.applications.api.DialogV2.wait({
|
|
window: { title: "Defense Roll" },
|
|
classes: ["lethalfantasy"],
|
|
content,
|
|
buttons: [
|
|
{
|
|
label: "Roll Defense",
|
|
icon: "fa-solid fa-shield",
|
|
callback: (event, button, dialog) => {
|
|
const weaponId = button.form.elements.weaponId.value
|
|
return weaponId
|
|
},
|
|
},
|
|
],
|
|
rejectClose: false
|
|
})
|
|
|
|
// Si l'utilisateur a validé, lancer le jet de défense
|
|
if (result) {
|
|
// Stocker temporairement les données pour le hook preCreateChatMessage
|
|
game.lethalFantasy = game.lethalFantasy || {}
|
|
game.lethalFantasy.nextDefenseData = {
|
|
attackerId,
|
|
attackRoll,
|
|
attackerName,
|
|
defenderName,
|
|
attackWeaponId,
|
|
attackRollType,
|
|
attackRollKey,
|
|
defenderId: defender.id
|
|
}
|
|
|
|
console.log("Storing defense data for character:", defender.id)
|
|
|
|
defender.prepareRoll("weapon-defense", result)
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static async offerGritLuckBonus(defender, attackRoll, currentDefenseRoll, attackerName, defenderName) {
|
|
let totalBonus = 0
|
|
let keepOffering = true
|
|
|
|
while (keepOffering && currentDefenseRoll + totalBonus < attackRoll) {
|
|
const currentGrit = defender.system.grit.current
|
|
const currentLuck = defender.system.luck.current
|
|
|
|
// Si plus de points disponibles, sortir
|
|
if (currentGrit <= 0 && currentLuck <= 0) {
|
|
break
|
|
}
|
|
|
|
const buttons = []
|
|
|
|
if (currentGrit > 0) {
|
|
buttons.push({
|
|
action: "grit",
|
|
label: `Spend 1 Grit (+1D6) [${currentGrit} left]`,
|
|
icon: "fa-solid fa-fist-raised",
|
|
callback: () => "grit"
|
|
})
|
|
}
|
|
|
|
if (currentLuck > 0) {
|
|
buttons.push({
|
|
action: "luck",
|
|
label: `Spend 1 Luck (+1D6) [${currentLuck} left]`,
|
|
icon: "fa-solid fa-clover",
|
|
callback: () => "luck"
|
|
})
|
|
}
|
|
|
|
buttons.push({
|
|
action: "continue",
|
|
label: "Continue (no bonus)",
|
|
icon: "fa-solid fa-forward",
|
|
callback: () => "continue"
|
|
})
|
|
|
|
const content = `
|
|
<div class="grit-luck-dialog">
|
|
<div class="combat-status">
|
|
<p><strong>${attackerName}</strong> rolled <strong>${attackRoll}</strong></p>
|
|
<p><strong>${defenderName}</strong> currently has <strong>${currentDefenseRoll + totalBonus}</strong></p>
|
|
${totalBonus > 0 ? `<p class="bonus-info">Bonus already added: +${totalBonus}</p>` : ''}
|
|
</div>
|
|
<p class="offer-text">You are losing! Spend Grit or Luck to add 1D6 to your defense?</p>
|
|
</div>
|
|
`
|
|
|
|
const choice = await foundry.applications.api.DialogV2.wait({
|
|
window: { title: "Defend with Grit or Luck" },
|
|
classes: ["lethalfantasy"],
|
|
content,
|
|
buttons,
|
|
rejectClose: false
|
|
})
|
|
|
|
if (!choice || choice === "continue") {
|
|
keepOffering = false
|
|
break
|
|
}
|
|
|
|
// Lancer 1D6
|
|
const bonusRoll = new Roll("1d6")
|
|
await bonusRoll.evaluate()
|
|
|
|
if (game?.dice3d) {
|
|
await game.dice3d.showForRoll(bonusRoll, game.user, true)
|
|
}
|
|
|
|
totalBonus += bonusRoll.total
|
|
|
|
// Déduire le point de Grit ou Luck
|
|
if (choice === "grit") {
|
|
await defender.update({ "system.grit.current": currentGrit - 1 })
|
|
await ChatMessage.create({
|
|
content: `<p><strong>${defenderName}</strong> spends 1 Grit and rolls <strong>${bonusRoll.total}</strong>! (Total defense bonus: +${totalBonus})</p>`,
|
|
speaker: ChatMessage.getSpeaker({ actor: defender })
|
|
})
|
|
} else if (choice === "luck") {
|
|
await defender.update({ "system.luck.current": currentLuck - 1 })
|
|
await ChatMessage.create({
|
|
content: `<p><strong>${defenderName}</strong> spends 1 Luck and rolls <strong>${bonusRoll.total}</strong>! (Total defense bonus: +${totalBonus})</p>`,
|
|
speaker: ChatMessage.getSpeaker({ actor: defender })
|
|
})
|
|
}
|
|
}
|
|
|
|
return totalBonus
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static async offerAttackerGritBonus(attacker, currentAttackRoll, defenseRoll, attackerName, defenderName) {
|
|
let totalBonus = 0
|
|
let keepOffering = true
|
|
|
|
while (keepOffering && currentAttackRoll + totalBonus <= defenseRoll) {
|
|
const currentGrit = attacker.system.grit.current
|
|
|
|
// Si plus de points de Grit disponibles, sortir
|
|
if (currentGrit <= 0) {
|
|
break
|
|
}
|
|
|
|
const buttons = [
|
|
{
|
|
action: "grit",
|
|
label: `Spend 1 Grit (+1D6) [${currentGrit} left]`,
|
|
icon: "fa-solid fa-fist-raised",
|
|
callback: () => "grit"
|
|
},
|
|
{
|
|
action: "continue",
|
|
label: "Continue (no bonus)",
|
|
icon: "fa-solid fa-forward",
|
|
callback: () => "continue"
|
|
}
|
|
]
|
|
|
|
const content = `
|
|
<div class="grit-luck-dialog">
|
|
<div class="combat-status">
|
|
<p><strong>${attackerName}</strong> currently has <strong>${currentAttackRoll + totalBonus}</strong></p>
|
|
<p><strong>${defenderName}</strong> rolled <strong>${defenseRoll}</strong></p>
|
|
${totalBonus > 0 ? `<p class="bonus-info">Bonus already added: +${totalBonus}</p>` : ''}
|
|
</div>
|
|
<p class="offer-text">You are losing! Spend Grit to add 1D6 to your attack?</p>
|
|
</div>
|
|
`
|
|
|
|
const choice = await foundry.applications.api.DialogV2.wait({
|
|
window: { title: "Attack with Grit" },
|
|
classes: ["lethalfantasy"],
|
|
content,
|
|
buttons,
|
|
rejectClose: false
|
|
})
|
|
|
|
if (!choice || choice === "continue") {
|
|
keepOffering = false
|
|
break
|
|
}
|
|
|
|
// Lancer 1D6
|
|
const bonusRoll = new Roll("1d6")
|
|
await bonusRoll.evaluate()
|
|
|
|
if (game?.dice3d) {
|
|
await game.dice3d.showForRoll(bonusRoll, game.user, true)
|
|
}
|
|
|
|
totalBonus += bonusRoll.total
|
|
|
|
// Déduire le point de Grit
|
|
await attacker.update({ "system.grit.current": currentGrit - 1 })
|
|
await ChatMessage.create({
|
|
content: `<p><strong>${attackerName}</strong> spends 1 Grit and rolls <strong>${bonusRoll.total}</strong>! (Total attack bonus: +${totalBonus})</p>`,
|
|
speaker: ChatMessage.getSpeaker({ actor: attacker })
|
|
})
|
|
}
|
|
|
|
return totalBonus
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static async compareAttackDefense(data) {
|
|
console.log("compareAttackDefense called with:", data)
|
|
const isAttackWin = data.attackRoll > data.defenseRoll
|
|
console.log("isAttackWin:", isAttackWin, "attackRoll:", data.attackRoll, "defenseRoll:", data.defenseRoll)
|
|
|
|
let damageButton = ""
|
|
if (isAttackWin && (data.attackWeaponId || data.attackRollKey)) {
|
|
console.log("Creating damage button. defenderId:", data.defenderId)
|
|
// Déterminer le type de dégâts à lancer
|
|
if (data.attackRollType === "weapon-attack") {
|
|
damageButton = `
|
|
<div class="attack-result-damage">
|
|
<button class="roll-damage-btn" data-attacker-id="${data.attackerId}" data-defender-id="${data.defenderId}" data-weapon-id="${data.attackWeaponId}" data-damage-type="small">
|
|
<i class="fa-solid fa-dice-d6"></i> Damage (Small)
|
|
</button>
|
|
<button class="roll-damage-btn" data-attacker-id="${data.attackerId}" data-defender-id="${data.defenderId}" data-weapon-id="${data.attackWeaponId}" data-damage-type="medium">
|
|
<i class="fa-solid fa-dice-d20"></i> Damage (Medium)
|
|
</button>
|
|
</div>
|
|
`
|
|
} else if (data.attackRollType === "monster-attack") {
|
|
damageButton = `
|
|
<div class="attack-result-damage">
|
|
<button class="roll-damage-btn" data-attacker-id="${data.attackerId}" data-defender-id="${data.defenderId}" data-attack-key="${data.attackRollKey}" data-damage-type="monster">
|
|
<i class="fa-solid fa-burst"></i> Damage
|
|
</button>
|
|
</div>
|
|
`
|
|
}
|
|
}
|
|
|
|
const resultMessage = `
|
|
<div class="attack-result ${isAttackWin ? 'attack-success' : 'attack-failure'}">
|
|
<h3><i class="fa-solid ${isAttackWin ? 'fa-sword' : 'fa-shield'}"></i> Combat Result</h3>
|
|
<div class="combat-comparison">
|
|
<div class="combat-side attacker ${isAttackWin ? 'winner' : 'loser'}">
|
|
<div class="side-label">Attacker</div>
|
|
<div class="side-info">
|
|
<div class="side-name">${data.attackerName}</div>
|
|
<div class="side-roll">${data.attackRoll}</div>
|
|
</div>
|
|
</div>
|
|
<div class="combat-vs">VS</div>
|
|
<div class="combat-side defender ${isAttackWin ? 'loser' : 'winner'}">
|
|
<div class="side-label">Defender</div>
|
|
<div class="side-info">
|
|
<div class="side-name">${data.defenderName}</div>
|
|
<div class="side-roll">${data.defenseRoll}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="combat-result-text">
|
|
${isAttackWin ?
|
|
`<i class="fa-solid fa-circle-check"></i> <strong>${data.attackerName}</strong> hits <strong>${data.defenderName}</strong>!` :
|
|
`<i class="fa-solid fa-shield-halved"></i> <strong>${data.defenderName}</strong> parries the attack!`
|
|
}
|
|
</div>
|
|
${damageButton}
|
|
</div>
|
|
`
|
|
|
|
console.log("Creating combat result message...")
|
|
await ChatMessage.create({
|
|
content: resultMessage,
|
|
speaker: { alias: "Combat System" }
|
|
})
|
|
console.log("Combat result message created!")
|
|
}
|
|
|
|
static registerHandlebarsHelpers() {
|
|
|
|
Handlebars.registerHelper('isNull', function (val) {
|
|
return val == null;
|
|
});
|
|
Handlebars.registerHelper('match', function (val, search) {
|
|
if (val && search) {
|
|
return val?.match(search);
|
|
}
|
|
return false
|
|
});
|
|
|
|
Handlebars.registerHelper('exists', function (val) {
|
|
return val != null && val !== undefined;
|
|
});
|
|
|
|
Handlebars.registerHelper('isEmpty', function (list) {
|
|
if (list) return list.length === 0;
|
|
else return false;
|
|
});
|
|
|
|
Handlebars.registerHelper('notEmpty', function (list) {
|
|
return list.length > 0;
|
|
});
|
|
|
|
Handlebars.registerHelper('isNegativeOrNull', function (val) {
|
|
return val <= 0;
|
|
});
|
|
|
|
Handlebars.registerHelper('isNegative', function (val) {
|
|
return val < 0;
|
|
});
|
|
|
|
Handlebars.registerHelper('isPositive', function (val) {
|
|
return val > 0;
|
|
});
|
|
|
|
Handlebars.registerHelper('equals', function (val1, val2) {
|
|
return val1 === val2;
|
|
});
|
|
|
|
Handlebars.registerHelper('neq', function (val1, val2) {
|
|
return val1 !== val2;
|
|
});
|
|
|
|
Handlebars.registerHelper('gt', function (val1, val2) {
|
|
return val1 > val2;
|
|
})
|
|
|
|
Handlebars.registerHelper('lt', function (val1, val2) {
|
|
return val1 < val2;
|
|
})
|
|
|
|
Handlebars.registerHelper('gte', function (val1, val2) {
|
|
return val1 >= val2;
|
|
})
|
|
|
|
Handlebars.registerHelper('lte', function (val1, val2) {
|
|
return val1 <= val2;
|
|
})
|
|
Handlebars.registerHelper('and', function (val1, val2) {
|
|
return val1 && val2;
|
|
})
|
|
Handlebars.registerHelper('or', function (val1, val2) {
|
|
return val1 || val2;
|
|
})
|
|
|
|
Handlebars.registerHelper('or3', function (val1, val2, val3) {
|
|
return val1 || val2 || val3;
|
|
})
|
|
|
|
Handlebars.registerHelper('for', function (from, to, incr, block) {
|
|
let accum = '';
|
|
for (let i = from; i < to; i += incr)
|
|
accum += block.fn(i);
|
|
return accum;
|
|
})
|
|
|
|
Handlebars.registerHelper('not', function (cond) {
|
|
return !cond;
|
|
})
|
|
Handlebars.registerHelper('count', function (list) {
|
|
return list.length;
|
|
})
|
|
Handlebars.registerHelper('countKeys', function (obj) {
|
|
return Object.keys(obj).length;
|
|
})
|
|
|
|
Handlebars.registerHelper('isEnabled', function (configKey) {
|
|
return game.settings.get("bol", configKey);
|
|
})
|
|
Handlebars.registerHelper('split', function (str, separator, keep) {
|
|
return str.split(separator)[keep];
|
|
})
|
|
|
|
// If you need to add Handlebars helpers, here are a few useful examples:
|
|
Handlebars.registerHelper('concat', function () {
|
|
let outStr = '';
|
|
for (let arg in arguments) {
|
|
if (typeof arguments[arg] != 'object') {
|
|
outStr += arguments[arg];
|
|
}
|
|
}
|
|
return outStr;
|
|
})
|
|
|
|
Handlebars.registerHelper('add', function (a, b) {
|
|
return parseInt(a) + parseInt(b);
|
|
});
|
|
Handlebars.registerHelper('mul', function (a, b) {
|
|
return parseInt(a) * parseInt(b);
|
|
})
|
|
Handlebars.registerHelper('sub', function (a, b) {
|
|
return parseInt(a) - parseInt(b);
|
|
})
|
|
Handlebars.registerHelper('abbrev2', function (a) {
|
|
return a.substring(0, 2);
|
|
})
|
|
Handlebars.registerHelper('abbrev3', function (a) {
|
|
return a.substring(0, 3);
|
|
})
|
|
Handlebars.registerHelper('valueAtIndex', function (arr, idx) {
|
|
return arr[idx];
|
|
})
|
|
Handlebars.registerHelper('includesKey', function (items, type, key) {
|
|
return items.filter(i => i.type === type).map(i => i.system.key).includes(key);
|
|
})
|
|
Handlebars.registerHelper('includes', function (array, val) {
|
|
return array.includes(val);
|
|
})
|
|
Handlebars.registerHelper('eval', function (expr) {
|
|
return eval(expr);
|
|
})
|
|
Handlebars.registerHelper('isOwnerOrGM', function (actor) {
|
|
console.log("Testing actor", actor.isOwner, game.userId)
|
|
return actor.isOwner || game.isGM;
|
|
})
|
|
Handlebars.registerHelper('upperCase', function (text) {
|
|
if (typeof text !== 'string') return text
|
|
return text.toUpperCase()
|
|
})
|
|
Handlebars.registerHelper('upperFirst', function (text) {
|
|
if (typeof text !== 'string') return text
|
|
return text.charAt(0).toUpperCase() + text.slice(1)
|
|
})
|
|
Handlebars.registerHelper('upperFirstOnly', function (text) {
|
|
if (typeof text !== 'string') return text
|
|
return text.charAt(0).toUpperCase()
|
|
})
|
|
|
|
// Handle v12 removal of this helper
|
|
Handlebars.registerHelper('select', function (selected, options) {
|
|
const escapedValue = RegExp.escape(Handlebars.escapeExpression(selected));
|
|
const rgx = new RegExp(' value=[\"\']' + escapedValue + '[\"\']');
|
|
const html = options.fn(this);
|
|
return html.replace(rgx, "$& selected");
|
|
});
|
|
|
|
}
|
|
|
|
static getLethargyDice(level) {
|
|
for (let s of SYSTEM.SPELL_LETHARGY_DICE) {
|
|
if (Number(level) <= s.maxLevel) {
|
|
return s.dice
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static async applyDamage(message, event) {
|
|
// Récupérer les données du message
|
|
let combatantId = event.currentTarget.dataset.combatantId
|
|
if (!combatantId || !game.combat) {
|
|
ui.notifications.error("No combatant selected")
|
|
return
|
|
}
|
|
|
|
let combatant = game.combat.combatants.get(combatantId)
|
|
if (!combatant) {
|
|
ui.notifications.error("Combatant not found")
|
|
return
|
|
}
|
|
|
|
let targetActor = combatant.token?.actor || game.actors.get(combatant.actorId)
|
|
if (!targetActor) {
|
|
ui.notifications.error("Target actor not found")
|
|
return
|
|
}
|
|
|
|
// Récupérer les données de dégâts du message
|
|
let damageTotal = message.rolls[0]?.total || 0
|
|
let weaponName = message.rolls[0]?.options?.rollName || "Unknown Weapon"
|
|
|
|
// Calculer les DR
|
|
let armorDR = targetActor.computeDamageReduction() || 0
|
|
let shieldDR = targetActor.getShieldDR() || 0
|
|
let totalDR = armorDR + shieldDR
|
|
|
|
// Créer le dialogue
|
|
const content = await foundry.applications.handlebars.renderTemplate(
|
|
"systems/fvtt-lethal-fantasy/templates/apply-damage-dialog.hbs",
|
|
{
|
|
targetName: targetActor.name,
|
|
weaponName: weaponName,
|
|
damageTotal: damageTotal,
|
|
armorDR: armorDR,
|
|
shieldDR: shieldDR,
|
|
totalDR: totalDR,
|
|
damageNoDR: damageTotal,
|
|
damageWithArmor: Math.max(0, damageTotal - armorDR),
|
|
damageWithAll: Math.max(0, damageTotal - totalDR)
|
|
}
|
|
)
|
|
|
|
const result = await foundry.applications.api.DialogV2.wait({
|
|
window: { title: "Apply Damage" },
|
|
classes: ["lethalfantasy"],
|
|
position: { width: 280 },
|
|
content,
|
|
buttons: [
|
|
{
|
|
action: "noDR",
|
|
label: "No DR",
|
|
callback: () => ({ drType: "none", damage: damageTotal })
|
|
},
|
|
{
|
|
action: "armorDR",
|
|
label: "With Armor DR",
|
|
callback: () => ({ drType: "armor", damage: Math.max(0, damageTotal - armorDR) })
|
|
},
|
|
{
|
|
action: "allDR",
|
|
label: "With Armor + Shield DR",
|
|
callback: () => ({ drType: "all", damage: Math.max(0, damageTotal - totalDR) })
|
|
},
|
|
{
|
|
action: "cancel",
|
|
label: "Cancel",
|
|
callback: () => null
|
|
}
|
|
],
|
|
rejectClose: false
|
|
})
|
|
|
|
if (result && result.damage !== undefined) {
|
|
await targetActor.applyDamage(-result.damage)
|
|
|
|
// Message de confirmation
|
|
let drText = ""
|
|
if (result.drType === "armor") {
|
|
drText = `Armor DR: ${armorDR}`
|
|
} else if (result.drType === "all") {
|
|
drText = `Total DR: ${totalDR}`
|
|
}
|
|
|
|
const messageContent = await foundry.applications.handlebars.renderTemplate(
|
|
"systems/fvtt-lethal-fantasy/templates/damage-applied-message.hbs",
|
|
{
|
|
targetName: targetActor.name,
|
|
damage: result.damage,
|
|
drText: drText,
|
|
weaponName: weaponName
|
|
}
|
|
)
|
|
|
|
ChatMessage.create({
|
|
user: game.user.id,
|
|
speaker: { alias: targetActor.name },
|
|
rollMode: "gmroll",
|
|
content: messageContent
|
|
})
|
|
}
|
|
}
|
|
|
|
}
|